From 6e91bfb88990f1f64b46cc1c1193ee18ffb446ac Mon Sep 17 00:00:00 2001 From: Nate Rupsis Date: Mon, 13 Feb 2023 12:41:29 -0500 Subject: [PATCH] just adding in the Phab DIFF to local repo to easily see what's left to refactor --- source/blender/blenkernel/BKE_nla.h | 2 +- source/blender/blenkernel/BKE_nla.h.orig | 437 +++ source/blender/blenkernel/BKE_nla.h.rej | 145 + source/blender/blenkernel/intern/ipo.c | 6 +- source/blender/blenkernel/intern/nla.c | 6 +- source/blender/blenkernel/intern/nla.c.orig | 2339 ++++++++++++++ source/blender/blenkernel/intern/nla.c.rej | 478 +++ .../editors/space_action/action_data.c.orig | 988 ++++++ .../editors/space_action/action_data.c.rej | 25 + .../blender/editors/space_nla/nla_channels.c | 8 +- .../editors/space_nla/nla_channels.c.orig | 828 +++++ source/blender/editors/space_nla/nla_edit.c | 36 +- .../blender/editors/space_nla/nla_edit.c.orig | 2850 +++++++++++++++++ .../blender/editors/space_nla/nla_edit.c.rej | 71 + .../editors/transform/transform_convert_nla.c | 3 +- .../transform/transform_convert_nla.c.orig | 549 ++++ .../transform/transform_convert_nla.c.rej | 38 + .../blender/makesrna/intern/rna_animation.c | 11 +- source/blender/makesrna/intern/rna_nla.c.orig | 1071 +++++++ source/blender/makesrna/intern/rna_nla.c.rej | 44 + source/tools | 2 +- 21 files changed, 9899 insertions(+), 38 deletions(-) create mode 100644 source/blender/blenkernel/BKE_nla.h.orig create mode 100644 source/blender/blenkernel/BKE_nla.h.rej create mode 100644 source/blender/blenkernel/intern/nla.c.orig create mode 100644 source/blender/blenkernel/intern/nla.c.rej create mode 100644 source/blender/editors/space_action/action_data.c.orig create mode 100644 source/blender/editors/space_action/action_data.c.rej create mode 100644 source/blender/editors/space_nla/nla_channels.c.orig create mode 100644 source/blender/editors/space_nla/nla_edit.c.orig create mode 100644 source/blender/editors/space_nla/nla_edit.c.rej create mode 100644 source/blender/editors/transform/transform_convert_nla.c.orig create mode 100644 source/blender/editors/transform/transform_convert_nla.c.rej create mode 100644 source/blender/makesrna/intern/rna_nla.c.orig create mode 100644 source/blender/makesrna/intern/rna_nla.c.rej diff --git a/source/blender/blenkernel/BKE_nla.h b/source/blender/blenkernel/BKE_nla.h index 0fa3d16d328..24f2ca4f94c 100644 --- a/source/blender/blenkernel/BKE_nla.h +++ b/source/blender/blenkernel/BKE_nla.h @@ -179,7 +179,7 @@ void BKE_nlastrips_clear_metastrip(ListBase *strips, struct NlaStrip *strip); * Add the given NLA-Strip to the given Meta-Strip, assuming that the * strip isn't attached to any list of strips */ -bool BKE_nlameta_add_strip(struct NlaStrip *mstrip, struct NlaStrip *strip); +bool BKE_nlameta_try_add_strip(struct NlaStrip *mstrip, struct NlaStrip *strip); /** * Adjust the settings of NLA-Strips contained within a Meta-Strip (recursively), * until the Meta-Strips children all fit within the Meta-Strip's new dimensions diff --git a/source/blender/blenkernel/BKE_nla.h.orig b/source/blender/blenkernel/BKE_nla.h.orig new file mode 100644 index 00000000000..0fa3d16d328 --- /dev/null +++ b/source/blender/blenkernel/BKE_nla.h.orig @@ -0,0 +1,437 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later + * Copyright 2009 Blender Foundation, Joshua Leung. All rights reserved. */ + +#pragma once + +/** \file + * \ingroup bke + */ + +/** Temp constant defined for these functions only. */ +#define NLASTRIP_MIN_LEN_THRESH 0.1f + +#include "DNA_listBase.h" + +#ifdef __cplusplus +extern "C" { +#endif + +struct AnimData; +struct LibraryForeachIDData; +struct Main; +struct NlaStrip; +struct NlaTrack; +struct Scene; +struct Speaker; +struct bAction; + +struct BlendDataReader; +struct BlendExpander; +struct BlendLibReader; +struct BlendWriter; +struct PointerRNA; +struct PropertyRNA; + +/* ----------------------------- */ +/* Data Management */ + +/** + * Frees the given NLA strip, and calls #BKE_nlastrip_remove_and_free to + * remove and free all children strips. + */ +void BKE_nlastrip_free(struct NlaStrip *strip, bool do_id_user); +/** + * Remove the given NLA track from the set of NLA tracks, free the track's data, + * and the track itself. + */ +void BKE_nlatrack_free(ListBase *tracks, struct NlaTrack *nlt, bool do_id_user); +/** + * Free the elements of type NLA Tracks provided in the given list, but do not free + * the list itself since that is not free-standing + */ +void BKE_nla_tracks_free(ListBase *tracks, bool do_id_user); + +/** + * Copy NLA strip + * + * \param use_same_action: When true, the existing action is used (instead of being duplicated) + * \param flag: Control ID pointers management, see LIB_ID_CREATE_.../LIB_ID_COPY_... + * flags in BKE_lib_id.h + */ +struct NlaStrip *BKE_nlastrip_copy(struct Main *bmain, + struct NlaStrip *strip, + bool use_same_action, + int flag); +/** + * Copy a single NLA Track. + * \param flag: Control ID pointers management, see LIB_ID_CREATE_.../LIB_ID_COPY_... + * flags in BKE_lib_id.h + */ +struct NlaTrack *BKE_nlatrack_copy(struct Main *bmain, + struct NlaTrack *nlt, + bool use_same_actions, + int flag); +/** + * Copy all NLA data. + * \param flag: Control ID pointers management, see LIB_ID_CREATE_.../LIB_ID_COPY_... + * flags in BKE_lib_id.h + */ +void BKE_nla_tracks_copy(struct Main *bmain, ListBase *dst, const ListBase *src, int flag); + +/** + * Copy NLA tracks from #adt_source to #adt_dest, and update the active track/strip pointers to + * point at those copies. + */ +void BKE_nla_tracks_copy_from_adt(struct Main *bmain, + struct AnimData *adt_dest, + const struct AnimData *adt_source, + int flag); + +/** + * Add a NLA Track to the given AnimData. + * \param prev: NLA-Track to add the new one after. + */ +struct NlaTrack *BKE_nlatrack_add(struct AnimData *adt, + struct NlaTrack *prev, + bool is_liboverride); + +/** + * Create a NLA Strip referencing the given Action. + */ +struct NlaStrip *BKE_nlastrip_new(struct bAction *act); + +/* + * Removes the given NLA strip from the list of strips provided. + */ +void BKE_nlastrip_remove(ListBase *strips, struct NlaStrip *strip); + +/* + * Removes the given NLA strip from the list of strips provided, and frees it's memory. + */ +void BKE_nlastrip_remove_and_free(ListBase *strips, struct NlaStrip *strip, const bool do_id_user); + +/** + * Add new NLA-strip to the top of the NLA stack - i.e. + * into the last track if space, or a new one otherwise. + */ +struct NlaStrip *BKE_nlastack_add_strip(struct AnimData *adt, + struct bAction *act, + bool is_liboverride); +/** + * Add a NLA Strip referencing the given speaker's sound. + */ +struct NlaStrip *BKE_nla_add_soundstrip(struct Main *bmain, + struct Scene *scene, + struct Speaker *speaker); + +/** + * Callback used by lib_query to walk over all ID usages + * (mimics `foreach_id` callback of #IDTypeInfo structure). + */ +void BKE_nla_strip_foreach_id(struct NlaStrip *strip, struct LibraryForeachIDData *data); + +/* ----------------------------- */ +/* API */ + +/** + * Check if there is any space in the given list to add the given strip. + */ +bool BKE_nlastrips_has_space(ListBase *strips, float start, float end); +/** + * Rearrange the strips in the track so that they are always in order + * (usually only needed after a strip has been moved) + */ +void BKE_nlastrips_sort_strips(ListBase *strips); + +/** + * Add the given NLA-Strip to the given list of strips, assuming that it + * isn't currently a member of another list, NULL, or conflicting with existing + * strips position. + */ +void BKE_nlastrips_add_strip_unsafe(ListBase *strips, struct NlaStrip *strip); + +/** + * NULL checks incoming strip and verifies no overlap / invalid + * configuration against other strips in NLA Track before calling + * #BKE_nlastrips_add_strip_unsafe. + */ +bool BKE_nlastrips_add_strip(ListBase *strips, struct NlaStrip *strip); + +/** + * Convert 'islands' (i.e. continuous string of) selected strips to be + * contained within 'Meta-Strips' which act as strips which contain strips. + * + * \param is_temp: are the meta-strips to be created 'temporary' ones used for transforms? + */ +void BKE_nlastrips_make_metas(ListBase *strips, bool is_temp); +/** + * Remove meta-strips (i.e. flatten the list of strips) from the top-level of the list of strips. + * + * \param only_sel: only consider selected meta-strips, otherwise all meta-strips are removed + * \param only_temp: only remove the 'temporary' meta-strips used for transforms + */ +void BKE_nlastrips_clear_metas(ListBase *strips, bool only_sel, bool only_temp); +/** + * Split a meta-strip into a set of normal strips. + */ +void BKE_nlastrips_clear_metastrip(ListBase *strips, struct NlaStrip *strip); +/** + * Add the given NLA-Strip to the given Meta-Strip, assuming that the + * strip isn't attached to any list of strips + */ +bool BKE_nlameta_add_strip(struct NlaStrip *mstrip, struct NlaStrip *strip); +/** + * Adjust the settings of NLA-Strips contained within a Meta-Strip (recursively), + * until the Meta-Strips children all fit within the Meta-Strip's new dimensions + */ +void BKE_nlameta_flush_transforms(struct NlaStrip *mstrip); + +/* ............ */ + +/** + * Find the active NLA-track for the given stack. + */ +struct NlaTrack *BKE_nlatrack_find_active(ListBase *tracks); +/** + * Make the given NLA-track the active one for the given stack. If no track is provided, + * this function can be used to simply deactivate all the NLA tracks in the given stack too. + */ +void BKE_nlatrack_set_active(ListBase *tracks, struct NlaTrack *nlt); + +/** + * Get the NLA Track that the active action/action strip comes from, + * since this info is not stored in AnimData. It also isn't as simple + * as just using the active track, since multiple tracks may have been + * entered at the same time. + */ +struct NlaTrack *BKE_nlatrack_find_tweaked(struct AnimData *adt); + +/** + * Toggle the 'solo' setting for the given NLA-track, making sure that it is the only one + * that has this status in its AnimData block. + */ +void BKE_nlatrack_solo_toggle(struct AnimData *adt, struct NlaTrack *nlt); + +/** + * Check if there is any space in the given track to add a strip of the given length. + */ +bool BKE_nlatrack_has_space(struct NlaTrack *nlt, float start, float end); +/** + * Rearrange the strips in the track so that they are always in order + * (usually only needed after a strip has been moved). + */ +void BKE_nlatrack_sort_strips(struct NlaTrack *nlt); + +/** + * Add the given NLA-Strip to the given NLA-Track. + * Calls #BKE_nlastrips_add_strip to check if strip can be added. + */ +bool BKE_nlatrack_add_strip(struct NlaTrack *nlt, struct NlaStrip *strip, bool is_liboverride); + +/** + * Remove the NLA-Strip from the given NLA-Track. + */ +void BKE_nlatrack_remove_strip(struct NlaTrack *track, struct NlaStrip *strip); + +/** + * Get the extents of the given NLA-Track including gaps between strips, + * returning whether this succeeded or not + */ +bool BKE_nlatrack_get_bounds(struct NlaTrack *nlt, float bounds[2]); +/** + * Check whether given NLA track is not local (i.e. from linked data) when the object is a library + * override. + * + * \param nlt: May be NULL, in which case we consider it as a non-local track case. + */ +bool BKE_nlatrack_is_nonlocal_in_liboverride(const struct ID *id, const struct NlaTrack *nlt); + +/* ............ */ + +/** + * Compute the left-hand-side 'frame limit' of that strip, in its NLA track. + * + * \details This is either : + * - the end frame of the previous strip, if the strip's track contains another strip on it left + * - the macro MINFRAMEF, if no strips are to the left of this strip in its track + * + * \param strip: The strip to compute the left-hand-side 'frame limit' of. + * \return The beginning frame of the previous strip, or MINFRAMEF if no strips are next in that + * track. + */ +float BKE_nlastrip_compute_frame_from_previous_strip(struct NlaStrip *strip); +/** + * Compute the right-hand-side 'frame limit' of that strip, in its NLA track. + * + * \details This is either : + * + * - the begin frame of the next strip, if the strip's track contains another strip on it right + * - the macro MAXFRAMEF, if no strips are to the right of this strip in its track + * + * \param strip: The strip to compute the right-hand-side 'frame limit' of. + * \return The beginning frame of the next strip, or MAXFRAMEF if no strips are next in that track. + */ +float BKE_nlastrip_compute_frame_to_next_strip(struct NlaStrip *strip); + +/** + * Returns the next strip in this strip's NLA track, or a null pointer. + * + * \param strip: The strip to find the next trip from. + * \param check_transitions: Whether or not to skip transitions. + * \return The next strip in the track, or NULL if none are present. + */ +struct NlaStrip *BKE_nlastrip_next_in_track(struct NlaStrip *strip, bool skip_transitions); + +/** + * Returns the previous strip in this strip's NLA track, or a null pointer. + * + * \param strip: The strip to find the previous trip from. + * \param check_transitions: Whether or not to skip transitions. + * \return The previous strip in the track, or NULL if none are present. + */ +struct NlaStrip *BKE_nlastrip_prev_in_track(struct NlaStrip *strip, bool skip_transitions); + +/* ............ */ + +/** + * Find the active NLA-strip within the given track. + */ +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); + +/** + * Does the given NLA-strip fall within the given bounds (times)?. + */ +bool BKE_nlastrip_within_bounds(struct NlaStrip *strip, float min, float max); +/** + * Return the distance from the given frame to the NLA strip, measured in frames. + * If the given frame intersects the NLA strip, the distance is zero. + */ +float BKE_nlastrip_distance_to_frame(const struct NlaStrip *strip, float timeline_frame); +/** + * Recalculate the start and end frames for the current strip, after changing + * the extents of the action or the mapping (repeats or scale factor) info. + */ +void BKE_nlastrip_recalculate_bounds(struct NlaStrip *strip); +/** + * Recalculate the start and end frames for the strip to match the bounds of its action such that + * the overall NLA animation result is unchanged. + */ +void BKE_nlastrip_recalculate_bounds_sync_action(struct NlaStrip *strip); + +/** + * Recalculate the blend-in and blend-out values after a strip transform update. + */ +void BKE_nlastrip_recalculate_blend(struct NlaStrip *strip); + +/** + * Find (and set) a unique name for a strip from the whole AnimData block + * Uses a similar method to the BLI method, but is implemented differently + * as we need to ensure that the name is unique over several lists of tracks, + * not just a single track. + */ +void BKE_nlastrip_validate_name(struct AnimData *adt, struct NlaStrip *strip); + +/* ............ */ + +/** + * Check if the given NLA-Track has any strips with own F-Curves. + */ +bool BKE_nlatrack_has_animated_strips(struct NlaTrack *nlt); +/** + * Check if given NLA-Tracks have any strips with own F-Curves. + */ +bool BKE_nlatracks_have_animated_strips(ListBase *tracks); +/** + * Validate the NLA-Strips 'control' F-Curves based on the flags set. + */ +void BKE_nlastrip_validate_fcurves(struct NlaStrip *strip); + +/** + * Check if the given RNA pointer + property combo should be handled by + * NLA strip curves or not. + */ +bool BKE_nlastrip_has_curves_for_property(const struct PointerRNA *ptr, + const struct PropertyRNA *prop); + +/** + * Ensure that auto-blending and other settings are set correctly. + */ +void BKE_nla_validate_state(struct AnimData *adt); + +/* ............ */ + +/** + * Check if an action is "stashed" in the NLA already + * + * The criteria for this are: + * 1) The action in question lives in a "stash" track. + * 2) We only check first-level strips. That is, we will not check inside meta strips. + */ +bool BKE_nla_action_is_stashed(struct AnimData *adt, struct bAction *act); +/** + * "Stash" an action (i.e. store it as a track/layer in the NLA, but non-contributing) + * to retain it in the file for future uses. + */ +bool BKE_nla_action_stash(struct AnimData *adt, bool is_liboverride); + +/* ............ */ + +/** + * For the given AnimData block, add the active action to the NLA + * stack (i.e. 'push-down' action). The UI should only allow this + * for normal editing only (i.e. not in edit-mode for some strip's action), + * so no checks for this are performed. + * + * TODO: maybe we should have checks for this too. + */ +void BKE_nla_action_pushdown(struct AnimData *adt, bool is_liboverride); + +/** + * Find the active strip + track combination, and set them up as the tweaking track, + * and return if successful or not. + */ +bool BKE_nla_tweakmode_enter(struct AnimData *adt); +/** + * Exit tweak-mode for this AnimData block. + */ +void BKE_nla_tweakmode_exit(struct AnimData *adt); + +/* ----------------------------- */ +/* Time Mapping */ + +/* time mapping conversion modes */ +enum eNlaTime_ConvertModes { + /* convert from global time to strip time - for evaluation */ + NLATIME_CONVERT_EVAL = 0, + /* convert from global time to strip time - for editing corrections */ + /* XXX: old 0 invert. */ + NLATIME_CONVERT_UNMAP, + /* convert from strip time to global time */ + /* XXX: old 1 invert. */ + NLATIME_CONVERT_MAP, +}; + +/** + * Non clipped mapping for strip-time <-> global time: + * `mode = eNlaTime_ConvertModes -> NLATIME_CONVERT_*` + * + * Public API method - perform this mapping using the given AnimData block + * and perform any necessary sanity checks on the value + */ +float BKE_nla_tweakedit_remap(struct AnimData *adt, float cframe, short mode); + +/* ----------------------------- */ +/* .blend file API */ + +void BKE_nla_blend_write(struct BlendWriter *writer, struct ListBase *tracks); +void BKE_nla_blend_read_data(struct BlendDataReader *reader, struct ListBase *tracks); +void BKE_nla_blend_read_lib(struct BlendLibReader *reader, struct ID *id, struct ListBase *tracks); +void BKE_nla_blend_read_expand(struct BlendExpander *expander, struct ListBase *tracks); + +#ifdef __cplusplus +} +#endif diff --git a/source/blender/blenkernel/BKE_nla.h.rej b/source/blender/blenkernel/BKE_nla.h.rej new file mode 100644 index 00000000000..ab6c7bd458b --- /dev/null +++ b/source/blender/blenkernel/BKE_nla.h.rej @@ -0,0 +1,145 @@ +*************** +*** 30,44 **** + /* ----------------------------- */ + /* Data Management */ + +- /** +- * Remove the given NLA strip from the NLA track it occupies, free the strip's data, +- * and the strip itself. +- */ + void BKE_nlastrip_free(ListBase *strips, struct NlaStrip *strip, bool do_id_user); +- /** +- * Remove the given NLA track from the set of NLA tracks, free the track's data, +- * and the track itself. +- */ + void BKE_nlatrack_free(ListBase *tracks, struct NlaTrack *nlt, bool do_id_user); + /** + * Free the elements of type NLA Tracks provided in the given list, but do not free +--- 30,38 ---- + /* ----------------------------- */ + /* Data Management */ + ++ /* Free the strip's data and the strip itself. */ + void BKE_nlastrip_free(ListBase *strips, struct NlaStrip *strip, bool do_id_user); ++ /* Free the track's data and the track itself. */ + void BKE_nlatrack_free(ListBase *tracks, struct NlaTrack *nlt, bool do_id_user); + /** + * Free the elements of type NLA Tracks provided in the given list, but do not free +*************** +*** 82,98 **** + const struct AnimData *adt_source, + int flag); + + /** + * Add a NLA Track to the given AnimData. + * \param prev: NLA-Track to add the new one after. + */ +- struct NlaTrack *BKE_nlatrack_add(struct AnimData *adt, +- struct NlaTrack *prev, +- bool is_liboverride); + /** + * Create a NLA Strip referencing the given Action. + */ + struct NlaStrip *BKE_nlastrip_new(struct bAction *act); + /** + * Add new NLA-strip to the top of the NLA stack - i.e. + * into the last track if space, or a new one otherwise. +--- 76,121 ---- + const struct AnimData *adt_source, + int flag); + ++ ++ struct NlaTrack *BKE_nlatrack_new(); ++ void BKE_nlatrack_remove(ListBase *tracks, struct NlaTrack *nlt); ++ void BKE_nlatrack_remove_and_free(ListBase *tracks, struct NlaTrack *nlt, bool do_id_user); ++ ++ void BKE_nlatrack_insert_after(ListBase *nla_tracks, ++ struct NlaTrack *prev, ++ struct NlaTrack *new_track, ++ bool is_liboverride); ++ ++ void BKE_nlatrack_insert_before(ListBase *nla_tracks, ++ struct NlaTrack *next, ++ struct NlaTrack *new_track, ++ bool is_liboverride); + /** + * Add a NLA Track to the given AnimData. + * \param prev: NLA-Track to add the new one after. + */ ++ struct NlaTrack *BKE_nlatrack_new_after_and_set_active(ListBase *nla_tracks, ++ struct NlaTrack *prev, ++ bool is_liboverride); ++ ++ struct NlaTrack *BKE_nlatrack_new_before_and_set_active(ListBase *nla_tracks, ++ struct NlaTrack *next, ++ bool is_liboverride); ++ ++ struct NlaTrack *BKE_nlatrack_new_tail_and_set_active(ListBase *nla_tracks, ++ const bool is_liboverride); ++ struct NlaTrack *BKE_nlatrack_new_head_and_set_active(ListBase *nla_tracks, ++ bool is_liboverride); ++ + /** + * Create a NLA Strip referencing the given Action. + */ + struct NlaStrip *BKE_nlastrip_new(struct bAction *act); ++ ++ void BKE_nlatrack_remove_strip(struct NlaTrack *track, struct NlaStrip *strip); ++ void BKE_nlastrip_remove(ListBase *strips, struct NlaStrip *strip); ++ void BKE_nlastrip_remove_and_free(ListBase *strips, struct NlaStrip *strip, const bool do_id_user); ++ + /** + * Add new NLA-strip to the top of the NLA stack - i.e. + * into the last track if space, or a new one otherwise. +*************** +*** 126,136 **** + */ + void BKE_nlastrips_sort_strips(ListBase *strips); + + /** + * Add the given NLA-Strip to the given list of strips, assuming that it + * isn't currently a member of another list + */ +- bool BKE_nlastrips_add_strip(ListBase *strips, struct NlaStrip *strip); + + /** + * Convert 'islands' (i.e. continuous string of) selected strips to be +--- 149,161 ---- + */ + void BKE_nlastrips_sort_strips(ListBase *strips); + ++ bool BKE_nlastrips_add_strip(ListBase *strips, struct NlaStrip *strip); + /** + * Add the given NLA-Strip to the given list of strips, assuming that it + * isn't currently a member of another list + */ ++ void BKE_nlastrips_add_strip(ListBase *strips, struct NlaStrip *strip); ++ bool BKE_nlastrips_try_add_strip(ListBase *strips, struct NlaStrip *strip); + + /** + * Convert 'islands' (i.e. continuous string of) selected strips to be +*************** +*** 222,232 **** + */ + void BKE_nlatrack_sort_strips(struct NlaTrack *nlt); + +- /** +- * Add the given NLA-Strip to the given NLA-Track, assuming that it +- * isn't currently attached to another one. + */ +- bool BKE_nlatrack_add_strip(struct NlaTrack *nlt, struct NlaStrip *strip, bool is_liboverride); + + /** + * Get the extents of the given NLA-Track including gaps between strips, +--- 247,257 ---- + */ + void BKE_nlatrack_sort_strips(struct NlaTrack *nlt); + ++ void BKE_nlatrack_add_strip(struct NlaTrack *nlt, struct NlaStrip *strip); ++ /** Compared to non-try version, this function does checks (NULL, track flags, whether track has ++ * space for strip, etc). + */ ++ bool BKE_nlatrack_try_add_strip(struct NlaTrack *nlt, struct NlaStrip *strip, bool is_liboverride); + + /** + * Get the extents of the given NLA-Track including gaps between strips, diff --git a/source/blender/blenkernel/intern/ipo.c b/source/blender/blenkernel/intern/ipo.c index a21033a8b91..ab303542c08 100644 --- a/source/blender/blenkernel/intern/ipo.c +++ b/source/blender/blenkernel/intern/ipo.c @@ -2001,12 +2001,12 @@ static void nlastrips_to_animdata(ID *id, ListBase *strips) /* Try to add this strip to the current NLA-Track * (i.e. the 'last' one on the stack at the moment). */ - if (BKE_nlatrack_add_strip(nlt, strip, false) == 0) { + if (!BKE_nlatrack_try_add_strip(nlt, strip, false)) { /* trying to add to the current failed (no space), * so add a new track to the stack, and add to that... */ - nlt = BKE_nlatrack_add(adt, NULL, false); - BKE_nlatrack_add_strip(nlt, strip, false); + nlt = BKE_nlatrack_new_tail_and_set_active(&adt->nla_tracks, false); + BKE_nlatrack_add_strip(nlt, strip); } /* ensure that strip has a name */ diff --git a/source/blender/blenkernel/intern/nla.c b/source/blender/blenkernel/intern/nla.c index 29d4cc802fe..6ac4b621f65 100644 --- a/source/blender/blenkernel/intern/nla.c +++ b/source/blender/blenkernel/intern/nla.c @@ -61,7 +61,7 @@ static void nla_tweakmode_find_active(const ListBase /* NlaTrack */ *nla_tracks, /* Freeing ------------------------------------------- */ -void BKE_nlastrip_free(NlaStrip *strip, const bool do_id_user) +void BKE_nlastrip_free(ListBase *strips, NlaStrip *strip, bool do_id_user) { NlaStrip *cs, *csn; @@ -73,7 +73,7 @@ void BKE_nlastrip_free(NlaStrip *strip, const bool do_id_user) /* free child-strips */ for (cs = strip->strips.first; cs; cs = csn) { csn = cs->next; - BKE_nlastrip_remove_and_free(&strip->strips, cs, do_id_user); + BKE_nlastrip_free(&strip->strips, cs, do_id_user); } /* remove reference to action */ @@ -866,7 +866,7 @@ void BKE_nlastrips_clear_metastrip(ListBase *strips, NlaStrip *strip) } /* free the meta-strip now */ - BKE_nlastrip_remove_and_free(strips, strip, true); + BKE_nlastrip_free(strips, strip, true); } void BKE_nlastrips_clear_metas(ListBase *strips, bool only_sel, bool only_temp) diff --git a/source/blender/blenkernel/intern/nla.c.orig b/source/blender/blenkernel/intern/nla.c.orig new file mode 100644 index 00000000000..29d4cc802fe --- /dev/null +++ b/source/blender/blenkernel/intern/nla.c.orig @@ -0,0 +1,2339 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later + * Copyright 2009 Blender Foundation, Joshua Leung. All rights reserved. */ + +/** \file + * \ingroup bke + */ + +#include +#include +#include +#include +#include +#include + +#include "CLG_log.h" + +#include "MEM_guardedalloc.h" + +#include "BLI_ghash.h" +#include "BLI_listbase.h" +#include "BLI_string.h" +#include "BLI_string_utils.h" +#include "BLI_utildefines.h" + +#include "BLT_translation.h" + +#include "DNA_anim_types.h" +#include "DNA_scene_types.h" +#include "DNA_sound_types.h" +#include "DNA_speaker_types.h" + +#include "BKE_action.h" +#include "BKE_fcurve.h" +#include "BKE_global.h" +#include "BKE_lib_id.h" +#include "BKE_lib_query.h" +#include "BKE_main.h" +#include "BKE_nla.h" +#include "BKE_sound.h" + +#include "BLO_read_write.h" + +#include "RNA_access.h" +#include "RNA_prototypes.h" + +#include "nla_private.h" + +static CLG_LogRef LOG = {"bke.nla"}; + +/** + * Find the active track and strip. + * + * The active strip may or may not be on the active track. + */ +static void nla_tweakmode_find_active(const ListBase /* NlaTrack */ *nla_tracks, + NlaTrack **r_track_of_active_strip, + NlaStrip **r_active_strip); + +/* *************************************************** */ +/* Data Management */ + +/* Freeing ------------------------------------------- */ + +void BKE_nlastrip_free(NlaStrip *strip, const bool do_id_user) +{ + NlaStrip *cs, *csn; + + /* sanity checks */ + if (strip == NULL) { + return; + } + + /* free child-strips */ + for (cs = strip->strips.first; cs; cs = csn) { + csn = cs->next; + BKE_nlastrip_remove_and_free(&strip->strips, cs, do_id_user); + } + + /* remove reference to action */ + if (strip->act != NULL && do_id_user) { + id_us_min(&strip->act->id); + } + + /* free own F-Curves */ + BKE_fcurves_free(&strip->fcurves); + + /* free own F-Modifiers */ + free_fmodifiers(&strip->modifiers); + + /* free the strip itself */ + MEM_freeN(strip); +} + +void BKE_nlatrack_free(ListBase *tracks, NlaTrack *nlt, bool do_id_user) +{ + NlaStrip *strip, *stripn; + + /* sanity checks */ + if (nlt == NULL) { + return; + } + + /* free strips */ + for (strip = nlt->strips.first; strip; strip = stripn) { + stripn = strip->next; + BKE_nlastrip_remove_and_free(&nlt->strips, strip, do_id_user); + } + + /* free NLA track itself now */ + if (tracks) { + BLI_freelinkN(tracks, nlt); + } + else { + MEM_freeN(nlt); + } +} + +void BKE_nla_tracks_free(ListBase *tracks, bool do_id_user) +{ + NlaTrack *nlt, *nltn; + + /* sanity checks */ + if (ELEM(NULL, tracks, tracks->first)) { + return; + } + + /* free tracks one by one */ + for (nlt = tracks->first; nlt; nlt = nltn) { + nltn = nlt->next; + BKE_nlatrack_free(tracks, nlt, do_id_user); + } + + /* clear the list's pointers to be safe */ + BLI_listbase_clear(tracks); +} + +/* Copying ------------------------------------------- */ + +NlaStrip *BKE_nlastrip_copy(Main *bmain, + NlaStrip *strip, + const bool use_same_action, + const int flag) +{ + NlaStrip *strip_d; + NlaStrip *cs, *cs_d; + + const bool do_id_user = (flag & LIB_ID_CREATE_NO_USER_REFCOUNT) == 0; + + /* sanity check */ + if (strip == NULL) { + return NULL; + } + + /* make a copy */ + strip_d = MEM_dupallocN(strip); + strip_d->next = strip_d->prev = NULL; + + /* handle action */ + if (strip_d->act) { + if (use_same_action) { + if (do_id_user) { + /* increase user-count of action */ + id_us_plus(&strip_d->act->id); + } + } + else { + /* use a copy of the action instead (user count shouldn't have changed yet) */ + BKE_id_copy_ex(bmain, &strip_d->act->id, (ID **)&strip_d->act, flag); + } + } + + /* copy F-Curves and modifiers */ + BKE_fcurves_copy(&strip_d->fcurves, &strip->fcurves); + copy_fmodifiers(&strip_d->modifiers, &strip->modifiers); + + /* make a copy of all the child-strips, one at a time */ + BLI_listbase_clear(&strip_d->strips); + + for (cs = strip->strips.first; cs; cs = cs->next) { + cs_d = BKE_nlastrip_copy(bmain, cs, use_same_action, flag); + BLI_addtail(&strip_d->strips, cs_d); + } + + /* return the strip */ + return strip_d; +} + +NlaTrack *BKE_nlatrack_copy(Main *bmain, + NlaTrack *nlt, + const bool use_same_actions, + const int flag) +{ + NlaStrip *strip, *strip_d; + NlaTrack *nlt_d; + + /* sanity check */ + if (nlt == NULL) { + return NULL; + } + + /* make a copy */ + nlt_d = MEM_dupallocN(nlt); + nlt_d->next = nlt_d->prev = NULL; + + /* make a copy of all the strips, one at a time */ + BLI_listbase_clear(&nlt_d->strips); + + for (strip = nlt->strips.first; strip; strip = strip->next) { + strip_d = BKE_nlastrip_copy(bmain, strip, use_same_actions, flag); + BLI_addtail(&nlt_d->strips, strip_d); + } + + /* return the copy */ + return nlt_d; +} + +void BKE_nla_tracks_copy(Main *bmain, ListBase *dst, const ListBase *src, const int flag) +{ + NlaTrack *nlt, *nlt_d; + + /* sanity checks */ + if (ELEM(NULL, dst, src)) { + return; + } + + /* clear out the destination list first for precautions... */ + BLI_listbase_clear(dst); + + /* copy each NLA-track, one at a time */ + for (nlt = src->first; nlt; nlt = nlt->next) { + /* make a copy, and add the copy to the destination list */ + /* XXX: we need to fix this sometime. */ + nlt_d = BKE_nlatrack_copy(bmain, nlt, true, flag); + BLI_addtail(dst, nlt_d); + } +} + +/** + * Find `active_strip` in `strips_source`, then return the strip with the same + * index from `strips_dest`. + */ +static NlaStrip *find_active_strip_from_listbase(const NlaStrip *active_strip, + const ListBase /* NlaStrip */ *strips_source, + const ListBase /* NlaStrip */ *strips_dest) +{ + BLI_assert_msg(BLI_listbase_count(strips_source) == BLI_listbase_count(strips_dest), + "Expecting the same number of source and destination strips"); + + NlaStrip *strip_dest = strips_dest->first; + LISTBASE_FOREACH (const NlaStrip *, strip_source, strips_source) { + if (strip_dest == NULL) { + /* The tracks are assumed to have an equal number of strips, but this is + * not the case. Not sure when this might happen, but it's better to not + * crash. */ + break; + } + if (strip_source == active_strip) { + return strip_dest; + } + + const bool src_is_meta = strip_source->type == NLASTRIP_TYPE_META; + const bool dst_is_meta = strip_dest->type == NLASTRIP_TYPE_META; + BLI_assert_msg(src_is_meta == dst_is_meta, + "Expecting topology of source and destination strips to be equal"); + if (src_is_meta && dst_is_meta) { + NlaStrip *found_in_meta = find_active_strip_from_listbase( + active_strip, &strip_source->strips, &strip_dest->strips); + if (found_in_meta != NULL) { + return found_in_meta; + } + } + + strip_dest = strip_dest->next; + } + + return NULL; +} + +/* Set adt_dest->actstrip to the strip with the same index as + * adt_source->actstrip. Note that this always sets `adt_dest->actstrip`; sets + * to NULL when `adt_source->actstrip` cannot be found. */ +static void update_active_strip(AnimData *adt_dest, + NlaTrack *track_dest, + const AnimData *adt_source, + const NlaTrack *track_source) +{ + BLI_assert(BLI_listbase_count(&track_source->strips) == BLI_listbase_count(&track_dest->strips)); + + NlaStrip *active_strip = find_active_strip_from_listbase( + adt_source->actstrip, &track_source->strips, &track_dest->strips); + adt_dest->actstrip = active_strip; +} + +/* Set adt_dest->act_track to the track with the same index as adt_source->act_track. */ +static void update_active_track(AnimData *adt_dest, const AnimData *adt_source) +{ + adt_dest->act_track = NULL; + adt_dest->actstrip = NULL; + if (adt_source->act_track == NULL && adt_source->actstrip == NULL) { + return; + } + + BLI_assert(BLI_listbase_count(&adt_source->nla_tracks) == + BLI_listbase_count(&adt_dest->nla_tracks)); + + NlaTrack *track_dest = adt_dest->nla_tracks.first; + LISTBASE_FOREACH (NlaTrack *, track_source, &adt_source->nla_tracks) { + if (track_source == adt_source->act_track) { + adt_dest->act_track = track_dest; + } + + /* Only search for the active strip if it hasn't been found yet. */ + if (adt_dest->actstrip == NULL && adt_source->actstrip != NULL) { + update_active_strip(adt_dest, track_dest, adt_source, track_source); + } + + track_dest = track_dest->next; + } + +#ifndef NDEBUG + { + const bool source_has_actstrip = adt_source->actstrip != NULL; + const bool dest_has_actstrip = adt_dest->actstrip != NULL; + BLI_assert_msg(source_has_actstrip == dest_has_actstrip, + "Active strip did not copy correctly"); + } +#endif +} + +void BKE_nla_tracks_copy_from_adt(Main *bmain, + AnimData *adt_dest, + const AnimData *adt_source, + const int flag) +{ + adt_dest->act_track = NULL; + adt_dest->actstrip = NULL; + + BKE_nla_tracks_copy(bmain, &adt_dest->nla_tracks, &adt_source->nla_tracks, flag); + update_active_track(adt_dest, adt_source); +} + +/* Adding ------------------------------------------- */ + +NlaTrack *BKE_nlatrack_add(AnimData *adt, NlaTrack *prev, const bool is_liboverride) +{ + NlaTrack *nlt; + + /* sanity checks */ + if (adt == NULL) { + return NULL; + } + + /* allocate new track */ + nlt = MEM_callocN(sizeof(NlaTrack), "NlaTrack"); + + /* set settings requiring the track to not be part of the stack yet */ + nlt->flag = NLATRACK_SELECTED | NLATRACK_OVERRIDELIBRARY_LOCAL; + nlt->index = BLI_listbase_count(&adt->nla_tracks); + + /* In liboverride case, we only add local tracks after all those coming from the linked data, + * so we need to find the first local track. */ + if (is_liboverride && prev != NULL && (prev->flag & NLATRACK_OVERRIDELIBRARY_LOCAL) == 0) { + NlaTrack *first_local = prev->next; + for (; first_local != NULL && (first_local->flag & NLATRACK_OVERRIDELIBRARY_LOCAL) == 0; + first_local = first_local->next) { + } + prev = first_local != NULL ? first_local->prev : NULL; + } + /* Add track to stack, and make it the active one. */ + if (prev != NULL) { + BLI_insertlinkafter(&adt->nla_tracks, prev, nlt); + } + else { + BLI_addtail(&adt->nla_tracks, nlt); + } + BKE_nlatrack_set_active(&adt->nla_tracks, nlt); + + /* must have unique name, but we need to seed this */ + strcpy(nlt->name, "NlaTrack"); + BLI_uniquename( + &adt->nla_tracks, nlt, DATA_("NlaTrack"), '.', offsetof(NlaTrack, name), sizeof(nlt->name)); + + /* return the new track */ + return nlt; +} + +NlaStrip *BKE_nlastrip_new(bAction *act) +{ + NlaStrip *strip; + + /* sanity checks */ + if (act == NULL) { + return NULL; + } + + /* allocate new strip */ + strip = MEM_callocN(sizeof(NlaStrip), "NlaStrip"); + + /* generic settings + * - selected flag to highlight this to the user + * - (XXX) disabled Auto-Blends, as this was often causing some unwanted effects + */ + strip->flag = NLASTRIP_FLAG_SELECT | NLASTRIP_FLAG_SYNC_LENGTH; + + /* Disable sync for actions with a manual frame range, since it only syncs to range anyway. */ + if (act->flag & ACT_FRAME_RANGE) { + strip->flag &= ~NLASTRIP_FLAG_SYNC_LENGTH; + } + + /* Enable cyclic time for known cyclic actions. */ + if (BKE_action_is_cyclic(act)) { + strip->flag |= NLASTRIP_FLAG_USR_TIME_CYCLIC; + } + + /* assign the action reference */ + strip->act = act; + id_us_plus(&act->id); + + /* determine initial range + * - strip length cannot be 0... ever... + */ + BKE_action_get_frame_range(strip->act, &strip->actstart, &strip->actend); + + strip->start = strip->actstart; + strip->end = IS_EQF(strip->actstart, strip->actend) ? (strip->actstart + 1.0f) : strip->actend; + + /* strip should be referenced as-is */ + strip->scale = 1.0f; + strip->repeat = 1.0f; + + /* return the new strip */ + return strip; +} + +NlaStrip *BKE_nlastack_add_strip(AnimData *adt, bAction *act, const bool is_liboverride) +{ + NlaStrip *strip; + NlaTrack *nlt; + + /* sanity checks */ + if (ELEM(NULL, adt, act)) { + return NULL; + } + + /* create a new NLA strip */ + strip = BKE_nlastrip_new(act); + if (strip == NULL) { + return NULL; + } + + /* firstly try adding strip to last track, but if that fails, add to a new track */ + if (BKE_nlatrack_add_strip(adt->nla_tracks.last, strip, is_liboverride) == 0) { + /* trying to add to the last track failed (no track or no space), + * so add a new track to the stack, and add to that... + */ + nlt = BKE_nlatrack_add(adt, NULL, is_liboverride); + BKE_nlatrack_add_strip(nlt, strip, is_liboverride); + BLI_strncpy(nlt->name, act->id.name + 2, sizeof(nlt->name)); + } + + /* automatically name it too */ + BKE_nlastrip_validate_name(adt, strip); + + /* returns the strip added */ + return strip; +} + +NlaStrip *BKE_nla_add_soundstrip(Main *bmain, Scene *scene, Speaker *speaker) +{ + NlaStrip *strip = MEM_callocN(sizeof(NlaStrip), "NlaSoundStrip"); + + /* if speaker has a sound, set the strip length to the length of the sound, + * otherwise default to length of 10 frames + */ +#ifdef WITH_AUDASPACE + if (speaker->sound) { + SoundInfo info; + if (BKE_sound_info_get(bmain, speaker->sound, &info)) { + strip->end = (float)ceil((double)info.length * FPS); + } + } + else +#endif + { + strip->end = 10.0f; + /* quiet compiler warnings */ + UNUSED_VARS(bmain, scene, speaker); + } + + /* general settings */ + strip->type = NLASTRIP_TYPE_SOUND; + + strip->flag = NLASTRIP_FLAG_SELECT; + strip->extendmode = NLASTRIP_EXTEND_NOTHING; /* nothing to extend... */ + + /* strip should be referenced as-is */ + strip->scale = 1.0f; + strip->repeat = 1.0f; + + /* return this strip */ + return strip; +} + +void BKE_nla_strip_foreach_id(NlaStrip *strip, LibraryForeachIDData *data) +{ + BKE_LIB_FOREACHID_PROCESS_IDSUPER(data, strip->act, IDWALK_CB_USER); + + LISTBASE_FOREACH (FCurve *, fcu, &strip->fcurves) { + BKE_LIB_FOREACHID_PROCESS_FUNCTION_CALL(data, BKE_fcurve_foreach_id(fcu, data)); + } + + LISTBASE_FOREACH (NlaStrip *, substrip, &strip->strips) { + BKE_LIB_FOREACHID_PROCESS_FUNCTION_CALL(data, BKE_nla_strip_foreach_id(substrip, data)); + } +} + +/* *************************************************** */ +/* NLA Evaluation <-> Editing Stuff */ + +/* Strip Mapping ------------------------------------- */ + +/* non clipped mapping for strip-time <-> global time (for Action-Clips) + * invert = convert action-strip time to global time + */ +static float nlastrip_get_frame_actionclip(NlaStrip *strip, float cframe, short mode) +{ + float actlength, scale; + // float repeat; // UNUSED + + /* get number of repeats */ + if (IS_EQF(strip->repeat, 0.0f)) { + strip->repeat = 1.0f; + } + // repeat = strip->repeat; /* UNUSED */ + + /* scaling */ + if (IS_EQF(strip->scale, 0.0f)) { + strip->scale = 1.0f; + } + + /* Scale must be positive - we've got a special flag for reversing. */ + scale = fabsf(strip->scale); + + /* length of referenced action */ + actlength = strip->actend - strip->actstart; + if (IS_EQF(actlength, 0.0f)) { + actlength = 1.0f; + } + + /* reversed = play strip backwards */ + if (strip->flag & NLASTRIP_FLAG_REVERSE) { + /* FIXME: this won't work right with Graph Editor? */ + if (mode == NLATIME_CONVERT_MAP) { + return strip->end - scale * (cframe - strip->actstart); + } + if (mode == NLATIME_CONVERT_UNMAP) { + return (strip->end + (strip->actstart * scale - cframe)) / scale; + } + /* if (mode == NLATIME_CONVERT_EVAL) */ + if (IS_EQF((float)cframe, strip->end) && IS_EQF(strip->repeat, floorf(strip->repeat))) { + /* This case prevents the motion snapping back to the first frame at the end of the strip + * by catching the case where repeats is a whole number, which means that the end of the + * strip could also be interpreted as the end of the start of a repeat. */ + return strip->actstart; + } + + /* - the 'fmod(..., actlength * scale)' is needed to get the repeats working + * - the '/ scale' is needed to ensure that scaling influences the timing within the repeat + */ + return strip->actend - fmodf(cframe - strip->start, actlength * scale) / scale; + } + + if (mode == NLATIME_CONVERT_MAP) { + return strip->start + scale * (cframe - strip->actstart); + } + if (mode == NLATIME_CONVERT_UNMAP) { + return strip->actstart + (cframe - strip->start) / scale; + } + /* if (mode == NLATIME_CONVERT_EVAL) */ + if (IS_EQF(cframe, strip->end) && IS_EQF(strip->repeat, floorf(strip->repeat))) { + /* This case prevents the motion snapping back to the first frame at the end of the strip + * by catching the case where repeats is a whole number, which means that the end of the + * strip could also be interpreted as the end of the start of a repeat. */ + return strip->actend; + } + + /* - the 'fmod(..., actlength * scale)' is needed to get the repeats working + * - the '/ scale' is needed to ensure that scaling influences the timing within the repeat + */ + return strip->actstart + fmodf(cframe - strip->start, actlength * scale) / scale; +} + +/* non clipped mapping for strip-time <-> global time (for Transitions) + * invert = convert action-strip time to global time + */ +static float nlastrip_get_frame_transition(NlaStrip *strip, float cframe, short mode) +{ + float length; + + /* length of strip */ + length = strip->end - strip->start; + + /* reversed = play strip backwards */ + if (strip->flag & NLASTRIP_FLAG_REVERSE) { + if (mode == NLATIME_CONVERT_MAP) { + return strip->end - (length * cframe); + } + + return (strip->end - cframe) / length; + } + + if (mode == NLATIME_CONVERT_MAP) { + return (length * cframe) + strip->start; + } + + return (cframe - strip->start) / length; +} + +float nlastrip_get_frame(NlaStrip *strip, float cframe, short mode) +{ + switch (strip->type) { + case NLASTRIP_TYPE_META: /* Meta - for now, does the same as transition + * (is really just an empty container). */ + case NLASTRIP_TYPE_TRANSITION: /* transition */ + return nlastrip_get_frame_transition(strip, cframe, mode); + + case NLASTRIP_TYPE_CLIP: /* action-clip (default) */ + default: + return nlastrip_get_frame_actionclip(strip, cframe, mode); + } +} + +float BKE_nla_tweakedit_remap(AnimData *adt, float cframe, short mode) +{ + NlaStrip *strip; + + /* Sanity checks: + * - Obviously we've got to have some starting data. + * - When not in tweak-mode, the active Action does not have any scaling applied :) + * - When in tweak-mode, if the no-mapping flag is set, do not map. + */ + if ((adt == NULL) || (adt->flag & ADT_NLA_EDIT_ON) == 0 || (adt->flag & ADT_NLA_EDIT_NOMAP)) { + return cframe; + } + + /* if the active-strip info has been stored already, access this, otherwise look this up + * and store for (very probable) future usage + */ + if (adt->act_track == NULL) { + if (adt->actstrip) { + adt->act_track = BKE_nlatrack_find_tweaked(adt); + } + else { + adt->act_track = BKE_nlatrack_find_active(&adt->nla_tracks); + } + } + if (adt->actstrip == NULL) { + adt->actstrip = BKE_nlastrip_find_active(adt->act_track); + } + strip = adt->actstrip; + + /* Sanity checks: + * - In rare cases, we may not be able to find this strip for some reason (internal error) + * - For now, if the user has defined a curve to control the time, this correction cannot be + * performed reliably. + */ + if ((strip == NULL) || (strip->flag & NLASTRIP_FLAG_USR_TIME)) { + return cframe; + } + + /* perform the correction now... */ + return nlastrip_get_frame(strip, cframe, mode); +} + +/* *************************************************** */ +/* NLA API */ + +/* List of Strips ------------------------------------ */ +/* (these functions are used for NLA-Tracks and also for nested/meta-strips) */ + +bool BKE_nlastrips_has_space(ListBase *strips, float start, float end) +{ + NlaStrip *strip; + + /* sanity checks */ + if ((strips == NULL) || IS_EQF(start, end)) { + return false; + } + if (start > end) { + puts("BKE_nlastrips_has_space() error... start and end arguments swapped"); + SWAP(float, start, end); + } + + /* loop over NLA strips checking for any overlaps with this area... */ + for (strip = strips->first; strip; strip = strip->next) { + /* if start frame of strip is past the target end-frame, that means that + * we've gone past the window we need to check for, so things are fine + */ + if (strip->start >= end) { + return true; + } + + /* if the end of the strip is greater than either of the boundaries, the range + * must fall within the extents of the strip + */ + if ((strip->end > start) || (strip->end > end)) { + return false; + } + } + + /* if we are still here, we haven't encountered any overlapping strips */ + return true; +} + +void BKE_nlastrips_sort_strips(ListBase *strips) +{ + ListBase tmp = {NULL, NULL}; + NlaStrip *strip, *sstrip, *stripn; + + /* sanity checks */ + if (ELEM(NULL, strips, strips->first)) { + return; + } + + /* we simply perform insertion sort on this list, since it is assumed that per track, + * there are only likely to be at most 5-10 strips + */ + for (strip = strips->first; strip; strip = stripn) { + short not_added = 1; + + stripn = strip->next; + + /* remove this strip from the list, and add it to the new list, searching from the end of + * the list, assuming that the lists are in order + */ + BLI_remlink(strips, strip); + + for (sstrip = tmp.last; sstrip; sstrip = sstrip->prev) { + /* check if add after */ + if (sstrip->end <= strip->start) { + BLI_insertlinkafter(&tmp, sstrip, strip); + not_added = 0; + break; + } + } + + /* add before first? */ + if (not_added) { + BLI_addhead(&tmp, strip); + } + } + + /* reassign the start and end points of the strips */ + strips->first = tmp.first; + strips->last = tmp.last; +} + +void BKE_nlastrips_add_strip_unsafe(ListBase *strips, NlaStrip *strip) +{ + NlaStrip *ns; + bool not_added = true; + + /* sanity checks */ + BLI_assert(!ELEM(NULL, strips, strip)); + + /* find the right place to add the strip to the nominated track */ + for (ns = strips->first; ns; ns = ns->next) { + /* if current strip occurs after the new strip, add it before */ + if (ns->start >= strip->end) { + BLI_insertlinkbefore(strips, ns, strip); + not_added = 0; + break; + } + } + if (not_added) { + /* just add to the end of the list of the strips then... */ + BLI_addtail(strips, strip); + } +} + +bool BKE_nlastrips_add_strip(ListBase *strips, NlaStrip *strip) +{ + if (ELEM(NULL, strips, strip)) { + return false; + } + + if (!BKE_nlastrips_has_space(strips, strip->start, strip->end)) { + return false; + } + + BKE_nlastrips_add_strip_unsafe(strips, strip); + return true; +} + +/* Meta-Strips ------------------------------------ */ + +void BKE_nlastrips_make_metas(ListBase *strips, bool is_temp) +{ + NlaStrip *mstrip = NULL; + NlaStrip *strip, *stripn; + + /* sanity checks */ + if (ELEM(NULL, strips, strips->first)) { + return; + } + + /* group all continuous chains of selected strips into meta-strips */ + for (strip = strips->first; strip; strip = stripn) { + stripn = strip->next; + + if (strip->flag & NLASTRIP_FLAG_SELECT) { + /* if there is an existing meta-strip, add this strip to it, otherwise, create a new one */ + if (mstrip == NULL) { + /* add a new meta-strip, and add it before the current strip that it will replace... */ + mstrip = MEM_callocN(sizeof(NlaStrip), "Meta-NlaStrip"); + mstrip->type = NLASTRIP_TYPE_META; + BLI_insertlinkbefore(strips, strip, mstrip); + + /* set flags */ + mstrip->flag = NLASTRIP_FLAG_SELECT; + + /* set temp flag if appropriate (i.e. for transform-type editing) */ + if (is_temp) { + mstrip->flag |= NLASTRIP_FLAG_TEMP_META; + } + + /* set default repeat/scale values to prevent warnings */ + mstrip->repeat = mstrip->scale = 1.0f; + + /* make its start frame be set to the start frame of the current strip */ + mstrip->start = strip->start; + } + + /* remove the selected strips from the track, and add to the meta */ + BLI_remlink(strips, strip); + BLI_addtail(&mstrip->strips, strip); + + /* expand the meta's dimensions to include the newly added strip- i.e. its last frame */ + mstrip->end = strip->end; + } + else { + /* current strip wasn't selected, so the end of 'island' of selected strips has been reached, + * so stop adding strips to the current meta + */ + mstrip = NULL; + } + } +} + +void BKE_nlastrips_clear_metastrip(ListBase *strips, NlaStrip *strip) +{ + NlaStrip *cs, *csn; + + /* sanity check */ + if (ELEM(NULL, strips, strip)) { + return; + } + + /* move each one of the meta-strip's children before the meta-strip + * in the list of strips after unlinking them from the meta-strip + */ + for (cs = strip->strips.first; cs; cs = csn) { + csn = cs->next; + BLI_remlink(&strip->strips, cs); + BLI_insertlinkbefore(strips, strip, cs); + } + + /* free the meta-strip now */ + BKE_nlastrip_remove_and_free(strips, strip, true); +} + +void BKE_nlastrips_clear_metas(ListBase *strips, bool only_sel, bool only_temp) +{ + NlaStrip *strip, *stripn; + + /* sanity checks */ + if (ELEM(NULL, strips, strips->first)) { + return; + } + + /* remove meta-strips fitting the criteria of the arguments */ + for (strip = strips->first; strip; strip = stripn) { + stripn = strip->next; + + /* check if strip is a meta-strip */ + if (strip->type == NLASTRIP_TYPE_META) { + /* if check if selection and 'temporary-only' considerations are met */ + if ((!only_sel) || (strip->flag & NLASTRIP_FLAG_SELECT)) { + if ((!only_temp) || (strip->flag & NLASTRIP_FLAG_TEMP_META)) { + BKE_nlastrips_clear_metastrip(strips, strip); + } + } + } + } +} + +bool BKE_nlameta_add_strip(NlaStrip *mstrip, NlaStrip *strip) +{ + /* sanity checks */ + if (ELEM(NULL, mstrip, strip)) { + return false; + } + + /* firstly, check if the meta-strip has space for this */ + if (BKE_nlastrips_has_space(&mstrip->strips, strip->start, strip->end) == 0) { + return false; + } + + /* check if this would need to be added to the ends of the meta, + * and subsequently, if the neighboring strips allow us enough room + */ + if (strip->start < mstrip->start) { + /* check if strip to the left (if it exists) ends before the + * start of the strip we're trying to add + */ + if ((mstrip->prev == NULL) || (mstrip->prev->end <= strip->start)) { + /* add strip to start of meta's list, and expand dimensions */ + BLI_addhead(&mstrip->strips, strip); + mstrip->start = strip->start; + + return true; + } + /* failed... no room before */ + return false; + } + if (strip->end > mstrip->end) { + /* check if strip to the right (if it exists) starts before the + * end of the strip we're trying to add + */ + if ((mstrip->next == NULL) || (mstrip->next->start >= strip->end)) { + /* add strip to end of meta's list, and expand dimensions */ + BLI_addtail(&mstrip->strips, strip); + mstrip->end = strip->end; + + return true; + } + /* failed... no room after */ + return false; + } + + /* just try to add to the meta-strip (no dimension changes needed) */ + return BKE_nlastrips_add_strip(&mstrip->strips, strip); +} + +void BKE_nlameta_flush_transforms(NlaStrip *mstrip) +{ + NlaStrip *strip; + float oStart, oEnd, offset; + float oLen, nLen; + short scaleChanged = 0; + + /* sanity checks + * - strip must exist + * - strip must be a meta-strip with some contents + */ + if (ELEM(NULL, mstrip, mstrip->strips.first)) { + return; + } + if (mstrip->type != NLASTRIP_TYPE_META) { + return; + } + + /* get the original start/end points, and calculate the start-frame offset + * - these are simply the start/end frames of the child strips, + * since we assume they weren't transformed yet + */ + oStart = ((NlaStrip *)mstrip->strips.first)->start; + oEnd = ((NlaStrip *)mstrip->strips.last)->end; + offset = mstrip->start - oStart; + + /* check if scale changed */ + oLen = oEnd - oStart; + nLen = mstrip->end - mstrip->start; + scaleChanged = !IS_EQF(oLen, nLen); + + /* optimization: + * don't flush if nothing changed yet + * TODO: maybe we need a flag to say always flush? + */ + if (IS_EQF(oStart, mstrip->start) && IS_EQF(oEnd, mstrip->end) && !scaleChanged) { + return; + } + + /* for each child-strip, calculate new start/end points based on this new info */ + for (strip = mstrip->strips.first; strip; strip = strip->next) { + if (scaleChanged) { + float p1, p2; + + /* compute positions of endpoints relative to old extents of strip */ + p1 = (strip->start - oStart) / oLen; + p2 = (strip->end - oStart) / oLen; + + /* Apply new strip endpoints using the proportions, + * then wait for second pass to flush scale properly. */ + strip->start = (p1 * nLen) + mstrip->start; + strip->end = (p2 * nLen) + mstrip->start; + + // Recompute the playback scale, given the new start & end frame of the strip. + const double action_len = strip->actend - strip->actstart; + const double repeated_len = action_len * strip->repeat; + const double strip_len = strip->end - strip->start; + strip->scale = strip_len / repeated_len; + } + else { + /* just apply the changes in offset to both ends of the strip */ + strip->start += offset; + strip->end += offset; + } + } + + /* apply a second pass over child strips, to finish up unfinished business */ + for (strip = mstrip->strips.first; strip; strip = strip->next) { + /* only if scale changed, need to perform RNA updates */ + if (scaleChanged) { + PointerRNA ptr; + + /* use RNA updates to compute scale properly */ + RNA_pointer_create(NULL, &RNA_NlaStrip, strip, &ptr); + + RNA_float_set(&ptr, "frame_start", strip->start); + RNA_float_set(&ptr, "frame_end", strip->end); + } + + /* finally, make sure the strip's children (if it is a meta-itself), get updated */ + BKE_nlameta_flush_transforms(strip); + } +} + +/* NLA-Tracks ---------------------------------------- */ + +NlaTrack *BKE_nlatrack_find_active(ListBase *tracks) +{ + NlaTrack *nlt; + + /* sanity check */ + if (ELEM(NULL, tracks, tracks->first)) { + return NULL; + } + + /* try to find the first active track */ + for (nlt = tracks->first; nlt; nlt = nlt->next) { + if (nlt->flag & NLATRACK_ACTIVE) { + return nlt; + } + } + + /* none found */ + return NULL; +} + +NlaTrack *BKE_nlatrack_find_tweaked(AnimData *adt) +{ + NlaTrack *nlt; + + /* sanity check */ + if (adt == NULL) { + return NULL; + } + + /* Since the track itself gets disabled, we want the first disabled... */ + for (nlt = adt->nla_tracks.first; nlt; nlt = nlt->next) { + if (nlt->flag & (NLATRACK_ACTIVE | NLATRACK_DISABLED)) { + /* For good measure, make sure that strip actually exists there */ + if (BLI_findindex(&nlt->strips, adt->actstrip) != -1) { + return nlt; + } + if (G.debug & G_DEBUG) { + printf("%s: Active strip (%p, %s) not in NLA track found (%p, %s)\n", + __func__, + adt->actstrip, + (adt->actstrip) ? adt->actstrip->name : "", + nlt, + nlt->name); + } + } + } + + /* Not found! */ + return NULL; +} + +void BKE_nlatrack_solo_toggle(AnimData *adt, NlaTrack *nlt) +{ + NlaTrack *nt; + + /* sanity check */ + if (ELEM(NULL, adt, adt->nla_tracks.first)) { + return; + } + + /* firstly, make sure 'solo' flag for all tracks is disabled */ + for (nt = adt->nla_tracks.first; nt; nt = nt->next) { + if (nt != nlt) { + nt->flag &= ~NLATRACK_SOLO; + } + } + + /* now, enable 'solo' for the given track if appropriate */ + if (nlt) { + /* toggle solo status */ + nlt->flag ^= NLATRACK_SOLO; + + /* set or clear solo-status on AnimData */ + if (nlt->flag & NLATRACK_SOLO) { + adt->flag |= ADT_NLA_SOLO_TRACK; + } + else { + adt->flag &= ~ADT_NLA_SOLO_TRACK; + } + } + else { + adt->flag &= ~ADT_NLA_SOLO_TRACK; + } +} + +void BKE_nlatrack_set_active(ListBase *tracks, NlaTrack *nlt_a) +{ + NlaTrack *nlt; + + /* sanity check */ + if (ELEM(NULL, tracks, tracks->first)) { + return; + } + + /* deactivate all the rest */ + for (nlt = tracks->first; nlt; nlt = nlt->next) { + nlt->flag &= ~NLATRACK_ACTIVE; + } + + /* set the given one as the active one */ + if (nlt_a) { + nlt_a->flag |= NLATRACK_ACTIVE; + } +} + +bool BKE_nlatrack_has_space(NlaTrack *nlt, float start, float end) +{ + /* sanity checks + * - track must exist + * - track must be editable + * - bounds cannot be equal (0-length is nasty) + */ + if ((nlt == NULL) || (nlt->flag & NLATRACK_PROTECTED) || IS_EQF(start, end)) { + return false; + } + + if (start > end) { + puts("BKE_nlatrack_has_space() error... start and end arguments swapped"); + SWAP(float, start, end); + } + + /* check if there's any space left in the track for a strip of the given length */ + return BKE_nlastrips_has_space(&nlt->strips, start, end); +} + +void BKE_nlatrack_sort_strips(NlaTrack *nlt) +{ + /* sanity checks */ + if (ELEM(NULL, nlt, nlt->strips.first)) { + return; + } + + /* sort the strips with a more generic function */ + BKE_nlastrips_sort_strips(&nlt->strips); +} + +bool BKE_nlatrack_add_strip(NlaTrack *nlt, NlaStrip *strip, const bool is_liboverride) +{ + /* sanity checks */ + if (ELEM(NULL, nlt, strip)) { + return false; + } + + /* Do not allow adding strips if this track is locked, or not a local one in liboverride case. */ + if (nlt->flag & NLATRACK_PROTECTED || + (is_liboverride && (nlt->flag & NLATRACK_OVERRIDELIBRARY_LOCAL) == 0)) { + return false; + } + + /* try to add the strip to the track using a more generic function */ + return BKE_nlastrips_add_strip(&nlt->strips, strip); +} + +void BKE_nlatrack_remove_strip(NlaTrack *track, NlaStrip *strip) +{ + BLI_assert(track); + BKE_nlastrip_remove(&track->strips, strip); +} + +bool BKE_nlatrack_get_bounds(NlaTrack *nlt, float bounds[2]) +{ + NlaStrip *strip; + + /* initialize bounds */ + if (bounds) { + bounds[0] = bounds[1] = 0.0f; + } + else { + return false; + } + + /* sanity checks */ + if (ELEM(NULL, nlt, nlt->strips.first)) { + return false; + } + + /* lower bound is first strip's start frame */ + strip = nlt->strips.first; + bounds[0] = strip->start; + + /* upper bound is last strip's end frame */ + strip = nlt->strips.last; + bounds[1] = strip->end; + + /* done */ + return true; +} + +bool BKE_nlatrack_is_nonlocal_in_liboverride(const ID *id, const NlaTrack *nlt) +{ + return (ID_IS_OVERRIDE_LIBRARY(id) && + (nlt == NULL || (nlt->flag & NLATRACK_OVERRIDELIBRARY_LOCAL) == 0)); +} + +/* NLA Strips -------------------------------------- */ + +static NlaStrip *nlastrip_find_active(ListBase /* NlaStrip */ *strips) +{ + LISTBASE_FOREACH (NlaStrip *, strip, strips) { + if (strip->flag & NLASTRIP_FLAG_ACTIVE) { + return strip; + } + + if (strip->type != NLASTRIP_TYPE_META) { + continue; + } + + NlaStrip *inner_active = nlastrip_find_active(&strip->strips); + if (inner_active != NULL) { + return inner_active; + } + } + + return NULL; +} + +float BKE_nlastrip_compute_frame_from_previous_strip(NlaStrip *strip) +{ + float limit_prev = MINAFRAMEF; + + /* Find the previous end frame, with a special case if the previous strip was a transition : */ + if (strip->prev) { + if (strip->prev->type == NLASTRIP_TYPE_TRANSITION) { + limit_prev = strip->prev->start + NLASTRIP_MIN_LEN_THRESH; + } + else { + limit_prev = strip->prev->end; + } + } + + return limit_prev; +} + +float BKE_nlastrip_compute_frame_to_next_strip(NlaStrip *strip) +{ + float limit_next = MAXFRAMEF; + + /* Find the next begin frame, with a special case if the next strip's a transition : */ + if (strip->next) { + if (strip->next->type == NLASTRIP_TYPE_TRANSITION) { + limit_next = strip->next->end - NLASTRIP_MIN_LEN_THRESH; + } + else { + limit_next = strip->next->start; + } + } + + return limit_next; +} + +NlaStrip *BKE_nlastrip_next_in_track(struct NlaStrip *strip, bool skip_transitions) +{ + NlaStrip *next = strip->next; + while (next != NULL) { + if (skip_transitions && (next->type & NLASTRIP_TYPE_TRANSITION)) { + next = next->next; + } + else { + return next; + } + } + return NULL; +} + +NlaStrip *BKE_nlastrip_prev_in_track(struct NlaStrip *strip, bool skip_transitions) +{ + NlaStrip *prev = strip->prev; + while (prev != NULL) { + if (skip_transitions && (prev->type & NLASTRIP_TYPE_TRANSITION)) { + prev = prev->prev; + } + else { + return prev; + } + } + return NULL; +} + +NlaStrip *BKE_nlastrip_find_active(NlaTrack *nlt) +{ + if (nlt == NULL) { + return NULL; + } + + return nlastrip_find_active(&nlt->strips); +} + +void BKE_nlastrip_remove(ListBase *strips, NlaStrip *strip) +{ + BLI_assert(strips); + BLI_remlink(strips, strip); +} + +void BKE_nlastrip_remove_and_free(ListBase *strips, NlaStrip *strip, const bool do_id_user) +{ + BKE_nlastrip_remove(strips, strip); + BKE_nlastrip_free(strip, do_id_user); +} + +void BKE_nlastrip_set_active(AnimData *adt, NlaStrip *strip) +{ + NlaTrack *nlt; + NlaStrip *nls; + + /* sanity checks */ + if (adt == NULL) { + return; + } + + /* Loop over tracks, deactivating. */ + for (nlt = adt->nla_tracks.first; nlt; nlt = nlt->next) { + for (nls = nlt->strips.first; nls; nls = nls->next) { + if (nls != strip) { + nls->flag &= ~NLASTRIP_FLAG_ACTIVE; + } + else { + nls->flag |= NLASTRIP_FLAG_ACTIVE; + } + } + } +} + +bool BKE_nlastrip_within_bounds(NlaStrip *strip, float min, float max) +{ + const float stripLen = (strip) ? strip->end - strip->start : 0.0f; + const float boundsLen = fabsf(max - min); + + /* sanity checks */ + if ((strip == NULL) || IS_EQF(stripLen, 0.0f) || IS_EQF(boundsLen, 0.0f)) { + return false; + } + + /* only ok if at least part of the strip is within the bounding window + * - first 2 cases cover when the strip length is less than the bounding area + * - second 2 cases cover when the strip length is greater than the bounding area + */ + if ((stripLen < boundsLen) && + !(IN_RANGE(strip->start, min, max) || IN_RANGE(strip->end, min, max))) { + return false; + } + if ((stripLen > boundsLen) && + !(IN_RANGE(min, strip->start, strip->end) || IN_RANGE(max, strip->start, strip->end))) { + return false; + } + + /* should be ok! */ + return true; +} + +float BKE_nlastrip_distance_to_frame(const NlaStrip *strip, const float timeline_frame) +{ + if (timeline_frame < strip->start) { + return strip->start - timeline_frame; + } + if (strip->end < timeline_frame) { + return timeline_frame - strip->end; + } + return 0.0f; +} + +/* Ensure that strip doesn't overlap those around it after resizing + * by offsetting those which follow. */ +static void nlastrip_fix_resize_overlaps(NlaStrip *strip) +{ + /* next strips - do this first, since we're often just getting longer */ + if (strip->next) { + NlaStrip *nls = strip->next; + float offset = 0.0f; + + if (nls->type == NLASTRIP_TYPE_TRANSITION) { + /* transition strips should grow/shrink to accommodate the resized strip, + * but if the strip's bounds now exceed the transition, we're forced to + * offset everything to maintain the balance + */ + if (strip->end <= nls->start) { + /* grow the transition to fill the void */ + nls->start = strip->end; + } + else if (strip->end < nls->end) { + /* shrink the transition to give the strip room */ + nls->start = strip->end; + } + else { + /* Shrink transition down to 1 frame long (so that it can still be found), + * then offset everything else by the remaining deficit to give the strip room. */ + nls->start = nls->end - 1.0f; + + /* XXX: review whether preventing fractional values is good here... */ + offset = ceilf(strip->end - nls->start); + + /* apply necessary offset to ensure that the strip has enough space */ + for (; nls; nls = nls->next) { + nls->start += offset; + nls->end += offset; + } + } + } + else if (strip->end > nls->start) { + /* NOTE: need to ensure we don't have a fractional frame offset, even if that leaves a gap, + * otherwise it will be very hard to get rid of later + */ + offset = ceilf(strip->end - nls->start); + + /* apply to times of all strips in this direction */ + for (; nls; nls = nls->next) { + nls->start += offset; + nls->end += offset; + } + } + } + + /* previous strips - same routine as before */ + /* NOTE: when strip bounds are recalculated, this is not considered! */ + if (strip->prev) { + NlaStrip *nls = strip->prev; + float offset = 0.0f; + + if (nls->type == NLASTRIP_TYPE_TRANSITION) { + /* transition strips should grow/shrink to accommodate the resized strip, + * but if the strip's bounds now exceed the transition, we're forced to + * offset everything to maintain the balance + */ + if (strip->start >= nls->end) { + /* grow the transition to fill the void */ + nls->end = strip->start; + } + else if (strip->start > nls->start) { + /* shrink the transition to give the strip room */ + nls->end = strip->start; + } + else { + /* Shrink transition down to 1 frame long (so that it can still be found), + * then offset everything else by the remaining deficit to give the strip room. */ + nls->end = nls->start + 1.0f; + + /* XXX: review whether preventing fractional values is good here... */ + offset = ceilf(nls->end - strip->start); + + /* apply necessary offset to ensure that the strip has enough space */ + for (; nls; nls = nls->prev) { + nls->start -= offset; + nls->end -= offset; + } + } + } + else if (strip->start < nls->end) { + /* NOTE: need to ensure we don't have a fractional frame offset, even if that leaves a gap, + * otherwise it will be very hard to get rid of later + */ + offset = ceilf(nls->end - strip->start); + + /* apply to times of all strips in this direction */ + for (; nls; nls = nls->prev) { + nls->start -= offset; + nls->end -= offset; + } + } + } +} + +void BKE_nlastrip_recalculate_bounds_sync_action(NlaStrip *strip) +{ + float prev_actstart; + + if (strip == NULL || strip->type != NLASTRIP_TYPE_CLIP) { + return; + } + + prev_actstart = strip->actstart; + + BKE_action_get_frame_range(strip->act, &strip->actstart, &strip->actend); + + /* Set start such that key's do not visually move, to preserve the overall animation result. */ + strip->start += (strip->actstart - prev_actstart) * strip->scale; + + BKE_nlastrip_recalculate_bounds(strip); +} +void BKE_nlastrip_recalculate_bounds(NlaStrip *strip) +{ + float actlen, mapping; + + /* sanity checks + * - must have a strip + * - can only be done for action clips + */ + if ((strip == NULL) || (strip->type != NLASTRIP_TYPE_CLIP)) { + return; + } + + /* calculate new length factors */ + actlen = strip->actend - strip->actstart; + if (IS_EQF(actlen, 0.0f)) { + actlen = 1.0f; + } + + mapping = strip->scale * strip->repeat; + + /* adjust endpoint of strip in response to this */ + if (IS_EQF(mapping, 0.0f) == 0) { + strip->end = (actlen * mapping) + strip->start; + } + + /* make sure we don't overlap our neighbors */ + nlastrip_fix_resize_overlaps(strip); +} + +void BKE_nlastrip_recalculate_blend(NlaStrip *strip) +{ + + /* check if values need to be re-calculated. */ + if (strip->blendin == 0 && strip->blendout == 0) { + return; + } + + const double strip_len = strip->end - strip->start; + double blend_in = strip->blendin; + double blend_out = strip->blendout; + + double blend_in_max = strip_len - blend_out; + + CLAMP_MIN(blend_in_max, 0); + + /* blend-out is limited to the length of the strip. */ + CLAMP(blend_in, 0, blend_in_max); + CLAMP(blend_out, 0, strip_len - blend_in); + + strip->blendin = blend_in; + strip->blendout = blend_out; +} + +/* Animated Strips ------------------------------------------- */ + +bool BKE_nlatrack_has_animated_strips(NlaTrack *nlt) +{ + NlaStrip *strip; + + /* sanity checks */ + if (ELEM(NULL, nlt, nlt->strips.first)) { + return false; + } + + /* check each strip for F-Curves only (don't care about whether the flags are set) */ + for (strip = nlt->strips.first; strip; strip = strip->next) { + if (strip->fcurves.first) { + return true; + } + } + + /* none found */ + return false; +} + +bool BKE_nlatracks_have_animated_strips(ListBase *tracks) +{ + NlaTrack *nlt; + + /* sanity checks */ + if (ELEM(NULL, tracks, tracks->first)) { + return false; + } + + /* check each track, stopping on the first hit */ + for (nlt = tracks->first; nlt; nlt = nlt->next) { + if (BKE_nlatrack_has_animated_strips(nlt)) { + return true; + } + } + + /* none found */ + return false; +} + +void BKE_nlastrip_validate_fcurves(NlaStrip *strip) +{ + FCurve *fcu; + + /* sanity checks */ + if (strip == NULL) { + return; + } + + /* if controlling influence... */ + if (strip->flag & NLASTRIP_FLAG_USR_INFLUENCE) { + /* try to get F-Curve */ + fcu = BKE_fcurve_find(&strip->fcurves, "influence", 0); + + /* add one if not found */ + if (fcu == NULL) { + /* make new F-Curve */ + fcu = BKE_fcurve_create(); + BLI_addtail(&strip->fcurves, fcu); + + /* set default flags */ + fcu->flag = (FCURVE_VISIBLE | FCURVE_SELECTED); + fcu->auto_smoothing = U.auto_smoothing_new; + + /* store path - make copy, and store that */ + fcu->rna_path = BLI_strdupn("influence", 9); + + /* insert keyframe to ensure current value stays on first refresh */ + fcu->bezt = MEM_callocN(sizeof(BezTriple), "nlastrip influence bezt"); + fcu->totvert = 1; + + fcu->bezt->vec[1][0] = strip->start; + fcu->bezt->vec[1][1] = strip->influence; + + /* Respect User Preferences for default interpolation and handles. */ + fcu->bezt->h1 = fcu->bezt->h2 = U.keyhandles_new; + fcu->bezt->ipo = U.ipo_new; + } + } + + /* if controlling time... */ + if (strip->flag & NLASTRIP_FLAG_USR_TIME) { + /* try to get F-Curve */ + fcu = BKE_fcurve_find(&strip->fcurves, "strip_time", 0); + + /* add one if not found */ + if (fcu == NULL) { + /* make new F-Curve */ + fcu = BKE_fcurve_create(); + BLI_addtail(&strip->fcurves, fcu); + + /* set default flags */ + fcu->flag = (FCURVE_VISIBLE | FCURVE_SELECTED); + fcu->auto_smoothing = U.auto_smoothing_new; + + /* store path - make copy, and store that */ + fcu->rna_path = BLI_strdupn("strip_time", 10); + + /* TODO: insert a few keyframes to ensure default behavior? */ + } + } +} + +bool BKE_nlastrip_has_curves_for_property(const PointerRNA *ptr, const PropertyRNA *prop) +{ + /* sanity checks */ + if (ELEM(NULL, ptr, prop)) { + return false; + } + + /* 1) Must be NLA strip */ + if (ptr->type == &RNA_NlaStrip) { + /* 2) Must be one of the predefined properties */ + static PropertyRNA *prop_influence = NULL; + static PropertyRNA *prop_time = NULL; + static bool needs_init = true; + + /* Init the properties on first use */ + if (needs_init) { + prop_influence = RNA_struct_type_find_property(&RNA_NlaStrip, "influence"); + prop_time = RNA_struct_type_find_property(&RNA_NlaStrip, "strip_time"); + + needs_init = false; + } + + /* Check if match */ + if (ELEM(prop, prop_influence, prop_time)) { + return true; + } + } + + /* No criteria met */ + return false; +} + +/* Sanity Validation ------------------------------------ */ + +static bool nla_editbone_name_check(void *arg, const char *name) +{ + return BLI_ghash_haskey((GHash *)arg, (const void *)name); +} + +void BKE_nlastrip_validate_name(AnimData *adt, NlaStrip *strip) +{ + GHash *gh; + NlaStrip *tstrip; + NlaTrack *nlt; + + /* sanity checks */ + if (ELEM(NULL, adt, strip)) { + return; + } + + /* give strip a default name if none already */ + if (strip->name[0] == 0) { + switch (strip->type) { + case NLASTRIP_TYPE_CLIP: /* act-clip */ + BLI_strncpy(strip->name, + (strip->act) ? (strip->act->id.name + 2) : (""), + sizeof(strip->name)); + break; + case NLASTRIP_TYPE_TRANSITION: /* transition */ + BLI_strncpy(strip->name, "Transition", sizeof(strip->name)); + break; + case NLASTRIP_TYPE_META: /* meta */ + BLI_strncpy(strip->name, "Meta", sizeof(strip->name)); + break; + default: + BLI_strncpy(strip->name, "NLA Strip", sizeof(strip->name)); + break; + } + } + + /* build a hash-table of all the strips in the tracks + * - this is easier than iterating over all the tracks+strips hierarchy every time + * (and probably faster) + */ + gh = BLI_ghash_str_new("nlastrip_validate_name gh"); + + for (nlt = adt->nla_tracks.first; nlt; nlt = nlt->next) { + for (tstrip = nlt->strips.first; tstrip; tstrip = tstrip->next) { + /* don't add the strip of interest */ + if (tstrip == strip) { + continue; + } + + /* Use the name of the strip as the key, and the strip as the value, + * since we're mostly interested in the keys. */ + BLI_ghash_insert(gh, tstrip->name, tstrip); + } + } + + /* If the hash-table has a match for this name, try other names... + * - In an extreme case, it might not be able to find a name, + * but then everything else in Blender would fail too :). + */ + BLI_uniquename_cb(nla_editbone_name_check, + (void *)gh, + DATA_("NlaStrip"), + '.', + strip->name, + sizeof(strip->name)); + + /* free the hash... */ + BLI_ghash_free(gh, NULL, NULL); +} + +/* ---- */ + +/* Get strips which overlap the given one at the start/end of its range + * - strip: strip that we're finding overlaps for + * - track: nla-track that the overlapping strips should be found from + * - start, end: frames for the offending endpoints + */ +static void nlastrip_get_endpoint_overlaps(NlaStrip *strip, + NlaTrack *track, + float **start, + float **end) +{ + NlaStrip *nls; + + /* find strips that overlap over the start/end of the given strip, + * but which don't cover the entire length + */ + /* TODO: this scheme could get quite slow for doing this on many strips... */ + for (nls = track->strips.first; nls; nls = nls->next) { + /* Check if strip overlaps (extends over or exactly on) + * the entire range of the strip we're validating. */ + if ((nls->start <= strip->start) && (nls->end >= strip->end)) { + *start = NULL; + *end = NULL; + return; + } + + /* check if strip doesn't even occur anywhere near... */ + if (nls->end < strip->start) { + continue; /* skip checking this strip... not worthy of mention */ + } + if (nls->start > strip->end) { + return; /* the range we're after has already passed */ + } + + /* if this strip is not part of an island of continuous strips, it can be used + * - this check needs to be done for each end of the strip we try and use... + */ + if ((nls->next == NULL) || IS_EQF(nls->next->start, nls->end) == 0) { + if ((nls->end > strip->start) && (nls->end < strip->end)) { + *start = &nls->end; + } + } + if ((nls->prev == NULL) || IS_EQF(nls->prev->end, nls->start) == 0) { + if ((nls->start < strip->end) && (nls->start > strip->start)) { + *end = &nls->start; + } + } + } +} + +/* Determine auto-blending for the given strip */ +static void BKE_nlastrip_validate_autoblends(NlaTrack *nlt, NlaStrip *nls) +{ + float *ps = NULL, *pe = NULL; + float *ns = NULL, *ne = NULL; + + /* sanity checks */ + if (ELEM(NULL, nls, nlt)) { + return; + } + if ((nlt->prev == NULL) && (nlt->next == NULL)) { + return; + } + if ((nls->flag & NLASTRIP_FLAG_AUTO_BLENDS) == 0) { + return; + } + + /* get test ranges */ + if (nlt->prev) { + nlastrip_get_endpoint_overlaps(nls, nlt->prev, &ps, &pe); + } + if (nlt->next) { + nlastrip_get_endpoint_overlaps(nls, nlt->next, &ns, &ne); + } + + /* set overlaps for this strip + * - don't use the values obtained though if the end in question + * is directly followed/preceded by another strip, forming an + * 'island' of continuous strips + */ + if ((ps || ns) && ((nls->prev == NULL) || IS_EQF(nls->prev->end, nls->start) == 0)) { + /* start overlaps - pick the largest overlap */ + if (((ps && ns) && (*ps > *ns)) || (ps)) { + nls->blendin = *ps - nls->start; + } + else { + nls->blendin = *ns - nls->start; + } + } + else { /* no overlap allowed/needed */ + nls->blendin = 0.0f; + } + + if ((pe || ne) && ((nls->next == NULL) || IS_EQF(nls->next->start, nls->end) == 0)) { + /* end overlaps - pick the largest overlap */ + if (((pe && ne) && (*pe > *ne)) || (pe)) { + nls->blendout = nls->end - *pe; + } + else { + nls->blendout = nls->end - *ne; + } + } + else { /* no overlap allowed/needed */ + nls->blendout = 0.0f; + } +} + +void BKE_nla_validate_state(AnimData *adt) +{ + NlaStrip *strip = NULL; + NlaTrack *nlt; + + /* sanity checks */ + if (ELEM(NULL, adt, adt->nla_tracks.first)) { + return; + } + + /* Adjust blending values for auto-blending, + * and also do an initial pass to find the earliest strip. */ + for (nlt = adt->nla_tracks.first; nlt; nlt = nlt->next) { + for (strip = nlt->strips.first; strip; strip = strip->next) { + /* auto-blending first */ + BKE_nlastrip_validate_autoblends(nlt, strip); + BKE_nlastrip_recalculate_blend(strip); + } + } +} + +/* Action Stashing -------------------------------------- */ + +/* name of stashed tracks - the translation stuff is included here to save extra work */ +#define STASH_TRACK_NAME DATA_("[Action Stash]") + +bool BKE_nla_action_is_stashed(AnimData *adt, bAction *act) +{ + NlaTrack *nlt; + NlaStrip *strip; + + for (nlt = adt->nla_tracks.first; nlt; nlt = nlt->next) { + if (strstr(nlt->name, STASH_TRACK_NAME)) { + for (strip = nlt->strips.first; strip; strip = strip->next) { + if (strip->act == act) { + return true; + } + } + } + } + + return false; +} + +bool BKE_nla_action_stash(AnimData *adt, const bool is_liboverride) +{ + NlaTrack *prev_track = NULL; + NlaTrack *nlt; + NlaStrip *strip; + + /* sanity check */ + if (ELEM(NULL, adt, adt->action)) { + CLOG_ERROR(&LOG, "Invalid argument - %p %p", adt, adt->action); + return false; + } + + /* do not add if it is already stashed */ + if (BKE_nla_action_is_stashed(adt, adt->action)) { + return false; + } + + /* create a new track, and add this immediately above the previous stashing track */ + for (prev_track = adt->nla_tracks.last; prev_track; prev_track = prev_track->prev) { + if (strstr(prev_track->name, STASH_TRACK_NAME)) { + break; + } + } + + nlt = BKE_nlatrack_add(adt, prev_track, is_liboverride); + BLI_assert(nlt != NULL); + + /* We need to ensure that if there wasn't any previous instance, + * it must go to be bottom of the stack. */ + if (prev_track == NULL) { + BLI_remlink(&adt->nla_tracks, nlt); + BLI_addhead(&adt->nla_tracks, nlt); + } + + BLI_strncpy(nlt->name, STASH_TRACK_NAME, sizeof(nlt->name)); + BLI_uniquename( + &adt->nla_tracks, nlt, STASH_TRACK_NAME, '.', offsetof(NlaTrack, name), sizeof(nlt->name)); + + /* add the action as a strip in this new track + * NOTE: a new user is created here + */ + strip = BKE_nlastrip_new(adt->action); + BLI_assert(strip != NULL); + + BKE_nlatrack_add_strip(nlt, strip, is_liboverride); + BKE_nlastrip_validate_name(adt, strip); + + /* mark the stash track and strip so that they doesn't disturb the stack animation, + * and are unlikely to draw attention to itself (or be accidentally bumped around) + * + * NOTE: this must be done *after* adding the strip to the track, or else + * the strip locking will prevent the strip from getting added + */ + nlt->flag |= (NLATRACK_MUTED | NLATRACK_PROTECTED); + strip->flag &= ~(NLASTRIP_FLAG_SELECT | NLASTRIP_FLAG_ACTIVE); + + /* also mark the strip for auto syncing the length, so that the strips accurately + * reflect the length of the action + * XXX: we could do with some extra flags here to prevent repeats/scaling options from working! + */ + strip->flag |= NLASTRIP_FLAG_SYNC_LENGTH; + + /* succeeded */ + return true; +} + +/* Core Tools ------------------------------------------- */ + +void BKE_nla_action_pushdown(AnimData *adt, const bool is_liboverride) +{ + NlaStrip *strip; + + /* sanity checks */ + /* TODO: need to report the error for this */ + if (ELEM(NULL, adt, adt->action)) { + return; + } + + /* if the action is empty, we also shouldn't try to add to stack, + * as that will cause us grief down the track + */ + /* TODO: what about modifiers? */ + if (action_has_motion(adt->action) == 0) { + CLOG_ERROR(&LOG, "action has no data"); + return; + } + + /* add a new NLA strip to the track, which references the active action */ + strip = BKE_nlastack_add_strip(adt, adt->action, is_liboverride); + if (strip == NULL) { + return; + } + + /* clear reference to action now that we've pushed it onto the stack */ + id_us_min(&adt->action->id); + adt->action = NULL; + + /* copy current "action blending" settings from adt to the strip, + * as it was keyframed with these settings, so omitting them will + * change the effect [#54233]. */ + strip->blendmode = adt->act_blendmode; + strip->influence = adt->act_influence; + strip->extendmode = adt->act_extendmode; + + if (adt->act_influence < 1.0f) { + /* enable "user-controlled" influence (which will insert a default keyframe) + * so that the influence doesn't get lost on the new update + * + * NOTE: An alternative way would have been to instead hack the influence + * to not get always get reset to full strength if NLASTRIP_FLAG_USR_INFLUENCE + * is disabled but auto-blending isn't being used. However, that approach + * is a bit hacky/hard to discover, and may cause backwards compatibility issues, + * so it's better to just do it this way. + */ + strip->flag |= NLASTRIP_FLAG_USR_INFLUENCE; + BKE_nlastrip_validate_fcurves(strip); + } + + /* make strip the active one... */ + BKE_nlastrip_set_active(adt, strip); +} + +static void nla_tweakmode_find_active(const ListBase /* NlaTrack */ *nla_tracks, + NlaTrack **r_track_of_active_strip, + NlaStrip **r_active_strip) +{ + NlaTrack *nlt, *activeTrack = NULL; + NlaStrip *strip, *activeStrip = NULL; + + /* go over the tracks, finding the active one, and its active strip + * - if we cannot find both, then there's nothing to do + */ + for (nlt = nla_tracks->first; nlt; nlt = nlt->next) { + /* check if active */ + if (nlt->flag & NLATRACK_ACTIVE) { + /* store reference to this active track */ + activeTrack = nlt; + + /* now try to find active strip */ + activeStrip = BKE_nlastrip_find_active(nlt); + break; + } + } + + /* There are situations where we may have multiple strips selected and we want to enter + * tweak-mode on all of those at once. Usually in those cases, + * it will usually just be a single strip per AnimData. + * In such cases, compromise and take the last selected track and/or last selected strip, #28468. + */ + if (activeTrack == NULL) { + /* try last selected track for active strip */ + for (nlt = nla_tracks->last; nlt; nlt = nlt->prev) { + if (nlt->flag & NLATRACK_SELECTED) { + /* assume this is the active track */ + activeTrack = nlt; + + /* try to find active strip */ + activeStrip = BKE_nlastrip_find_active(nlt); + break; + } + } + } + if ((activeTrack) && (activeStrip == NULL)) { + /* No active strip in active or last selected track; + * compromise for first selected (assuming only single). */ + for (strip = activeTrack->strips.first; strip; strip = strip->next) { + if (strip->flag & (NLASTRIP_FLAG_SELECT | NLASTRIP_FLAG_ACTIVE)) { + activeStrip = strip; + break; + } + } + } + + *r_track_of_active_strip = activeTrack; + *r_active_strip = activeStrip; +} + +bool BKE_nla_tweakmode_enter(AnimData *adt) +{ + NlaTrack *nlt, *activeTrack = NULL; + NlaStrip *strip, *activeStrip = NULL; + + /* verify that data is valid */ + if (ELEM(NULL, adt, adt->nla_tracks.first)) { + return false; + } + + /* If block is already in tweak-mode, just leave, but we should report + * that this block is in tweak-mode (as our return-code). */ + if (adt->flag & ADT_NLA_EDIT_ON) { + return true; + } + + nla_tweakmode_find_active(&adt->nla_tracks, &activeTrack, &activeStrip); + + if (ELEM(NULL, activeTrack, activeStrip, activeStrip->act)) { + if (G.debug & G_DEBUG) { + printf("NLA tweak-mode enter - neither active requirement found\n"); + printf("\tactiveTrack = %p, activeStrip = %p\n", (void *)activeTrack, (void *)activeStrip); + } + return false; + } + + /* Go over all the tracks, tagging each strip that uses the same + * action as the active strip, but leaving everything else alone. + */ + for (nlt = adt->nla_tracks.first; nlt; nlt = nlt->next) { + for (strip = nlt->strips.first; strip; strip = strip->next) { + if (strip->act == activeStrip->act) { + strip->flag |= NLASTRIP_FLAG_TWEAKUSER; + } + else { + strip->flag &= ~NLASTRIP_FLAG_TWEAKUSER; + } + } + } + + /* Untag tweaked track. This leads to non tweaked actions being drawn differently than the + * tweaked action. */ + activeStrip->flag &= ~NLASTRIP_FLAG_TWEAKUSER; + + /* go over all the tracks after AND INCLUDING the active one, tagging them as being disabled + * - the active track needs to also be tagged, otherwise, it'll overlap with the tweaks going on + */ + activeTrack->flag |= NLATRACK_DISABLED; + if ((adt->flag & ADT_NLA_EVAL_UPPER_TRACKS) == 0) { + for (nlt = activeTrack->next; nlt; nlt = nlt->next) { + nlt->flag |= NLATRACK_DISABLED; + } + } + + /* handle AnimData level changes: + * - 'real' active action to temp storage (no need to change user-counts). + * - Action of active strip set to be the 'active action', and have its user-count incremented. + * - Editing-flag for this AnimData block should also get turned on + * (for more efficient restoring). + * - Take note of the active strip for mapping-correction of keyframes + * in the action being edited. + */ + adt->tmpact = adt->action; + adt->action = activeStrip->act; + adt->act_track = activeTrack; + adt->actstrip = activeStrip; + id_us_plus(&activeStrip->act->id); + adt->flag |= ADT_NLA_EDIT_ON; + + /* done! */ + return true; +} + +void BKE_nla_tweakmode_exit(AnimData *adt) +{ + NlaStrip *strip; + NlaTrack *nlt; + + /* verify that data is valid */ + if (ELEM(NULL, adt, adt->nla_tracks.first)) { + return; + } + + /* hopefully the flag is correct - skip if not on */ + if ((adt->flag & ADT_NLA_EDIT_ON) == 0) { + return; + } + + /* sync the length of the user-strip with the new state of the action + * but only if the user has explicitly asked for this to happen + * (see #34645 for things to be careful about) + */ + if ((adt->actstrip) && (adt->actstrip->flag & NLASTRIP_FLAG_SYNC_LENGTH)) { + strip = adt->actstrip; + + /* must be action-clip only (transitions don't have scale) */ + if ((strip->type == NLASTRIP_TYPE_CLIP) && (strip->act)) { + BKE_nlastrip_recalculate_bounds_sync_action(strip); + } + } + + /* for all Tracks, clear the 'disabled' flag + * for all Strips, clear the 'tweak-user' flag + */ + for (nlt = adt->nla_tracks.first; nlt; nlt = nlt->next) { + nlt->flag &= ~NLATRACK_DISABLED; + + for (strip = nlt->strips.first; strip; strip = strip->next) { + /* sync strip extents if this strip uses the same action */ + if ((adt->actstrip) && (adt->actstrip->act == strip->act) && + (strip->flag & NLASTRIP_FLAG_SYNC_LENGTH)) { + BKE_nlastrip_recalculate_bounds_sync_action(strip); + } + + /* clear tweakuser flag */ + strip->flag &= ~NLASTRIP_FLAG_TWEAKUSER; + } + } + + /* handle AnimData level changes: + * - 'temporary' active action needs its user-count decreased, + * since we're removing this reference + * - 'real' active action is restored from storage + * - storage pointer gets cleared (to avoid having bad notes hanging around) + * - editing-flag for this AnimData block should also get turned off + * - clear pointer to active strip + */ + if (adt->action) { + id_us_min(&adt->action->id); + } + adt->action = adt->tmpact; + adt->tmpact = NULL; + adt->act_track = NULL; + adt->actstrip = NULL; + adt->flag &= ~ADT_NLA_EDIT_ON; +} + +static void blend_write_nla_strips(BlendWriter *writer, ListBase *strips) +{ + BLO_write_struct_list(writer, NlaStrip, strips); + LISTBASE_FOREACH (NlaStrip *, strip, strips) { + /* write the strip's F-Curves and modifiers */ + BKE_fcurve_blend_write(writer, &strip->fcurves); + BKE_fmodifiers_blend_write(writer, &strip->modifiers); + + /* write the strip's children */ + blend_write_nla_strips(writer, &strip->strips); + } +} + +static void blend_data_read_nla_strips(BlendDataReader *reader, ListBase *strips) +{ + LISTBASE_FOREACH (NlaStrip *, strip, strips) { + /* strip's child strips */ + BLO_read_list(reader, &strip->strips); + blend_data_read_nla_strips(reader, &strip->strips); + + /* strip's F-Curves */ + BLO_read_list(reader, &strip->fcurves); + BKE_fcurve_blend_read_data(reader, &strip->fcurves); + + /* strip's F-Modifiers */ + BLO_read_list(reader, &strip->modifiers); + BKE_fmodifiers_blend_read_data(reader, &strip->modifiers, NULL); + } +} + +static void blend_lib_read_nla_strips(BlendLibReader *reader, ID *id, ListBase *strips) +{ + LISTBASE_FOREACH (NlaStrip *, strip, strips) { + /* check strip's children */ + blend_lib_read_nla_strips(reader, id, &strip->strips); + + /* check strip's F-Curves */ + BKE_fcurve_blend_read_lib(reader, id, &strip->fcurves); + + /* reassign the counted-reference to action */ + BLO_read_id_address(reader, id->lib, &strip->act); + } +} + +static void blend_read_expand_nla_strips(BlendExpander *expander, ListBase *strips) +{ + LISTBASE_FOREACH (NlaStrip *, strip, strips) { + /* check child strips */ + blend_read_expand_nla_strips(expander, &strip->strips); + + /* check F-Curves */ + BKE_fcurve_blend_read_expand(expander, &strip->fcurves); + + /* check F-Modifiers */ + BKE_fmodifiers_blend_read_expand(expander, &strip->modifiers); + + /* relink referenced action */ + BLO_expand(expander, strip->act); + } +} + +void BKE_nla_blend_write(BlendWriter *writer, ListBase *tracks) +{ + /* write all the tracks */ + LISTBASE_FOREACH (NlaTrack *, nlt, tracks) { + /* write the track first */ + BLO_write_struct(writer, NlaTrack, nlt); + + /* write the track's strips */ + blend_write_nla_strips(writer, &nlt->strips); + } +} + +void BKE_nla_blend_read_data(BlendDataReader *reader, ListBase *tracks) +{ + LISTBASE_FOREACH (NlaTrack *, nlt, tracks) { + /* relink list of strips */ + BLO_read_list(reader, &nlt->strips); + + /* relink strip data */ + blend_data_read_nla_strips(reader, &nlt->strips); + } +} + +void BKE_nla_blend_read_lib(BlendLibReader *reader, ID *id, ListBase *tracks) +{ + /* we only care about the NLA strips inside the tracks */ + LISTBASE_FOREACH (NlaTrack *, nlt, tracks) { + /* If linking from a library, clear 'local' library override flag. */ + if (ID_IS_LINKED(id)) { + nlt->flag &= ~NLATRACK_OVERRIDELIBRARY_LOCAL; + } + + blend_lib_read_nla_strips(reader, id, &nlt->strips); + } +} + +void BKE_nla_blend_read_expand(struct BlendExpander *expander, struct ListBase *tracks) +{ + /* nla-data - referenced actions */ + LISTBASE_FOREACH (NlaTrack *, nlt, tracks) { + blend_read_expand_nla_strips(expander, &nlt->strips); + } +} diff --git a/source/blender/blenkernel/intern/nla.c.rej b/source/blender/blenkernel/intern/nla.c.rej new file mode 100644 index 00000000000..13ee5a7ed53 --- /dev/null +++ b/source/blender/blenkernel/intern/nla.c.rej @@ -0,0 +1,478 @@ +*************** +*** 91,106 **** + /* free own F-Modifiers */ + free_fmodifiers(&strip->modifiers); + +- MEM_freeN(strip); +- } +- +- void BKE_nlatrack_remove_strip(NlaTrack *track, NlaStrip *strip) +- { +- BLI_assert(track); +- BKE_nlastrip_remove(&track->strips, strip); + } + +- void BKE_nlatrack_free(NlaTrack *nlt, const bool do_id_user) + { + NlaStrip *strip, *stripn; + +--- 91,106 ---- + /* free own F-Modifiers */ + free_fmodifiers(&strip->modifiers); + ++ /* free the strip itself */ ++ if (strips) { ++ BLI_freelinkN(strips, strip); ++ } ++ else { ++ MEM_freeN(strip); ++ } + } + ++ void BKE_nlatrack_free(ListBase *tracks, NlaTrack *nlt, bool do_id_user) + { + NlaStrip *strip, *stripn; + +*************** +*** 112,152 **** + /* free strips */ + for (strip = nlt->strips.first; strip; strip = stripn) { + stripn = strip->next; +- BKE_nlastrip_remove_and_free(&nlt->strips, strip, do_id_user); + } + + /* free NLA track itself now */ +- MEM_freeN(nlt); +- } +- +- void BKE_nlastrip_remove(ListBase *strips, NlaStrip *strip) +- { +- BLI_assert(strips); +- BLI_remlink(strips, strip); +- } +- +- void BKE_nlastrip_remove_and_free(ListBase *strips, NlaStrip *strip, const bool do_id_user) +- { +- BKE_nlastrip_remove(strips, strip); +- BKE_nlastrip_free(strip, do_id_user); +- } +- +- void BKE_nlatrack_remove(ListBase *tracks, NlaTrack *nlt) +- { +- BLI_assert(tracks); +- BLI_remlink(tracks, nlt); +- } +- +- /* Remove the given NLA track from the set of NLA tracks, free the track's data, +- * and the track itself. +- */ +- void BKE_nlatrack_remove_and_free(ListBase *tracks, NlaTrack *nlt, const bool do_id_user) +- { +- BKE_nlatrack_remove(tracks, nlt); +- BKE_nlatrack_free(nlt, do_id_user); + } + +- void BKE_nla_tracks_free(ListBase *tracks, const bool do_id_user) + { + NlaTrack *nlt, *nltn; + +--- 112,130 ---- + /* free strips */ + for (strip = nlt->strips.first; strip; strip = stripn) { + stripn = strip->next; ++ BKE_nlastrip_free(&nlt->strips, strip, do_id_user); + } + + /* free NLA track itself now */ ++ if (tracks) { ++ BLI_freelinkN(tracks, nlt); ++ } ++ else { ++ MEM_freeN(nlt); ++ } + } + ++ void BKE_nla_tracks_free(ListBase *tracks, bool do_id_user) + { + NlaTrack *nlt, *nltn; + +*************** +*** 158,164 **** + /* free tracks one by one */ + for (nlt = tracks->first; nlt; nlt = nltn) { + nltn = nlt->next; +- BKE_nlatrack_remove_and_free(tracks, nlt, do_id_user); + } + + /* clear the list's pointers to be safe */ +--- 136,142 ---- + /* free tracks one by one */ + for (nlt = tracks->first; nlt; nlt = nltn) { + nltn = nlt->next; ++ BKE_nlatrack_free(tracks, nlt, do_id_user); + } + + /* clear the list's pointers to be safe */ +*************** +*** 324,355 **** + + /* Adding ------------------------------------------- */ + +- NlaTrack *BKE_nlatrack_new() + { + /* allocate new track */ +- NlaTrack *nlt = MEM_callocN(sizeof(NlaTrack), "NlaTrack"); + + /* set settings requiring the track to not be part of the stack yet */ + nlt->flag = NLATRACK_SELECTED | NLATRACK_OVERRIDELIBRARY_LOCAL; +- +- return nlt; +- } +- +- void BKE_nlatrack_insert_after(ListBase *nla_tracks, +- NlaTrack *prev, +- NlaTrack *new_track, +- const bool is_liboverride) +- { +- BLI_assert(!ELEM(NULL, nla_tracks, new_track)); +- +- /** If NULL, then caller intends to insert a new head. But, tracks are not allowed to be placed +- * before library overrides. So it must inserted after the last override. */ +- if (prev == NULL) { +- NlaTrack *first_track = (NlaTrack *)nla_tracks->first; +- if (first_track != NULL && (first_track->flag & NLATRACK_OVERRIDELIBRARY_LOCAL) == 0) { +- prev = first_track; +- } +- } + + /* In liboverride case, we only add local tracks after all those coming from the linked data, + * so we need to find the first local track. */ +--- 302,322 ---- + + /* Adding ------------------------------------------- */ + ++ NlaTrack *BKE_nlatrack_add(AnimData *adt, NlaTrack *prev, const bool is_liboverride) + { ++ NlaTrack *nlt; ++ ++ /* sanity checks */ ++ if (adt == NULL) { ++ return NULL; ++ } ++ + /* allocate new track */ ++ nlt = MEM_callocN(sizeof(NlaTrack), "NlaTrack"); + + /* set settings requiring the track to not be part of the stack yet */ + nlt->flag = NLATRACK_SELECTED | NLATRACK_OVERRIDELIBRARY_LOCAL; ++ nlt->index = BLI_listbase_count(&adt->nla_tracks); + + /* In liboverride case, we only add local tracks after all those coming from the linked data, + * so we need to find the first local track. */ +*************** +*** 361,441 **** + prev = first_local != NULL ? first_local->prev : NULL; + } + /* Add track to stack, and make it the active one. */ +- BLI_insertlinkafter(nla_tracks, prev, new_track); +- new_track->index = BLI_findindex(nla_tracks, new_track); + + /* must have unique name, but we need to seed this */ +- strcpy(new_track->name, "NlaTrack"); +- BLI_uniquename(nla_tracks, +- new_track, +- DATA_("NlaTrack"), +- '.', +- offsetof(NlaTrack, name), +- sizeof(new_track->name)); +- } +- +- void BKE_nlatrack_insert_before(ListBase *nla_tracks, +- NlaTrack *next, +- NlaTrack *new_track, +- const bool is_liboverride) +- { +- if (is_liboverride) { +- +- /** Currently, all library override tracks are assumed to be grouped together at the start of +- * the list. So we can only add the new track after the last library track. */ +- if (next != NULL && (next->flag & NLATRACK_OVERRIDELIBRARY_LOCAL) == 0) { +- BKE_nlatrack_insert_after(nla_tracks, next, new_track, is_liboverride); +- return; +- } +- } + +- BLI_insertlinkbefore(nla_tracks, next, new_track); +- new_track->index = BLI_findindex(nla_tracks, new_track); +- +- /* Must have unique name, but we need to seed this. */ +- strcpy(new_track->name, "NlaTrack"); +- BLI_uniquename(nla_tracks, +- new_track, +- DATA_("NlaTrack"), +- '.', +- offsetof(NlaTrack, name), +- sizeof(new_track->name)); +- } +- +- NlaTrack *BKE_nlatrack_new_after_and_set_active(ListBase *nla_tracks, +- NlaTrack *prev, +- const bool is_liboverride) +- { +- NlaTrack *new_track = BKE_nlatrack_new(); +- +- BKE_nlatrack_insert_after(nla_tracks, prev, new_track, is_liboverride); +- BKE_nlatrack_set_active(nla_tracks, new_track); +- +- return new_track; +- } +- +- NlaTrack *BKE_nlatrack_new_before_and_set_active(ListBase *nla_tracks, +- NlaTrack *next, +- const bool is_liboverride) +- { +- NlaTrack *new_track = BKE_nlatrack_new(); +- +- BKE_nlatrack_insert_before(nla_tracks, next, new_track, is_liboverride); +- BKE_nlatrack_set_active(nla_tracks, new_track); +- +- return new_track; +- } +- +- NlaTrack *BKE_nlatrack_new_tail_and_set_active(ListBase *nla_tracks, const bool is_liboverride) +- { +- return BKE_nlatrack_new_after_and_set_active( +- nla_tracks, (NlaTrack *)nla_tracks->last, is_liboverride); +- } +- +- NlaTrack *BKE_nlatrack_new_head_and_set_active(ListBase *nla_tracks, const bool is_liboverride) +- { +- return BKE_nlatrack_new_before_and_set_active( +- nla_tracks, (NlaTrack *)nla_tracks->first, is_liboverride); + } + + NlaStrip *BKE_nlastrip_new(bAction *act) +--- 328,348 ---- + prev = first_local != NULL ? first_local->prev : NULL; + } + /* Add track to stack, and make it the active one. */ ++ if (prev != NULL) { ++ BLI_insertlinkafter(&adt->nla_tracks, prev, nlt); ++ } ++ else { ++ BLI_addtail(&adt->nla_tracks, nlt); ++ } ++ BKE_nlatrack_set_active(&adt->nla_tracks, nlt); + + /* must have unique name, but we need to seed this */ ++ strcpy(nlt->name, "NlaTrack"); ++ BLI_uniquename( ++ &adt->nla_tracks, nlt, DATA_("NlaTrack"), '.', offsetof(NlaTrack, name), sizeof(nlt->name)); + ++ /* return the new track */ ++ return nlt; + } + + NlaStrip *BKE_nlastrip_new(bAction *act) +*************** +*** 504,515 **** + } + + /* firstly try adding strip to last track, but if that fails, add to a new track */ +- if (!BKE_nlatrack_try_add_strip(adt->nla_tracks.last, strip, is_liboverride)) { + /* trying to add to the last track failed (no track or no space), + * so add a new track to the stack, and add to that... + */ +- nlt = BKE_nlatrack_new_tail_and_set_active(&adt->nla_tracks, is_liboverride); +- BKE_nlatrack_add_strip(nlt, strip); + } + + /* automatically name it too */ +--- 411,422 ---- + } + + /* firstly try adding strip to last track, but if that fails, add to a new track */ ++ if (BKE_nlatrack_add_strip(adt->nla_tracks.last, strip, is_liboverride) == 0) { + /* trying to add to the last track failed (no track or no space), + * so add a new track to the stack, and add to that... + */ ++ nlt = BKE_nlatrack_add(adt, NULL, is_liboverride); ++ BKE_nlatrack_add_strip(nlt, strip, is_liboverride); + } + + /* automatically name it too */ +*************** +*** 809,821 **** + strips->last = tmp.last; + } + +- void BKE_nlastrips_add_strip(ListBase *strips, NlaStrip *strip) + { + NlaStrip *ns; + bool not_added = true; + + /* sanity checks */ +- BLI_assert(!ELEM(NULL, strips, strip)); + + /* find the right place to add the strip to the nominated track */ + for (ns = strips->first; ns; ns = ns->next) { +--- 716,735 ---- + strips->last = tmp.last; + } + ++ bool BKE_nlastrips_add_strip(ListBase *strips, NlaStrip *strip) + { + NlaStrip *ns; + bool not_added = true; + + /* sanity checks */ ++ if (ELEM(NULL, strips, strip)) { ++ return false; ++ } ++ ++ /* check if any space to add */ ++ if (BKE_nlastrips_has_space(strips, strip->start, strip->end) == 0) { ++ return false; ++ } + + /* find the right place to add the strip to the nominated track */ + for (ns = strips->first; ns; ns = ns->next) { +*************** +*** 830,849 **** + /* just add to the end of the list of the strips then... */ + BLI_addtail(strips, strip); + } +- } +- +- /** This version does additional checks (NULL check and space check). */ +- bool BKE_nlastrips_try_add_strip(ListBase *strips, NlaStrip *strip) +- { +- if (ELEM(NULL, strips, strip)) { +- return false; +- } +- +- if (!BKE_nlastrips_has_space(strips, strip->start, strip->end)) { +- return false; +- } + +- BKE_nlastrips_add_strip(strips, strip); + return true; + } + +--- 744,751 ---- + /* just add to the end of the list of the strips then... */ + BLI_addtail(strips, strip); + } + ++ /* added... */ + return true; + } + +*************** +*** 894,900 **** + } + } + +- bool BKE_nlameta_try_add_strip(NlaStrip *mstrip, NlaStrip *strip) + { + /* sanity checks */ + if (ELEM(NULL, mstrip, strip)) { +--- 796,802 ---- + } + } + ++ bool BKE_nlameta_add_strip(NlaStrip *mstrip, NlaStrip *strip) + { + /* sanity checks */ + if (ELEM(NULL, mstrip, strip)) { +*************** +*** 939,945 **** + } + + /* just try to add to the meta-strip (no dimension changes needed) */ +- return BKE_nlastrips_try_add_strip(&mstrip->strips, strip); + } + + void BKE_nlameta_flush_transforms(NlaStrip *mstrip) +--- 841,847 ---- + } + + /* just try to add to the meta-strip (no dimension changes needed) */ ++ return BKE_nlastrips_add_strip(&mstrip->strips, strip); + } + + void BKE_nlameta_flush_transforms(NlaStrip *mstrip) +*************** +*** 1160,1166 **** + BKE_nlastrips_sort_strips(&nlt->strips); + } + +- bool BKE_nlatrack_try_add_strip(NlaTrack *nlt, NlaStrip *strip, const bool is_liboverride) + { + /* sanity checks */ + if (ELEM(NULL, nlt, strip)) { +--- 1062,1068 ---- + BKE_nlastrips_sort_strips(&nlt->strips); + } + ++ bool BKE_nlatrack_add_strip(NlaTrack *nlt, NlaStrip *strip, const bool is_liboverride) + { + /* sanity checks */ + if (ELEM(NULL, nlt, strip)) { +*************** +*** 1174,1186 **** + } + + /* try to add the strip to the track using a more generic function */ +- return BKE_nlastrips_try_add_strip(&nlt->strips, strip); +- } +- +- void BKE_nlatrack_add_strip(NlaTrack *nlt, NlaStrip *strip) +- { +- BLI_assert(!ELEM(NULL, nlt, strip)); +- BKE_nlastrips_add_strip(&nlt->strips, strip); + } + + bool BKE_nlatrack_get_bounds(NlaTrack *nlt, float bounds[2]) +--- 1076,1082 ---- + } + + /* try to add the strip to the track using a more generic function */ ++ return BKE_nlastrips_add_strip(&nlt->strips, strip); + } + + bool BKE_nlatrack_get_bounds(NlaTrack *nlt, float bounds[2]) +*************** +*** 1884,1890 **** + } + } + +- nlt = BKE_nlatrack_new_after_and_set_active(&adt->nla_tracks, prev_track, is_liboverride); + BLI_assert(nlt != NULL); + + /* We need to ensure that if there wasn't any previous instance, +--- 1780,1786 ---- + } + } + ++ nlt = BKE_nlatrack_add(adt, prev_track, is_liboverride); + BLI_assert(nlt != NULL); + + /* We need to ensure that if there wasn't any previous instance, +*************** +*** 1904,1910 **** + strip = BKE_nlastrip_new(adt->action); + BLI_assert(strip != NULL); + +- BKE_nlatrack_add_strip(nlt, strip); + BKE_nlastrip_validate_name(adt, strip); + + /* mark the stash track and strip so that they doesn't disturb the stack animation, +--- 1800,1806 ---- + strip = BKE_nlastrip_new(adt->action); + BLI_assert(strip != NULL); + ++ BKE_nlatrack_add_strip(nlt, strip, is_liboverride); + BKE_nlastrip_validate_name(adt, strip); + + /* mark the stash track and strip so that they doesn't disturb the stack animation, diff --git a/source/blender/editors/space_action/action_data.c.orig b/source/blender/editors/space_action/action_data.c.orig new file mode 100644 index 00000000000..bb8456f6486 --- /dev/null +++ b/source/blender/editors/space_action/action_data.c.orig @@ -0,0 +1,988 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later + * Copyright 2015 Blender Foundation. */ + +/** \file + * \ingroup spaction + */ + +#include +#include +#include +#include + +#include "BLI_utildefines.h" + +#include "BLT_translation.h" + +#include "DNA_anim_types.h" +#include "DNA_gpencil_types.h" +#include "DNA_key_types.h" +#include "DNA_mask_types.h" +#include "DNA_object_types.h" +#include "DNA_scene_types.h" + +#include "RNA_access.h" +#include "RNA_define.h" +#include "RNA_enum_types.h" +#include "RNA_prototypes.h" + +#include "BKE_action.h" +#include "BKE_context.h" +#include "BKE_fcurve.h" +#include "BKE_key.h" +#include "BKE_lib_id.h" +#include "BKE_nla.h" +#include "BKE_report.h" +#include "BKE_scene.h" + +#include "UI_view2d.h" + +#include "ED_anim_api.h" +#include "ED_gpencil.h" +#include "ED_keyframes_edit.h" +#include "ED_keyframing.h" +#include "ED_markers.h" +#include "ED_mask.h" +#include "ED_screen.h" + +#include "DEG_depsgraph.h" + +#include "WM_api.h" +#include "WM_types.h" + +#include "UI_interface.h" + +#include "action_intern.h" + +/* ************************************************************************** */ +/* ACTION CREATION */ + +AnimData *ED_actedit_animdata_from_context(const bContext *C, ID **r_adt_id_owner) +{ + SpaceAction *saction = (SpaceAction *)CTX_wm_space_data(C); + Object *ob = CTX_data_active_object(C); + AnimData *adt = NULL; + + /* Get AnimData block to use */ + if (saction->mode == SACTCONT_ACTION) { + /* Currently, "Action Editor" means object-level only... */ + if (ob) { + adt = ob->adt; + if (r_adt_id_owner) { + *r_adt_id_owner = &ob->id; + } + } + } + else if (saction->mode == SACTCONT_SHAPEKEY) { + Key *key = BKE_key_from_object(ob); + if (key) { + adt = key->adt; + if (r_adt_id_owner) { + *r_adt_id_owner = &key->id; + } + } + } + + return adt; +} + +/* -------------------------------------------------------------------- */ + +/* Create new action */ +static bAction *action_create_new(bContext *C, bAction *oldact) +{ + ScrArea *area = CTX_wm_area(C); + bAction *action; + + /* create action - the way to do this depends on whether we've got an + * existing one there already, in which case we make a copy of it + * (which is useful for "versioning" actions within the same file) + */ + if (oldact && GS(oldact->id.name) == ID_AC) { + /* make a copy of the existing action */ + action = (bAction *)BKE_id_copy(CTX_data_main(C), &oldact->id); + } + else { + /* just make a new (empty) action */ + action = BKE_action_add(CTX_data_main(C), "Action"); + } + + /* when creating new ID blocks, there is already 1 user (as for all new datablocks), + * but the RNA pointer code will assign all the proper users instead, so we compensate + * for that here + */ + BLI_assert(action->id.us == 1); + id_us_min(&action->id); + + /* set ID-Root type */ + if (area->spacetype == SPACE_ACTION) { + SpaceAction *saction = (SpaceAction *)area->spacedata.first; + + if (saction->mode == SACTCONT_SHAPEKEY) { + action->idroot = ID_KE; + } + else { + action->idroot = ID_OB; + } + } + + return action; +} + +/* Change the active action used by the action editor */ +static void actedit_change_action(bContext *C, bAction *act) +{ + bScreen *screen = CTX_wm_screen(C); + SpaceAction *saction = (SpaceAction *)CTX_wm_space_data(C); + + PointerRNA ptr, idptr; + PropertyRNA *prop; + + /* create RNA pointers and get the property */ + RNA_pointer_create(&screen->id, &RNA_SpaceDopeSheetEditor, saction, &ptr); + prop = RNA_struct_find_property(&ptr, "action"); + + /* NOTE: act may be NULL here, so better to just use a cast here */ + RNA_id_pointer_create((ID *)act, &idptr); + + /* set the new pointer, and force a refresh */ + RNA_property_pointer_set(&ptr, prop, idptr, NULL); + RNA_property_update(C, &ptr, prop); +} + +/* ******************** New Action Operator *********************** */ + +/* Criteria: + * 1) There must be an dopesheet/action editor, and it must be in a mode which uses actions... + * OR + * The NLA Editor is active (i.e. Animation Data panel -> new action) + * 2) The associated AnimData block must not be in tweak-mode. + */ +static bool action_new_poll(bContext *C) +{ + Scene *scene = CTX_data_scene(C); + + /* Check tweak-mode is off (as you don't want to be tampering with the action in that case) */ + /* NOTE: unlike for pushdown, + * this operator needs to be run when creating an action from nothing... */ + if (ED_operator_action_active(C)) { + SpaceAction *saction = (SpaceAction *)CTX_wm_space_data(C); + Object *ob = CTX_data_active_object(C); + + /* For now, actions are only for the active object, and on object and shape-key levels... */ + if (saction->mode == SACTCONT_ACTION) { + /* XXX: This assumes that actions are assigned to the active object in this mode */ + if (ob) { + if ((ob->adt == NULL) || (ob->adt->flag & ADT_NLA_EDIT_ON) == 0) { + return true; + } + } + } + else if (saction->mode == SACTCONT_SHAPEKEY) { + Key *key = BKE_key_from_object(ob); + if (key) { + if ((key->adt == NULL) || (key->adt->flag & ADT_NLA_EDIT_ON) == 0) { + return true; + } + } + } + } + else if (ED_operator_nla_active(C)) { + if (!(scene->flag & SCE_NLA_EDIT_ON)) { + return true; + } + } + + /* something failed... */ + return false; +} + +static int action_new_exec(bContext *C, wmOperator *UNUSED(op)) +{ + PointerRNA ptr, idptr; + PropertyRNA *prop; + + bAction *oldact = NULL; + AnimData *adt = NULL; + ID *adt_id_owner = NULL; + /* hook into UI */ + UI_context_active_but_prop_get_templateID(C, &ptr, &prop); + + if (prop) { + /* The operator was called from a button. */ + PointerRNA oldptr; + + oldptr = RNA_property_pointer_get(&ptr, prop); + oldact = (bAction *)oldptr.owner_id; + + /* stash the old action to prevent it from being lost */ + if (ptr.type == &RNA_AnimData) { + adt = ptr.data; + adt_id_owner = ptr.owner_id; + } + else if (ptr.type == &RNA_SpaceDopeSheetEditor) { + adt = ED_actedit_animdata_from_context(C, &adt_id_owner); + } + } + else { + adt = ED_actedit_animdata_from_context(C, &adt_id_owner); + oldact = adt->action; + } + { + bAction *action = NULL; + + /* Perform stashing operation - But only if there is an action */ + if (adt && oldact) { + BLI_assert(adt_id_owner != NULL); + /* stash the action */ + if (BKE_nla_action_stash(adt, ID_IS_OVERRIDE_LIBRARY(adt_id_owner))) { + /* The stash operation will remove the user already + * (and unlink the action from the AnimData action slot). + * Hence, we must unset the ref to the action in the + * action editor too (if this is where we're being called from) + * first before setting the new action once it is created, + * or else the user gets decremented twice! + */ + if (ptr.type == &RNA_SpaceDopeSheetEditor) { + SpaceAction *saction = ptr.data; + saction->action = NULL; + } + } + else { +#if 0 + printf("WARNING: Failed to stash %s. It may already exist in the NLA stack though\n", + oldact->id.name); +#endif + } + } + + /* create action */ + action = action_create_new(C, oldact); + + if (prop) { + /* set this new action + * NOTE: we can't use actedit_change_action, as this function is also called from the NLA + */ + RNA_id_pointer_create(&action->id, &idptr); + RNA_property_pointer_set(&ptr, prop, idptr, NULL); + RNA_property_update(C, &ptr, prop); + } + } + + /* set notifier that keyframes have changed */ + WM_event_add_notifier(C, NC_ANIMATION | ND_KEYFRAME | NA_ADDED, NULL); + + return OPERATOR_FINISHED; +} + +void ACTION_OT_new(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "New Action"; + ot->idname = "ACTION_OT_new"; + ot->description = "Create new action"; + + /* api callbacks */ + ot->exec = action_new_exec; + ot->poll = action_new_poll; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; +} + +/* ******************* Action Push-Down Operator ******************** */ + +/* Criteria: + * 1) There must be an dopesheet/action editor, and it must be in a mode which uses actions + * 2) There must be an action active + * 3) The associated AnimData block must not be in tweak-mode + */ +static bool action_pushdown_poll(bContext *C) +{ + if (ED_operator_action_active(C)) { + SpaceAction *saction = (SpaceAction *)CTX_wm_space_data(C); + AnimData *adt = ED_actedit_animdata_from_context(C, NULL); + + /* Check for AnimData, Actions, and that tweak-mode is off. */ + if (adt && saction->action) { + /* NOTE: We check this for the AnimData block in question and not the global flag, + * as the global flag may be left dirty by some of the browsing ops here. + */ + if (!(adt->flag & ADT_NLA_EDIT_ON)) { + return true; + } + } + } + + /* something failed... */ + return false; +} + +static int action_pushdown_exec(bContext *C, wmOperator *op) +{ + SpaceAction *saction = (SpaceAction *)CTX_wm_space_data(C); + ID *adt_id_owner = NULL; + AnimData *adt = ED_actedit_animdata_from_context(C, &adt_id_owner); + + /* Do the deed... */ + if (adt) { + /* Perform the push-down operation + * - This will deal with all the AnimData-side user-counts. */ + if (action_has_motion(adt->action) == 0) { + /* action may not be suitable... */ + BKE_report(op->reports, RPT_WARNING, "Action must have at least one keyframe or F-Modifier"); + return OPERATOR_CANCELLED; + } + + /* action can be safely added */ + BKE_nla_action_pushdown(adt, ID_IS_OVERRIDE_LIBRARY(adt_id_owner)); + + struct Main *bmain = CTX_data_main(C); + DEG_id_tag_update_ex(bmain, adt_id_owner, ID_RECALC_ANIMATION); + + /* The action needs updating too, as FCurve modifiers are to be reevaluated. They won't extend + * beyond the NLA strip after pushing down to the NLA. */ + DEG_id_tag_update_ex(bmain, &adt->action->id, ID_RECALC_ANIMATION); + + /* Stop displaying this action in this editor + * NOTE: The editor itself doesn't set a user... + */ + saction->action = NULL; + } + + /* Send notifiers that stuff has changed */ + WM_event_add_notifier(C, NC_ANIMATION | ND_NLA_ACTCHANGE, NULL); + return OPERATOR_FINISHED; +} + +void ACTION_OT_push_down(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Push Down Action"; + ot->idname = "ACTION_OT_push_down"; + ot->description = "Push action down on to the NLA stack as a new strip"; + + /* callbacks */ + ot->exec = action_pushdown_exec; + ot->poll = action_pushdown_poll; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; +} + +/* ******************* Action Stash Operator ******************** */ + +static int action_stash_exec(bContext *C, wmOperator *op) +{ + SpaceAction *saction = (SpaceAction *)CTX_wm_space_data(C); + ID *adt_id_owner = NULL; + AnimData *adt = ED_actedit_animdata_from_context(C, &adt_id_owner); + + /* Perform stashing operation */ + if (adt) { + /* don't do anything if this action is empty... */ + if (action_has_motion(adt->action) == 0) { + /* action may not be suitable... */ + BKE_report(op->reports, RPT_WARNING, "Action must have at least one keyframe or F-Modifier"); + return OPERATOR_CANCELLED; + } + + /* stash the action */ + if (BKE_nla_action_stash(adt, ID_IS_OVERRIDE_LIBRARY(adt_id_owner))) { + /* The stash operation will remove the user already, + * so the flushing step later shouldn't double up + * the user-count fixes. Hence, we must unset this ref + * first before setting the new action. + */ + saction->action = NULL; + } + else { + /* action has already been added - simply warn about this, and clear */ + BKE_report(op->reports, RPT_ERROR, "Action has already been stashed"); + } + + /* clear action refs from editor, and then also the backing data (not necessary) */ + actedit_change_action(C, NULL); + } + + /* Send notifiers that stuff has changed */ + WM_event_add_notifier(C, NC_ANIMATION | ND_NLA_ACTCHANGE, NULL); + return OPERATOR_FINISHED; +} + +void ACTION_OT_stash(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Stash Action"; + ot->idname = "ACTION_OT_stash"; + ot->description = "Store this action in the NLA stack as a non-contributing strip for later use"; + + /* callbacks */ + ot->exec = action_stash_exec; + ot->poll = action_pushdown_poll; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + /* properties */ + ot->prop = RNA_def_boolean(ot->srna, + "create_new", + true, + "Create New Action", + "Create a new action once the existing one has been safely stored"); +} + +/* ----------------- */ + +/* Criteria: + * 1) There must be an dopesheet/action editor, and it must be in a mode which uses actions + * 2) The associated AnimData block must not be in tweak-mode + */ +static bool action_stash_create_poll(bContext *C) +{ + if (ED_operator_action_active(C)) { + AnimData *adt = ED_actedit_animdata_from_context(C, NULL); + + /* Check tweak-mode is off (as you don't want to be tampering with the action in that case) */ + /* NOTE: unlike for pushdown, + * this operator needs to be run when creating an action from nothing... */ + if (adt) { + if (!(adt->flag & ADT_NLA_EDIT_ON)) { + return true; + } + } + else { + /* There may not be any action/animdata yet, so, just fallback to the global setting + * (which may not be totally valid yet if the action editor was used and things are + * now in an inconsistent state) + */ + SpaceAction *saction = (SpaceAction *)CTX_wm_space_data(C); + Scene *scene = CTX_data_scene(C); + + if (!(scene->flag & SCE_NLA_EDIT_ON)) { + /* For now, actions are only for the active object, and on object and shape-key levels... + */ + return ELEM(saction->mode, SACTCONT_ACTION, SACTCONT_SHAPEKEY); + } + } + } + + /* something failed... */ + return false; +} + +static int action_stash_create_exec(bContext *C, wmOperator *op) +{ + SpaceAction *saction = (SpaceAction *)CTX_wm_space_data(C); + ID *adt_id_owner = NULL; + AnimData *adt = ED_actedit_animdata_from_context(C, &adt_id_owner); + + /* Check for no action... */ + if (saction->action == NULL) { + /* just create a new action */ + bAction *action = action_create_new(C, NULL); + actedit_change_action(C, action); + } + else if (adt) { + /* Perform stashing operation */ + if (action_has_motion(adt->action) == 0) { + /* don't do anything if this action is empty... */ + BKE_report(op->reports, RPT_WARNING, "Action must have at least one keyframe or F-Modifier"); + return OPERATOR_CANCELLED; + } + + /* stash the action */ + if (BKE_nla_action_stash(adt, ID_IS_OVERRIDE_LIBRARY(adt_id_owner))) { + bAction *new_action = NULL; + + /* Create new action not based on the old one + * (since the "new" operator already does that). */ + new_action = action_create_new(C, NULL); + + /* The stash operation will remove the user already, + * so the flushing step later shouldn't double up + * the user-count fixes. Hence, we must unset this ref + * first before setting the new action. + */ + saction->action = NULL; + actedit_change_action(C, new_action); + } + else { + /* action has already been added - simply warn about this, and clear */ + BKE_report(op->reports, RPT_ERROR, "Action has already been stashed"); + actedit_change_action(C, NULL); + } + } + + /* Send notifiers that stuff has changed */ + WM_event_add_notifier(C, NC_ANIMATION | ND_NLA_ACTCHANGE, NULL); + return OPERATOR_FINISHED; +} + +void ACTION_OT_stash_and_create(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Stash Action"; + ot->idname = "ACTION_OT_stash_and_create"; + ot->description = + "Store this action in the NLA stack as a non-contributing strip for later use, and create a " + "new action"; + + /* callbacks */ + ot->exec = action_stash_create_exec; + ot->poll = action_stash_create_poll; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; +} + +/* ************************************************************************** */ +/* ACTION UNLINK */ + +/* ******************* Action Unlink Operator ******************** */ +/* We use a custom unlink operator here, as there are some technicalities which need special care: + * 1) When in Tweak Mode, it shouldn't be possible to unlink the active action, + * or else, everything turns to custard. + * 2) If the Action doesn't have any other users, the user should at least get + * a warning that it is going to get lost. + * 3) We need a convenient way to exit Tweak Mode from the Action Editor + */ + +void ED_animedit_unlink_action( + bContext *C, ID *id, AnimData *adt, bAction *act, ReportList *reports, bool force_delete) +{ + ScrArea *area = CTX_wm_area(C); + + /* If the old action only has a single user (that it's about to lose), + * warn user about it + * + * TODO: Maybe we should just save it for them? But then, there's the problem of + * trying to get rid of stuff that's actually unwanted! + */ + if (act->id.us == 1) { + BKE_reportf(reports, + RPT_WARNING, + "Action '%s' will not be saved, create Fake User or Stash in NLA Stack to retain", + act->id.name + 2); + } + + /* Clear Fake User and remove action stashing strip (if present) */ + if (force_delete) { + /* Remove stashed strip binding this action to this datablock */ + /* XXX: we cannot unlink it from *OTHER* datablocks that may also be stashing it, + * but GE users only seem to use/care about single-object binding for now so this + * should be fine + */ + if (adt) { + NlaTrack *nlt, *nlt_next; + NlaStrip *strip, *nstrip; + + for (nlt = adt->nla_tracks.first; nlt; nlt = nlt_next) { + nlt_next = nlt->next; + + if (strstr(nlt->name, DATA_("[Action Stash]"))) { + for (strip = nlt->strips.first; strip; strip = nstrip) { + nstrip = strip->next; + + if (strip->act == act) { + /* Remove this strip, and the track too if it doesn't have anything else */ + BKE_nlastrip_remove_and_free(&nlt->strips, strip, true); + + if (nlt->strips.first == NULL) { + BLI_assert(nstrip == NULL); + BKE_nlatrack_free(&adt->nla_tracks, nlt, true); + } + } + } + } + } + } + + /* Clear Fake User */ + id_fake_user_clear(&act->id); + } + + /* If in Tweak Mode, don't unlink. Instead, this becomes a shortcut to exit Tweak Mode. */ + if ((adt) && (adt->flag & ADT_NLA_EDIT_ON)) { + BKE_nla_tweakmode_exit(adt); + + Scene *scene = CTX_data_scene(C); + if (scene != NULL) { + scene->flag &= ~SCE_NLA_EDIT_ON; + } + } + else { + /* Unlink normally - Setting it to NULL should be enough to get the old one unlinked */ + if (area->spacetype == SPACE_ACTION) { + /* clear action editor -> action */ + actedit_change_action(C, NULL); + } + else { + /* clear AnimData -> action */ + PointerRNA ptr; + PropertyRNA *prop; + + /* create AnimData RNA pointers */ + RNA_pointer_create(id, &RNA_AnimData, adt, &ptr); + prop = RNA_struct_find_property(&ptr, "action"); + + /* clear... */ + RNA_property_pointer_set(&ptr, prop, PointerRNA_NULL, NULL); + RNA_property_update(C, &ptr, prop); + } + } +} + +/* -------------------------- */ + +static bool action_unlink_poll(bContext *C) +{ + if (ED_operator_action_active(C)) { + SpaceAction *saction = (SpaceAction *)CTX_wm_space_data(C); + AnimData *adt = ED_actedit_animdata_from_context(C, NULL); + + /* Only when there's an active action, in the right modes... */ + if (saction->action && adt) { + return true; + } + } + + /* something failed... */ + return false; +} + +static int action_unlink_exec(bContext *C, wmOperator *op) +{ + AnimData *adt = ED_actedit_animdata_from_context(C, NULL); + bool force_delete = RNA_boolean_get(op->ptr, "force_delete"); + + if (adt && adt->action) { + ED_animedit_unlink_action(C, NULL, adt, adt->action, op->reports, force_delete); + } + + /* Unlink is also abused to exit NLA tweak mode. */ + WM_main_add_notifier(NC_ANIMATION | ND_NLA_ACTCHANGE, NULL); + + return OPERATOR_FINISHED; +} + +static int action_unlink_invoke(bContext *C, wmOperator *op, const wmEvent *event) +{ + /* NOTE: this is hardcoded to match the behavior for the unlink button + * (in interface_templates.c). */ + RNA_boolean_set(op->ptr, "force_delete", event->modifier & KM_SHIFT); + return action_unlink_exec(C, op); +} + +void ACTION_OT_unlink(wmOperatorType *ot) +{ + PropertyRNA *prop; + + /* identifiers */ + ot->name = "Unlink Action"; + ot->idname = "ACTION_OT_unlink"; + ot->description = "Unlink this action from the active action slot (and/or exit Tweak Mode)"; + + /* callbacks */ + ot->invoke = action_unlink_invoke; + ot->exec = action_unlink_exec; + ot->poll = action_unlink_poll; + + /* properties */ + prop = RNA_def_boolean(ot->srna, + "force_delete", + false, + "Force Delete", + "Clear Fake User and remove " + "copy stashed in this data-block's NLA stack"); + RNA_def_property_flag(prop, PROP_SKIP_SAVE); + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; +} + +/* ************************************************************************** */ +/* ACTION BROWSING */ + +/* Try to find NLA Strip to use for action layer up/down tool */ +static NlaStrip *action_layer_get_nlastrip(ListBase *strips, float ctime) +{ + NlaStrip *strip; + + for (strip = strips->first; strip; strip = strip->next) { + /* Can we use this? */ + if (IN_RANGE_INCL(ctime, strip->start, strip->end)) { + /* in range - use this one */ + return strip; + } + if ((ctime < strip->start) && (strip->prev == NULL)) { + /* before first - use this one */ + return strip; + } + if ((ctime > strip->end) && (strip->next == NULL)) { + /* after last - use this one */ + return strip; + } + } + + /* nothing suitable found... */ + return NULL; +} + +/* Switch NLA Strips/Actions. */ +static void action_layer_switch_strip( + AnimData *adt, NlaTrack *old_track, NlaStrip *old_strip, NlaTrack *nlt, NlaStrip *strip) +{ + /* Exit tweak-mode on old strip + * NOTE: We need to manually clear this stuff ourselves, as tweak-mode exit doesn't do it + */ + BKE_nla_tweakmode_exit(adt); + + if (old_strip) { + old_strip->flag &= ~(NLASTRIP_FLAG_ACTIVE | NLASTRIP_FLAG_SELECT); + } + if (old_track) { + old_track->flag &= ~(NLATRACK_ACTIVE | NLATRACK_SELECTED); + } + + /* Make this one the active one instead */ + strip->flag |= (NLASTRIP_FLAG_ACTIVE | NLASTRIP_FLAG_SELECT); + nlt->flag |= NLATRACK_ACTIVE; + + /* Copy over "solo" flag - This is useful for stashed actions... */ + if (old_track) { + if (old_track->flag & NLATRACK_SOLO) { + old_track->flag &= ~NLATRACK_SOLO; + nlt->flag |= NLATRACK_SOLO; + } + } + else { + /* NLA muting <==> Solo Tracks */ + if (adt->flag & ADT_NLA_EVAL_OFF) { + /* disable NLA muting */ + adt->flag &= ~ADT_NLA_EVAL_OFF; + + /* mark this track as being solo */ + adt->flag |= ADT_NLA_SOLO_TRACK; + nlt->flag |= NLATRACK_SOLO; + + /* TODO: Needs rest-pose flushing (when we get reference track) */ + } + } + + /* Enter tweak-mode again - hopefully we're now "it" */ + BKE_nla_tweakmode_enter(adt); + BLI_assert(adt->actstrip == strip); +} + +/* ********************** One Layer Up Operator ************************** */ + +static bool action_layer_next_poll(bContext *C) +{ + /* Action Editor's action editing modes only */ + if (ED_operator_action_active(C)) { + AnimData *adt = ED_actedit_animdata_from_context(C, NULL); + if (adt) { + /* only allow if we're in tweak-mode, and there's something above us... */ + if (adt->flag & ADT_NLA_EDIT_ON) { + /* We need to check if there are any tracks above the active one + * since the track the action comes from is not stored in AnimData + */ + if (adt->nla_tracks.last) { + NlaTrack *nlt = (NlaTrack *)adt->nla_tracks.last; + + if (nlt->flag & NLATRACK_DISABLED) { + /* A disabled track will either be the track itself, + * or one of the ones above it. + * + * If this is the top-most one, there is the possibility + * that there is no active action. For now, we let this + * case return true too, so that there is a natural way + * to "move to an empty layer", even though this means + * that we won't actually have an action. + */ + // return (adt->tmpact != NULL); + return true; + } + } + } + } + } + + /* something failed... */ + return false; +} + +static int action_layer_next_exec(bContext *C, wmOperator *op) +{ + AnimData *adt = ED_actedit_animdata_from_context(C, NULL); + NlaTrack *act_track; + + Scene *scene = CTX_data_scene(C); + float ctime = BKE_scene_ctime_get(scene); + + /* Get active track */ + act_track = BKE_nlatrack_find_tweaked(adt); + + if (act_track == NULL) { + BKE_report(op->reports, RPT_ERROR, "Could not find current NLA Track"); + return OPERATOR_CANCELLED; + } + + /* Find next action, and hook it up */ + if (act_track->next) { + NlaTrack *nlt; + + /* Find next action to use */ + for (nlt = act_track->next; nlt; nlt = nlt->next) { + NlaStrip *strip = action_layer_get_nlastrip(&nlt->strips, ctime); + + if (strip) { + action_layer_switch_strip(adt, act_track, adt->actstrip, nlt, strip); + break; + } + } + } + else { + /* No more actions (strips) - Go back to editing the original active action + * NOTE: This will mean exiting tweak-mode... + */ + BKE_nla_tweakmode_exit(adt); + + /* Deal with solo flags... + * Assume: Solo Track == NLA Muting + */ + if (adt->flag & ADT_NLA_SOLO_TRACK) { + /* turn off solo flags on tracks */ + act_track->flag &= ~NLATRACK_SOLO; + adt->flag &= ~ADT_NLA_SOLO_TRACK; + + /* turn on NLA muting (to keep same effect) */ + adt->flag |= ADT_NLA_EVAL_OFF; + + /* TODO: Needs rest-pose flushing (when we get reference track) */ + } + } + + /* Update the action that this editor now uses + * NOTE: The calls above have already handled the user-count/anim-data side of things. */ + actedit_change_action(C, adt->action); + return OPERATOR_FINISHED; +} + +void ACTION_OT_layer_next(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Next Layer"; + ot->idname = "ACTION_OT_layer_next"; + ot->description = + "Switch to editing action in animation layer above the current action in the NLA Stack"; + + /* callbacks */ + ot->exec = action_layer_next_exec; + ot->poll = action_layer_next_poll; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; +} + +/* ********************* One Layer Down Operator ************************* */ + +static bool action_layer_prev_poll(bContext *C) +{ + /* Action Editor's action editing modes only */ + if (ED_operator_action_active(C)) { + AnimData *adt = ED_actedit_animdata_from_context(C, NULL); + if (adt) { + if (adt->flag & ADT_NLA_EDIT_ON) { + /* Tweak Mode: We need to check if there are any tracks below the active one + * that we can move to */ + if (adt->nla_tracks.first) { + NlaTrack *nlt = (NlaTrack *)adt->nla_tracks.first; + + /* Since the first disabled track is the track being tweaked/edited, + * we can simplify things by only checking the first track: + * - If it is disabled, this is the track being tweaked, + * so there can't be anything below it + * - Otherwise, there is at least 1 track below the tweaking + * track that we can descend to + */ + if ((nlt->flag & NLATRACK_DISABLED) == 0) { + /* not disabled = there are actions below the one being tweaked */ + return true; + } + } + } + else { + /* Normal Mode: If there are any tracks, we can try moving to those */ + return (adt->nla_tracks.first != NULL); + } + } + } + + /* something failed... */ + return false; +} + +static int action_layer_prev_exec(bContext *C, wmOperator *op) +{ + AnimData *adt = ED_actedit_animdata_from_context(C, NULL); + NlaTrack *act_track; + NlaTrack *nlt; + + Scene *scene = CTX_data_scene(C); + float ctime = BKE_scene_ctime_get(scene); + + /* Sanity Check */ + if (adt == NULL) { + BKE_report( + op->reports, RPT_ERROR, "Internal Error: Could not find Animation Data/NLA Stack to use"); + return OPERATOR_CANCELLED; + } + + /* Get active track */ + act_track = BKE_nlatrack_find_tweaked(adt); + + /* If there is no active track, that means we are using the active action... */ + if (act_track) { + /* Active Track - Start from the one below it */ + nlt = act_track->prev; + } + else { + /* Active Action - Use the top-most track */ + nlt = adt->nla_tracks.last; + } + + /* Find previous action and hook it up */ + for (; nlt; nlt = nlt->prev) { + NlaStrip *strip = action_layer_get_nlastrip(&nlt->strips, ctime); + + if (strip) { + action_layer_switch_strip(adt, act_track, adt->actstrip, nlt, strip); + break; + } + } + + /* Update the action that this editor now uses + * NOTE: The calls above have already handled the user-count/animdata side of things. */ + actedit_change_action(C, adt->action); + return OPERATOR_FINISHED; +} + +void ACTION_OT_layer_prev(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Previous Layer"; + ot->idname = "ACTION_OT_layer_prev"; + ot->description = + "Switch to editing action in animation layer below the current action in the NLA Stack"; + + /* callbacks */ + ot->exec = action_layer_prev_exec; + ot->poll = action_layer_prev_poll; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; +} + +/* ************************************************************************** */ diff --git a/source/blender/editors/space_action/action_data.c.rej b/source/blender/editors/space_action/action_data.c.rej new file mode 100644 index 00000000000..95c5b360d41 --- /dev/null +++ b/source/blender/editors/space_action/action_data.c.rej @@ -0,0 +1,25 @@ +*************** +*** 576,586 **** + + if (strip->act == act) { + /* Remove this strip, and the track too if it doesn't have anything else */ +- BKE_nlastrip_free(&nlt->strips, strip, true); + + if (nlt->strips.first == NULL) { + BLI_assert(nstrip == NULL); +- BKE_nlatrack_free(&adt->nla_tracks, nlt, true); + } + } + } +--- 576,586 ---- + + if (strip->act == act) { + /* Remove this strip, and the track too if it doesn't have anything else */ ++ BKE_nlastrip_remove_and_free(&nlt->strips, strip, true); + + if (nlt->strips.first == NULL) { + BLI_assert(nstrip == NULL); ++ BKE_nlatrack_remove_and_free(&adt->nla_tracks, nlt, true); + } + } + } diff --git a/source/blender/editors/space_nla/nla_channels.c b/source/blender/editors/space_nla/nla_channels.c index a783ee32779..2786cd7e187 100644 --- a/source/blender/editors/space_nla/nla_channels.c +++ b/source/blender/editors/space_nla/nla_channels.c @@ -581,14 +581,14 @@ bool nlaedit_add_tracks_existing(bAnimContext *ac, bool above_sel) */ if (above_sel) { /* just add a new one above this one */ - BKE_nlatrack_add(adt, nlt, is_liboverride); + BKE_nlatrack_new_after_and_set_active(&adt->nla_tracks, nlt, is_liboverride); ale->update = ANIM_UPDATE_DEPS; added = true; } else if ((lastAdt == NULL) || (adt != lastAdt)) { /* add one track to the top of the owning AnimData's stack, * then don't add anymore to this stack */ - BKE_nlatrack_add(adt, NULL, is_liboverride); + BKE_nlatrack_new_tail_and_set_active(&adt->nla_tracks, is_liboverride); lastAdt = adt; ale->update = ANIM_UPDATE_DEPS; added = true; @@ -625,7 +625,7 @@ bool nlaedit_add_tracks_empty(bAnimContext *ac) /* ensure it is empty */ if (BLI_listbase_is_empty(&adt->nla_tracks)) { /* add new track to this AnimData block then */ - BKE_nlatrack_add(adt, NULL, ID_IS_OVERRIDE_LIBRARY(ale->id)); + BKE_nlatrack_new_tail_and_set_active(&adt->nla_tracks, ID_IS_OVERRIDE_LIBRARY(ale->id)); ale->update = ANIM_UPDATE_DEPS; added = true; } @@ -736,7 +736,7 @@ static int nlaedit_delete_tracks_exec(bContext *C, wmOperator *UNUSED(op)) } /* call delete on this track - deletes all strips too */ - BKE_nlatrack_free(&adt->nla_tracks, nlt, true); + BKE_nlatrack_remove_and_free(&adt->nla_tracks, nlt, true); ale->update = ANIM_UPDATE_DEPS; } } diff --git a/source/blender/editors/space_nla/nla_channels.c.orig b/source/blender/editors/space_nla/nla_channels.c.orig new file mode 100644 index 00000000000..a783ee32779 --- /dev/null +++ b/source/blender/editors/space_nla/nla_channels.c.orig @@ -0,0 +1,828 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later + * Copyright 2009 Blender Foundation, Joshua Leung. All rights reserved. */ + +/** \file + * \ingroup spnla + */ + +#include +#include +#include +#include + +#include "DNA_anim_types.h" +#include "DNA_object_types.h" +#include "DNA_scene_types.h" + +#include "BLI_blenlib.h" +#include "BLI_utildefines.h" + +#include "BKE_anim_data.h" +#include "BKE_context.h" +#include "BKE_global.h" +#include "BKE_layer.h" +#include "BKE_nla.h" +#include "BKE_report.h" +#include "BKE_scene.h" +#include "BKE_screen.h" + +#include "ED_anim_api.h" +#include "ED_keyframes_edit.h" +#include "ED_object.h" +#include "ED_screen.h" + +#include "RNA_access.h" +#include "RNA_define.h" + +#include "WM_api.h" +#include "WM_types.h" + +#include "UI_interface.h" + +#include "DEG_depsgraph.h" +#include "DEG_depsgraph_build.h" + +#include "UI_view2d.h" + +#include "nla_intern.h" /* own include */ + +/* *********************************************** */ +/* Operators for NLA channels-list which need to be different + * from the standard Animation Editor ones */ + +/* ******************** Mouse-Click Operator *********************** */ +/* Depending on the channel that was clicked on, the mouse click will activate whichever + * part of the channel is relevant. + * + * NOTE: eventually, + * this should probably be phased out when many of these things are replaced with buttons + * --> Most channels are now selection only. + */ + +static int mouse_nla_channels(bContext *C, bAnimContext *ac, int channel_index, short selectmode) +{ + ListBase anim_data = {NULL, NULL}; + bAnimListElem *ale; + int filter; + + int notifierFlags = 0; + + /* get the channel that was clicked on */ + /* filter channels */ + filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE | ANIMFILTER_LIST_CHANNELS | + ANIMFILTER_FCURVESONLY); + ANIM_animdata_filter(ac, &anim_data, filter, ac->data, ac->datatype); + + /* get channel from index */ + ale = BLI_findlink(&anim_data, channel_index); + if (ale == NULL) { + /* channel not found */ + if (G.debug & G_DEBUG) { + printf("Error: animation channel (index = %d) not found in mouse_anim_channels()\n", + channel_index); + } + + ANIM_animdata_freelist(&anim_data); + return 0; + } + + /* action to take depends on what channel we've got */ + /* WARNING: must keep this in sync with the equivalent function in anim_channels_edit.c */ + switch (ale->type) { + case ANIMTYPE_SCENE: { + Scene *sce = (Scene *)ale->data; + AnimData *adt = sce->adt; + + /* set selection status */ + if (selectmode == SELECT_INVERT) { + /* swap select */ + sce->flag ^= SCE_DS_SELECTED; + if (adt) { + adt->flag ^= ADT_UI_SELECTED; + } + } + else { + sce->flag |= SCE_DS_SELECTED; + if (adt) { + adt->flag |= ADT_UI_SELECTED; + } + } + + notifierFlags |= (ND_ANIMCHAN | NA_SELECTED); + break; + } + case ANIMTYPE_OBJECT: { + ViewLayer *view_layer = ac->view_layer; + Base *base = (Base *)ale->data; + Object *ob = base->object; + AnimData *adt = ob->adt; + + if (nlaedit_is_tweakmode_on(ac) == 0 && (base->flag & BASE_SELECTABLE)) { + /* set selection status */ + if (selectmode == SELECT_INVERT) { + /* swap select */ + ED_object_base_select(base, BA_INVERT); + + if (adt) { + adt->flag ^= ADT_UI_SELECTED; + } + } + else { + /* deselect all */ + /* TODO: should this deselect all other types of channels too? */ + BKE_view_layer_synced_ensure(ac->scene, view_layer); + LISTBASE_FOREACH (Base *, b, BKE_view_layer_object_bases_get(view_layer)) { + ED_object_base_select(b, BA_DESELECT); + if (b->object->adt) { + b->object->adt->flag &= ~(ADT_UI_SELECTED | ADT_UI_ACTIVE); + } + } + + /* select object now */ + ED_object_base_select(base, BA_SELECT); + if (adt) { + adt->flag |= ADT_UI_SELECTED; + } + } + + /* change active object - regardless of whether it is now selected [#37883] */ + ED_object_base_activate_with_mode_exit_if_needed(C, base); /* adds notifier */ + + if ((adt) && (adt->flag & ADT_UI_SELECTED)) { + adt->flag |= ADT_UI_ACTIVE; + } + + /* notifiers - channel was selected */ + notifierFlags |= (ND_ANIMCHAN | NA_SELECTED); + } + break; + } + case ANIMTYPE_FILLACTD: /* Action Expander */ + case ANIMTYPE_DSMAT: /* Datablock AnimData Expanders */ + case ANIMTYPE_DSLAM: + case ANIMTYPE_DSCAM: + case ANIMTYPE_DSCACHEFILE: + case ANIMTYPE_DSCUR: + case ANIMTYPE_DSSKEY: + case ANIMTYPE_DSWOR: + case ANIMTYPE_DSNTREE: + case ANIMTYPE_DSPART: + case ANIMTYPE_DSMBALL: + case ANIMTYPE_DSARM: + case ANIMTYPE_DSMESH: + case ANIMTYPE_DSTEX: + case ANIMTYPE_DSLAT: + case ANIMTYPE_DSLINESTYLE: + case ANIMTYPE_DSSPK: + case ANIMTYPE_DSGPENCIL: + case ANIMTYPE_PALETTE: + case ANIMTYPE_DSHAIR: + case ANIMTYPE_DSPOINTCLOUD: + case ANIMTYPE_DSVOLUME: + case ANIMTYPE_DSSIMULATION: { + /* sanity checking... */ + if (ale->adt) { + /* select/deselect */ + if (selectmode == SELECT_INVERT) { + /* inverse selection status of this AnimData block only */ + ale->adt->flag ^= ADT_UI_SELECTED; + } + else { + /* select AnimData block by itself */ + ANIM_anim_channels_select_set(ac, ACHANNEL_SETFLAG_CLEAR); + ale->adt->flag |= ADT_UI_SELECTED; + } + + /* set active? */ + if ((ale->adt) && (ale->adt->flag & ADT_UI_SELECTED)) { + ale->adt->flag |= ADT_UI_ACTIVE; + } + } + + notifierFlags |= (ND_ANIMCHAN | NA_SELECTED); + break; + } + case ANIMTYPE_NLATRACK: { + NlaTrack *nlt = (NlaTrack *)ale->data; + + if (nlaedit_is_tweakmode_on(ac) == 0) { + /* set selection */ + if (selectmode == SELECT_INVERT) { + /* inverse selection status of this F-Curve only */ + nlt->flag ^= NLATRACK_SELECTED; + } + else { + /* select F-Curve by itself */ + ANIM_anim_channels_select_set(ac, ACHANNEL_SETFLAG_CLEAR); + nlt->flag |= NLATRACK_SELECTED; + } + + /* if NLA-Track is selected now, + * make NLA-Track the 'active' one in the visible list */ + if (nlt->flag & NLATRACK_SELECTED) { + ANIM_set_active_channel(ac, ac->data, ac->datatype, filter, nlt, ANIMTYPE_NLATRACK); + } + + /* notifier flags - channel was selected */ + notifierFlags |= (ND_ANIMCHAN | NA_SELECTED); + } + break; + } + case ANIMTYPE_NLAACTION: { + AnimData *adt = BKE_animdata_from_id(ale->id); + + /* NOTE: rest of NLA-Action name doubles for operating on the AnimData block + * - this is useful when there's no clear divider, and makes more sense in + * the case of users trying to use this to change actions + * - in tweak-mode, clicking here gets us out of tweak-mode, as changing selection + * while in tweak-mode is really evil! + * - we disable "solo" flags too, to make it easier to work with stashed actions + * with less trouble + */ + if (nlaedit_is_tweakmode_on(ac)) { + /* Exit tweak-mode immediately. */ + nlaedit_disable_tweakmode(ac, true); + + /* changes to NLA-Action occurred */ + notifierFlags |= ND_NLA_ACTCHANGE; + ale->update |= ANIM_UPDATE_DEPS; + } + else { + /* select/deselect */ + if (selectmode == SELECT_INVERT) { + /* inverse selection status of this AnimData block only */ + adt->flag ^= ADT_UI_SELECTED; + } + else { + /* select AnimData block by itself */ + ANIM_anim_channels_select_set(ac, ACHANNEL_SETFLAG_CLEAR); + adt->flag |= ADT_UI_SELECTED; + } + + /* set active? */ + if (adt->flag & ADT_UI_SELECTED) { + adt->flag |= ADT_UI_ACTIVE; + } + + notifierFlags |= (ND_ANIMCHAN | NA_SELECTED); + } + break; + } + default: + if (G.debug & G_DEBUG) { + printf("Error: Invalid channel type in mouse_nla_channels()\n"); + } + break; + } + + /* free channels */ + ANIM_animdata_update(ac, &anim_data); + ANIM_animdata_freelist(&anim_data); + + /* return the notifier-flags set */ + return notifierFlags; +} + +/* ------------------- */ + +/* handle clicking */ +static int nlachannels_mouseclick_invoke(bContext *C, wmOperator *op, const wmEvent *event) +{ + bAnimContext ac; + SpaceNla *snla; + ARegion *region; + View2D *v2d; + int channel_index; + int notifierFlags = 0; + short selectmode; + float x, y; + + /* get editor data */ + if (ANIM_animdata_get_context(C, &ac) == 0) { + return OPERATOR_CANCELLED; + } + + /* get useful pointers from animation context data */ + snla = (SpaceNla *)ac.sl; + region = ac.region; + v2d = ®ion->v2d; + + /* select mode is either replace (deselect all, then add) or add/extend */ + if (RNA_boolean_get(op->ptr, "extend")) { + selectmode = SELECT_INVERT; + } + else { + selectmode = SELECT_REPLACE; + } + + /* Figure out which channel user clicked in. */ + UI_view2d_region_to_view(v2d, event->mval[0], event->mval[1], &x, &y); + UI_view2d_listview_view_to_cell(NLACHANNEL_NAMEWIDTH, + NLACHANNEL_STEP(snla), + 0, + NLACHANNEL_FIRST_TOP(&ac), + x, + y, + NULL, + &channel_index); + + /* handle mouse-click in the relevant channel then */ + notifierFlags = mouse_nla_channels(C, &ac, channel_index, selectmode); + + /* set notifier that things have changed */ + WM_event_add_notifier(C, NC_ANIMATION | notifierFlags, NULL); + + return OPERATOR_FINISHED; +} + +void NLA_OT_channels_click(wmOperatorType *ot) +{ + PropertyRNA *prop; + + /* identifiers */ + ot->name = "Mouse Click on NLA Channels"; + ot->idname = "NLA_OT_channels_click"; + ot->description = "Handle clicks to select NLA channels"; + + /* api callbacks */ + ot->invoke = nlachannels_mouseclick_invoke; + ot->poll = ED_operator_nla_active; + + /* flags */ + ot->flag = OPTYPE_UNDO; + + /* props */ + prop = RNA_def_boolean(ot->srna, "extend", 0, "Extend Select", ""); /* SHIFTKEY */ + RNA_def_property_flag(prop, PROP_SKIP_SAVE); +} + +/* *********************************************** */ +/* Special Operators */ + +/* ******************** Action Push Down ******************************** */ + +static int nlachannels_pushdown_exec(bContext *C, wmOperator *op) +{ + bAnimContext ac; + ID *id = NULL; + AnimData *adt = NULL; + int channel_index = RNA_int_get(op->ptr, "channel_index"); + + /* get editor data */ + if (ANIM_animdata_get_context(C, &ac) == 0) { + return OPERATOR_CANCELLED; + } + + /* get anim-channel to use (or more specifically, the animdata block behind it) */ + if (channel_index == -1) { + PointerRNA adt_ptr = {NULL}; + + /* active animdata block */ + if (nla_panel_context(C, &adt_ptr, NULL, NULL) == 0 || (adt_ptr.data == NULL)) { + BKE_report(op->reports, + RPT_ERROR, + "No active AnimData block to use " + "(select a data-block expander first or set the appropriate flags on an AnimData " + "block)"); + return OPERATOR_CANCELLED; + } + + id = adt_ptr.owner_id; + adt = adt_ptr.data; + } + else { + /* indexed channel */ + ListBase anim_data = {NULL, NULL}; + bAnimListElem *ale; + int filter; + + /* filter channels */ + filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE | ANIMFILTER_LIST_CHANNELS | + ANIMFILTER_FCURVESONLY); + ANIM_animdata_filter(&ac, &anim_data, filter, ac.data, ac.datatype); + + /* get channel from index */ + ale = BLI_findlink(&anim_data, channel_index); + if (ale == NULL) { + BKE_reportf(op->reports, RPT_ERROR, "No animation channel found at index %d", channel_index); + ANIM_animdata_freelist(&anim_data); + return OPERATOR_CANCELLED; + } + if (ale->type != ANIMTYPE_NLAACTION) { + BKE_reportf(op->reports, + RPT_ERROR, + "Animation channel at index %d is not a NLA 'Active Action' channel", + channel_index); + ANIM_animdata_freelist(&anim_data); + return OPERATOR_CANCELLED; + } + + /* grab AnimData from the channel */ + adt = ale->adt; + id = ale->id; + + /* we don't need anything here anymore, so free it all */ + ANIM_animdata_freelist(&anim_data); + } + + /* double-check that we are free to push down here... */ + if (adt == NULL) { + BKE_report(op->reports, RPT_WARNING, "Internal Error - AnimData block is not valid"); + return OPERATOR_CANCELLED; + } + if (nlaedit_is_tweakmode_on(&ac)) { + BKE_report(op->reports, + RPT_WARNING, + "Cannot push down actions while tweaking a strip's action, exit tweak mode first"); + return OPERATOR_CANCELLED; + } + if (adt->action == NULL) { + BKE_report(op->reports, RPT_WARNING, "No active action to push down"); + return OPERATOR_CANCELLED; + } + + /* 'push-down' action - only usable when not in Tweak-mode. */ + BKE_nla_action_pushdown(adt, ID_IS_OVERRIDE_LIBRARY(id)); + + struct Main *bmain = CTX_data_main(C); + DEG_id_tag_update_ex(bmain, id, ID_RECALC_ANIMATION); + + /* The action needs updating too, as FCurve modifiers are to be reevaluated. They won't extend + * beyond the NLA strip after pushing down to the NLA. */ + DEG_id_tag_update_ex(bmain, &adt->action->id, ID_RECALC_ANIMATION); + + /* set notifier that things have changed */ + WM_event_add_notifier(C, NC_ANIMATION | ND_NLA_ACTCHANGE, NULL); + return OPERATOR_FINISHED; +} + +void NLA_OT_action_pushdown(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Push Down Action"; + ot->idname = "NLA_OT_action_pushdown"; + ot->description = "Push action down onto the top of the NLA stack as a new strip"; + + /* callbacks */ + ot->exec = nlachannels_pushdown_exec; + ot->poll = nlaop_poll_tweakmode_off; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + /* properties */ + ot->prop = RNA_def_int(ot->srna, + "channel_index", + -1, + -1, + INT_MAX, + "Channel Index", + "Index of NLA action channel to perform pushdown operation on", + 0, + INT_MAX); + RNA_def_property_flag(ot->prop, PROP_SKIP_SAVE | PROP_HIDDEN); +} + +/* ******************** Action Unlink ******************************** */ + +static bool nla_action_unlink_poll(bContext *C) +{ + if (ED_operator_nla_active(C)) { + PointerRNA adt_ptr; + return (nla_panel_context(C, &adt_ptr, NULL, NULL) && (adt_ptr.data != NULL)); + } + + /* something failed... */ + return false; +} + +static int nla_action_unlink_exec(bContext *C, wmOperator *op) +{ + PointerRNA adt_ptr; + AnimData *adt; + + /* check context and also validity of pointer */ + if (!nla_panel_context(C, &adt_ptr, NULL, NULL)) { + return OPERATOR_CANCELLED; + } + + /* get animdata */ + adt = adt_ptr.data; + if (adt == NULL) { + return OPERATOR_CANCELLED; + } + + /* do unlinking */ + if (adt->action) { + bool force_delete = RNA_boolean_get(op->ptr, "force_delete"); + ED_animedit_unlink_action(C, adt_ptr.owner_id, adt, adt->action, op->reports, force_delete); + } + + return OPERATOR_FINISHED; +} + +static int nla_action_unlink_invoke(bContext *C, wmOperator *op, const wmEvent *event) +{ + /* NOTE: this is hardcoded to match the behavior for the unlink button + * (in interface_templates.c) */ + RNA_boolean_set(op->ptr, "force_delete", event->modifier & KM_SHIFT); + return nla_action_unlink_exec(C, op); +} + +void NLA_OT_action_unlink(wmOperatorType *ot) +{ + PropertyRNA *prop; + + /* identifiers */ + ot->name = "Unlink Action"; + ot->idname = "NLA_OT_action_unlink"; + ot->description = "Unlink this action from the active action slot (and/or exit Tweak Mode)"; + + /* callbacks */ + ot->invoke = nla_action_unlink_invoke; + ot->exec = nla_action_unlink_exec; + ot->poll = nla_action_unlink_poll; + + /* properties */ + prop = RNA_def_boolean(ot->srna, + "force_delete", + false, + "Force Delete", + "Clear Fake User and remove copy stashed in this data-block's NLA stack"); + RNA_def_property_flag(prop, PROP_SKIP_SAVE); +} + +/* ******************** Add Tracks Operator ***************************** */ +/* Add NLA Tracks to the same AnimData block as a selected track, or above the selected tracks */ + +bool nlaedit_add_tracks_existing(bAnimContext *ac, bool above_sel) +{ + ListBase anim_data = {NULL, NULL}; + bAnimListElem *ale; + int filter; + AnimData *lastAdt = NULL; + bool added = false; + + /* get a list of the (selected) NLA Tracks being shown in the NLA */ + filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE | ANIMFILTER_SEL | + ANIMFILTER_NODUPLIS | ANIMFILTER_FCURVESONLY); + ANIM_animdata_filter(ac, &anim_data, filter, ac->data, ac->datatype); + + /* add tracks... */ + for (ale = anim_data.first; ale; ale = ale->next) { + if (ale->type == ANIMTYPE_NLATRACK) { + NlaTrack *nlt = (NlaTrack *)ale->data; + AnimData *adt = ale->adt; + + const bool is_liboverride = ID_IS_OVERRIDE_LIBRARY(ale->id); + + /* check if just adding a new track above this one, + * or whether we're adding a new one to the top of the stack that this one belongs to + */ + if (above_sel) { + /* just add a new one above this one */ + BKE_nlatrack_add(adt, nlt, is_liboverride); + ale->update = ANIM_UPDATE_DEPS; + added = true; + } + else if ((lastAdt == NULL) || (adt != lastAdt)) { + /* add one track to the top of the owning AnimData's stack, + * then don't add anymore to this stack */ + BKE_nlatrack_add(adt, NULL, is_liboverride); + lastAdt = adt; + ale->update = ANIM_UPDATE_DEPS; + added = true; + } + } + } + + /* free temp data */ + ANIM_animdata_update(ac, &anim_data); + ANIM_animdata_freelist(&anim_data); + + return added; +} + +bool nlaedit_add_tracks_empty(bAnimContext *ac) +{ + ListBase anim_data = {NULL, NULL}; + bAnimListElem *ale; + int filter; + bool added = false; + + /* get a list of the selected AnimData blocks in the NLA */ + filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE | ANIMFILTER_ANIMDATA | + ANIMFILTER_SEL | ANIMFILTER_NODUPLIS | ANIMFILTER_FCURVESONLY); + ANIM_animdata_filter(ac, &anim_data, filter, ac->data, ac->datatype); + + /* check if selected AnimData blocks are empty, and add tracks if so... */ + for (ale = anim_data.first; ale; ale = ale->next) { + AnimData *adt = ale->adt; + + /* sanity check */ + BLI_assert(adt->flag & ADT_UI_SELECTED); + + /* ensure it is empty */ + if (BLI_listbase_is_empty(&adt->nla_tracks)) { + /* add new track to this AnimData block then */ + BKE_nlatrack_add(adt, NULL, ID_IS_OVERRIDE_LIBRARY(ale->id)); + ale->update = ANIM_UPDATE_DEPS; + added = true; + } + } + + /* cleanup */ + ANIM_animdata_update(ac, &anim_data); + ANIM_animdata_freelist(&anim_data); + + return added; +} + +/* ----- */ + +static int nlaedit_add_tracks_exec(bContext *C, wmOperator *op) +{ + bAnimContext ac; + bool above_sel = RNA_boolean_get(op->ptr, "above_selected"); + bool op_done = false; + + /* get editor data */ + if (ANIM_animdata_get_context(C, &ac) == 0) { + return OPERATOR_CANCELLED; + } + + /* perform adding in two passes - existing first so that we don't double up for empty */ + op_done |= nlaedit_add_tracks_existing(&ac, above_sel); + op_done |= nlaedit_add_tracks_empty(&ac); + + /* done? */ + if (op_done) { + DEG_relations_tag_update(CTX_data_main(C)); + + /* set notifier that things have changed */ + WM_event_add_notifier(C, NC_ANIMATION | ND_NLA | NA_ADDED, NULL); + + /* done */ + return OPERATOR_FINISHED; + } + + /* failed to add any tracks */ + BKE_report( + op->reports, RPT_WARNING, "Select an existing NLA Track or an empty action line first"); + + /* not done */ + return OPERATOR_CANCELLED; +} + +void NLA_OT_tracks_add(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Add Tracks"; + ot->idname = "NLA_OT_tracks_add"; + ot->description = "Add NLA-Tracks above/after the selected tracks"; + + /* api callbacks */ + ot->exec = nlaedit_add_tracks_exec; + ot->poll = nlaop_poll_tweakmode_off; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + /* properties */ + RNA_def_boolean(ot->srna, + "above_selected", + 0, + "Above Selected", + "Add a new NLA Track above every existing selected one"); +} + +/* ******************** Delete Tracks Operator ***************************** */ +/* Delete selected NLA Tracks */ + +static int nlaedit_delete_tracks_exec(bContext *C, wmOperator *UNUSED(op)) +{ + bAnimContext ac; + + ListBase anim_data = {NULL, NULL}; + bAnimListElem *ale; + int filter; + + /* get editor data */ + if (ANIM_animdata_get_context(C, &ac) == 0) { + return OPERATOR_CANCELLED; + } + + /* get a list of the AnimData blocks being shown in the NLA */ + filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE | ANIMFILTER_SEL | + ANIMFILTER_NODUPLIS | ANIMFILTER_FCURVESONLY); + ANIM_animdata_filter(&ac, &anim_data, filter, ac.data, ac.datatype); + + /* delete tracks */ + for (ale = anim_data.first; ale; ale = ale->next) { + if (ale->type == ANIMTYPE_NLATRACK) { + NlaTrack *nlt = (NlaTrack *)ale->data; + AnimData *adt = ale->adt; + + if (BKE_nlatrack_is_nonlocal_in_liboverride(ale->id, nlt)) { + /* No deletion of non-local tracks of override data. */ + continue; + } + + /* if track is currently 'solo', then AnimData should have its + * 'has solo' flag disabled + */ + if (nlt->flag & NLATRACK_SOLO) { + adt->flag &= ~ADT_NLA_SOLO_TRACK; + } + + /* call delete on this track - deletes all strips too */ + BKE_nlatrack_free(&adt->nla_tracks, nlt, true); + ale->update = ANIM_UPDATE_DEPS; + } + } + + /* free temp data */ + ANIM_animdata_update(&ac, &anim_data); + ANIM_animdata_freelist(&anim_data); + + DEG_relations_tag_update(ac.bmain); + + /* set notifier that things have changed */ + WM_event_add_notifier(C, NC_ANIMATION | ND_NLA | NA_REMOVED, NULL); + + /* done */ + return OPERATOR_FINISHED; +} + +void NLA_OT_tracks_delete(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Delete Tracks"; + ot->idname = "NLA_OT_tracks_delete"; + ot->description = "Delete selected NLA-Tracks and the strips they contain"; + + /* api callbacks */ + ot->exec = nlaedit_delete_tracks_exec; + ot->poll = nlaop_poll_tweakmode_off; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; +} + +/* *********************************************** */ +/* AnimData Related Operators */ + +/* ******************** Include Objects Operator ***************************** */ +/* Include selected objects in NLA Editor, by giving them AnimData blocks + * NOTE: This doesn't help for non-object AnimData, where we do not have any effective + * selection mechanism in place. Unfortunately, this means that non-object AnimData + * once again becomes a second-class citizen here. However, at least for the most + * common use case, we now have a nice shortcut again. + */ + +static int nlaedit_objects_add_exec(bContext *C, wmOperator *UNUSED(op)) +{ + bAnimContext ac; + SpaceNla *snla; + + /* get editor data */ + if (ANIM_animdata_get_context(C, &ac) == 0) { + return OPERATOR_CANCELLED; + } + + /* ensure that filters are set so that the effect will be immediately visible */ + snla = (SpaceNla *)ac.sl; + if (snla && snla->ads) { + snla->ads->filterflag &= ~ADS_FILTER_NLA_NOACT; + } + + /* operate on selected objects... */ + CTX_DATA_BEGIN (C, Object *, ob, selected_objects) { + /* ensure that object has AnimData... that's all */ + BKE_animdata_ensure_id(&ob->id); + } + CTX_DATA_END; + + /* set notifier that things have changed */ + WM_event_add_notifier(C, NC_ANIMATION | ND_NLA | NA_EDITED, NULL); + + /* done */ + return OPERATOR_FINISHED; +} + +void NLA_OT_selected_objects_add(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Include Selected Objects"; + ot->idname = "NLA_OT_selected_objects_add"; + ot->description = "Make selected objects appear in NLA Editor by adding Animation Data"; + + /* api callbacks */ + ot->exec = nlaedit_objects_add_exec; + ot->poll = nlaop_poll_tweakmode_off; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; +} + +/* *********************************************** */ diff --git a/source/blender/editors/space_nla/nla_edit.c b/source/blender/editors/space_nla/nla_edit.c index b61b5d8e63b..43f071ffd78 100644 --- a/source/blender/editors/space_nla/nla_edit.c +++ b/source/blender/editors/space_nla/nla_edit.c @@ -713,12 +713,12 @@ static int nlaedit_add_actionclip_exec(bContext *C, wmOperator *op) strip->start = cfra; /* firstly try adding strip to our current track, but if that fails, add to a new track */ - if (BKE_nlatrack_add_strip(nlt, strip, is_liboverride) == 0) { + if (!BKE_nlatrack_try_add_strip(nlt, strip, is_liboverride)) { /* trying to add to the current failed (no space), * so add a new track to the stack, and add to that... */ - nlt = BKE_nlatrack_add(adt, NULL, is_liboverride); - BKE_nlatrack_add_strip(nlt, strip, is_liboverride); + nlt = BKE_nlatrack_new_tail_and_set_active(&adt->nla_tracks, is_liboverride); + BKE_nlatrack_add_strip(nlt, strip); } /* auto-name it */ @@ -951,12 +951,12 @@ static int nlaedit_add_sound_exec(bContext *C, wmOperator *UNUSED(op)) strip->end += cfra; /* firstly try adding strip to our current track, but if that fails, add to a new track */ - if (BKE_nlatrack_add_strip(nlt, strip, is_liboverride) == 0) { + if (!BKE_nlatrack_try_add_strip(nlt, strip, is_liboverride)) { /* trying to add to the current failed (no space), * so add a new track to the stack, and add to that... */ - nlt = BKE_nlatrack_add(adt, NULL, is_liboverride); - BKE_nlatrack_add_strip(nlt, strip, is_liboverride); + nlt = BKE_nlatrack_new_tail_and_set_active(&adt->nla_tracks, is_liboverride); + BKE_nlatrack_add_strip(nlt, strip); } /* auto-name it */ @@ -1190,13 +1190,9 @@ static int nlaedit_duplicate_exec(bContext *C, wmOperator *op) /* in case there's no space in the track above, * or we haven't got a reference to it yet, try adding */ - if (BKE_nlatrack_add_strip(nlt->next, nstrip, is_liboverride) == 0) { - /* need to add a new track above the one above the current one - * - if the current one is the last one, nlt->next will be NULL, which defaults to adding - * at the top of the stack anyway... - */ - track = BKE_nlatrack_add(adt, nlt->next, is_liboverride); - BKE_nlatrack_add_strip(track, nstrip, is_liboverride); + if (!BKE_nlatrack_try_add_strip(nlt->next, nstrip, is_liboverride)) { + track = BKE_nlatrack_new_after_and_set_active(&adt->nla_tracks, nlt, is_liboverride); + BKE_nlatrack_add_strip(track, nstrip); } /* deselect the original and the active flag */ @@ -1731,8 +1727,8 @@ static int nlaedit_swap_exec(bContext *C, wmOperator *op) } /* add strips back to track now */ - BKE_nlatrack_add_strip(nlt, area, is_liboverride); - BKE_nlatrack_add_strip(nlt, sb, is_liboverride); + BKE_nlatrack_try_add_strip(nlt, area, is_liboverride); + BKE_nlatrack_try_add_strip(nlt, sb, is_liboverride); } /* Clear (temp) meta-strips. */ @@ -1802,8 +1798,6 @@ static int nlaedit_move_up_exec(bContext *C, wmOperator *UNUSED(op)) NlaTrack *nltn = nlt->next; NlaStrip *strip, *stripn; - const bool is_liboverride = ID_IS_OVERRIDE_LIBRARY(ale->id); - /* if this track has no tracks after it, skip for now... */ if (nltn == NULL) { continue; @@ -1894,8 +1888,6 @@ static int nlaedit_move_down_exec(bContext *C, wmOperator *UNUSED(op)) NlaTrack *nltp = nlt->prev; NlaStrip *strip, *stripn; - const bool is_liboverride = ID_IS_OVERRIDE_LIBRARY(ale->id); - /* if this track has no tracks before it, skip for now... */ if (nltp == NULL) { continue; @@ -2457,10 +2449,10 @@ static int nlaedit_snap_exec(bContext *C, wmOperator *op) BLI_remlink(&tmp_strips, strip); /* in case there's no space in the current track, try adding */ - if (BKE_nlatrack_add_strip(nlt, strip, is_liboverride) == 0) { + if (!BKE_nlatrack_try_add_strip(nlt, strip, is_liboverride)) { /* need to add a new track above the current one */ - track = BKE_nlatrack_add(adt, nlt, is_liboverride); - BKE_nlatrack_add_strip(track, strip, is_liboverride); + track = BKE_nlatrack_new_after_and_set_active(&adt->nla_tracks, nlt, is_liboverride); + BKE_nlatrack_add_strip(track, strip); /* clear temp meta-strips on this new track, * as we may not be able to get back to it */ diff --git a/source/blender/editors/space_nla/nla_edit.c.orig b/source/blender/editors/space_nla/nla_edit.c.orig new file mode 100644 index 00000000000..b61b5d8e63b --- /dev/null +++ b/source/blender/editors/space_nla/nla_edit.c.orig @@ -0,0 +1,2850 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later + * Copyright 2009 Blender Foundation, Joshua Leung. All rights reserved. */ + +/** \file + * \ingroup spnla + */ + +#include +#include +#include + +#include "DNA_anim_types.h" +#include "DNA_object_types.h" +#include "DNA_scene_types.h" + +#include "MEM_guardedalloc.h" + +#include "BLI_blenlib.h" +#include "BLI_math.h" +#include "BLI_utildefines.h" + +#include "BLT_translation.h" + +#include "BKE_action.h" +#include "BKE_context.h" +#include "BKE_fcurve.h" +#include "BKE_lib_id.h" +#include "BKE_main.h" +#include "BKE_nla.h" +#include "BKE_report.h" +#include "BKE_screen.h" + +#include "ED_anim_api.h" +#include "ED_keyframes_edit.h" +#include "ED_markers.h" +#include "ED_screen.h" +#include "ED_transform.h" + +#include "RNA_access.h" +#include "RNA_define.h" +#include "RNA_enum_types.h" +#include "RNA_prototypes.h" + +#include "WM_api.h" +#include "WM_types.h" + +#include "DEG_depsgraph_build.h" + +#include "UI_interface.h" +#include "UI_view2d.h" + +#include "nla_intern.h" /* own include */ +#include "nla_private.h" /* FIXME: maybe this shouldn't be included? */ + +/* -------------------------------------------------------------------- */ +/** \name Public Utilities + * \{ */ + +void ED_nla_postop_refresh(bAnimContext *ac) +{ + ListBase anim_data = {NULL, NULL}; + bAnimListElem *ale; + short filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_ANIMDATA | ANIMFILTER_FOREDIT | + ANIMFILTER_FCURVESONLY); + + /* get blocks to work on */ + ANIM_animdata_filter(ac, &anim_data, filter, ac->data, ac->datatype); + + for (ale = anim_data.first; ale; ale = ale->next) { + /* performing auto-blending, extend-mode validation, etc. */ + BKE_nla_validate_state(ale->data); + + ale->update |= ANIM_UPDATE_DEPS; + } + + /* free temp memory */ + ANIM_animdata_update(ac, &anim_data); + ANIM_animdata_freelist(&anim_data); +} + +/** \} */ + +/* 'Special' Editing */ + +/* 'Tweak mode' allows the action referenced by the active NLA-strip to be edited + * as if it were the normal Active-Action of its AnimData block. + */ + +/* -------------------------------------------------------------------- */ +/** \name Enable Tweak-Mode Operator + * \{ */ + +static int nlaedit_enable_tweakmode_exec(bContext *C, wmOperator *op) +{ + bAnimContext ac; + + ListBase anim_data = {NULL, NULL}; + bAnimListElem *ale; + int filter; + + const bool do_solo = RNA_boolean_get(op->ptr, "isolate_action"); + const bool use_upper_stack_evaluation = RNA_boolean_get(op->ptr, "use_upper_stack_evaluation"); + bool ok = false; + + /* get editor data */ + if (ANIM_animdata_get_context(C, &ac) == 0) { + return OPERATOR_CANCELLED; + } + + /* get a list of the AnimData blocks being shown in the NLA */ + filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_ANIMDATA | ANIMFILTER_FCURVESONLY); + ANIM_animdata_filter(&ac, &anim_data, filter, ac.data, ac.datatype); + + /* if no blocks, popup error? */ + if (BLI_listbase_is_empty(&anim_data)) { + BKE_report(op->reports, RPT_ERROR, "No AnimData blocks to enter tweak mode for"); + return OPERATOR_CANCELLED; + } + + /* for each AnimData block with NLA-data, try setting it in tweak-mode */ + for (ale = anim_data.first; ale; ale = ale->next) { + AnimData *adt = ale->data; + + if (use_upper_stack_evaluation) { + adt->flag |= ADT_NLA_EVAL_UPPER_TRACKS; + } + else { + adt->flag &= ~ADT_NLA_EVAL_UPPER_TRACKS; + } + + /* Try entering tweak-mode if valid. */ + ok |= BKE_nla_tweakmode_enter(adt); + + /* mark the active track as being "solo"? */ + if (do_solo && adt->actstrip) { + NlaTrack *nlt = BKE_nlatrack_find_tweaked(adt); + + if (nlt && !(nlt->flag & NLATRACK_SOLO)) { + BKE_nlatrack_solo_toggle(adt, nlt); + } + } + + ale->update |= ANIM_UPDATE_DEPS; + } + + /* free temp data */ + ANIM_animdata_update(&ac, &anim_data); + ANIM_animdata_freelist(&anim_data); + + /* If we managed to enter tweak-mode on at least one AnimData block, + * set the flag for this in the active scene and send notifiers. */ + if (ac.scene && ok) { + /* set editing flag */ + ac.scene->flag |= SCE_NLA_EDIT_ON; + + /* set notifier that things have changed */ + WM_event_add_notifier(C, NC_ANIMATION | ND_NLA_ACTCHANGE, NULL); + } + else { + BKE_report(op->reports, RPT_ERROR, "No active strip(s) to enter tweak mode on"); + return OPERATOR_CANCELLED; + } + + /* done */ + return OPERATOR_FINISHED; +} + +void NLA_OT_tweakmode_enter(wmOperatorType *ot) +{ + PropertyRNA *prop; + + /* identifiers */ + ot->name = "Enter Tweak Mode"; + ot->idname = "NLA_OT_tweakmode_enter"; + ot->description = + "Enter tweaking mode for the action referenced by the active strip to edit its keyframes"; + + /* api callbacks */ + ot->exec = nlaedit_enable_tweakmode_exec; + ot->poll = nlaop_poll_tweakmode_off; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + /* properties */ + prop = RNA_def_boolean(ot->srna, + "isolate_action", + 0, + "Isolate Action", + "Enable 'solo' on the NLA Track containing the active strip, " + "to edit it without seeing the effects of the NLA stack"); + RNA_def_property_flag(prop, PROP_SKIP_SAVE); + + prop = RNA_def_boolean(ot->srna, + "use_upper_stack_evaluation", + false, + "Evaluate Upper Stack", + "In tweak mode, display the effects of the tracks above the tweak strip"); + RNA_def_property_flag(prop, PROP_SKIP_SAVE); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Disable Tweak-Mode Operator + * \{ */ + +bool nlaedit_disable_tweakmode(bAnimContext *ac, bool do_solo) +{ + ListBase anim_data = {NULL, NULL}; + bAnimListElem *ale; + int filter; + + /* get a list of the AnimData blocks being shown in the NLA */ + filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_ANIMDATA | ANIMFILTER_FCURVESONLY); + ANIM_animdata_filter(ac, &anim_data, filter, ac->data, ac->datatype); + + /* if no blocks, popup error? */ + if (BLI_listbase_is_empty(&anim_data)) { + BKE_report(ac->reports, RPT_ERROR, "No AnimData blocks in tweak mode to exit from"); + return false; + } + + /* For each AnimData block with NLA-data, try exiting tweak-mode. */ + for (ale = anim_data.first; ale; ale = ale->next) { + AnimData *adt = ale->data; + + /* clear solo flags */ + if ((do_solo) & (adt->flag & ADT_NLA_SOLO_TRACK) && (adt->flag & ADT_NLA_EDIT_ON)) { + BKE_nlatrack_solo_toggle(adt, NULL); + } + + /* To be sure that we're doing everything right, just exit tweak-mode. */ + BKE_nla_tweakmode_exit(adt); + + ale->update |= ANIM_UPDATE_DEPS; + } + + /* free temp data */ + ANIM_animdata_update(ac, &anim_data); + ANIM_animdata_freelist(&anim_data); + + /* Clear the tweak-mode flag in the active scene and send notifiers. */ + if (ac->scene) { + /* clear editing flag */ + ac->scene->flag &= ~SCE_NLA_EDIT_ON; + + /* set notifier that things have changed */ + WM_main_add_notifier(NC_ANIMATION | ND_NLA_ACTCHANGE, NULL); + } + + /* done */ + return true; +} + +/* Exit tweak-mode operator callback. */ +static int nlaedit_disable_tweakmode_exec(bContext *C, wmOperator *op) +{ + bAnimContext ac; + + const bool do_solo = RNA_boolean_get(op->ptr, "isolate_action"); + bool ok = false; + + /* get editor data */ + if (ANIM_animdata_get_context(C, &ac) == 0) { + return OPERATOR_CANCELLED; + } + + /* perform operation */ + ok = nlaedit_disable_tweakmode(&ac, do_solo); + + /* success? */ + if (ok) { + return OPERATOR_FINISHED; + } + return OPERATOR_CANCELLED; +} + +void NLA_OT_tweakmode_exit(wmOperatorType *ot) +{ + PropertyRNA *prop; + + /* identifiers */ + ot->name = "Exit Tweak Mode"; + ot->idname = "NLA_OT_tweakmode_exit"; + ot->description = "Exit tweaking mode for the action referenced by the active strip"; + + /* api callbacks */ + ot->exec = nlaedit_disable_tweakmode_exec; + ot->poll = nlaop_poll_tweakmode_on; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + /* properties */ + prop = RNA_def_boolean(ot->srna, + "isolate_action", + 0, + "Isolate Action", + "Disable 'solo' on any of the NLA Tracks after exiting tweak mode " + "to get things back to normal"); + RNA_def_property_flag(prop, PROP_SKIP_SAVE); +} + +/** \} */ + +/* NLA Strips Range Stuff */ + +/* -------------------------------------------------------------------- */ +/** \name Calculate NLA Strip Range + * \{ */ + +/* Get the min/max strip extents */ +static void get_nlastrip_extents(bAnimContext *ac, float *min, float *max, const bool only_sel) +{ + ListBase anim_data = {NULL, NULL}; + bAnimListElem *ale; + int filter; + bool found_bounds = false; + + /* get data to filter */ + filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE | ANIMFILTER_NODUPLIS | + ANIMFILTER_FCURVESONLY); + ANIM_animdata_filter(ac, &anim_data, filter, ac->data, ac->datatype); + + /* set large values to try to override */ + *min = 999999999.0f; + *max = -999999999.0f; + + /* check if any channels to set range with */ + if (anim_data.first) { + /* go through channels, finding max extents */ + for (ale = anim_data.first; ale; ale = ale->next) { + NlaTrack *nlt = (NlaTrack *)ale->data; + NlaStrip *strip; + + for (strip = nlt->strips.first; strip; strip = strip->next) { + /* only consider selected strips? */ + if ((only_sel == false) || (strip->flag & NLASTRIP_FLAG_SELECT)) { + /* extend range if appropriate */ + *min = min_ff(*min, strip->start); + *max = max_ff(*max, strip->end); + + found_bounds = true; + } + } + } + + /* free memory */ + ANIM_animdata_freelist(&anim_data); + } + + /* set default range if nothing happened */ + if (found_bounds == false) { + if (ac->scene) { + *min = (float)ac->scene->r.sfra; + *max = (float)ac->scene->r.efra; + } + else { + *min = -5; + *max = 100; + } + } +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Automatic Preview-Range Operator + * \{ */ + +static int nlaedit_previewrange_exec(bContext *C, wmOperator *UNUSED(op)) +{ + bAnimContext ac; + Scene *scene; + float min, max; + + /* get editor data */ + if (ANIM_animdata_get_context(C, &ac) == 0) { + return OPERATOR_CANCELLED; + } + + if (ac.scene == NULL) { + return OPERATOR_CANCELLED; + } + + scene = ac.scene; + + /* set the range directly */ + get_nlastrip_extents(&ac, &min, &max, true); + scene->r.flag |= SCER_PRV_RANGE; + scene->r.psfra = round_fl_to_int(min); + scene->r.pefra = round_fl_to_int(max); + + /* set notifier that things have changed */ + /* XXX err... there's nothing for frame ranges yet, but this should do fine too */ + WM_event_add_notifier(C, NC_SCENE | ND_FRAME, ac.scene); + + return OPERATOR_FINISHED; +} + +void NLA_OT_previewrange_set(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Set Preview Range to Selected"; + ot->idname = "NLA_OT_previewrange_set"; + ot->description = "Set Preview Range based on extends of selected strips"; + + /* api callbacks */ + ot->exec = nlaedit_previewrange_exec; + ot->poll = ED_operator_nla_active; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name View-All Operator + * \{ */ + +/** + * Find the extents of the active channel + * + * \param r_min: Bottom y-extent of channel. + * \param r_max: Top y-extent of channel. + * \return Success of finding a selected channel. + */ +static bool nla_channels_get_selected_extents(bAnimContext *ac, float *r_min, float *r_max) +{ + ListBase anim_data = {NULL, NULL}; + bAnimListElem *ale; + int filter; + + SpaceNla *snla = (SpaceNla *)ac->sl; + /* NOTE: not bool, since we want prioritize individual channels over expanders. */ + short found = 0; + + /* get all items - we need to do it this way */ + filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE | ANIMFILTER_LIST_CHANNELS | + ANIMFILTER_FCURVESONLY); + ANIM_animdata_filter(ac, &anim_data, filter, ac->data, ac->datatype); + + /* loop through all channels, finding the first one that's selected */ + float ymax = NLACHANNEL_FIRST_TOP(ac); + + for (ale = anim_data.first; ale; ale = ale->next, ymax -= NLACHANNEL_STEP(snla)) { + const bAnimChannelType *acf = ANIM_channel_get_typeinfo(ale); + + /* must be selected... */ + if (acf && acf->has_setting(ac, ale, ACHANNEL_SETTING_SELECT) && + ANIM_channel_setting_get(ac, ale, ACHANNEL_SETTING_SELECT)) { + /* update best estimate */ + *r_min = ymax - NLACHANNEL_HEIGHT(snla); + *r_max = ymax; + + /* is this high enough priority yet? */ + found = acf->channel_role; + + /* only stop our search when we've found an actual channel + * - data-block expanders get less priority so that we don't abort prematurely + */ + if (found == ACHANNEL_ROLE_CHANNEL) { + break; + } + } + } + + /* free all temp data */ + ANIM_animdata_freelist(&anim_data); + + return (found != 0); +} + +static int nlaedit_viewall(bContext *C, const bool only_sel) +{ + bAnimContext ac; + View2D *v2d; + float extra; + + /* get editor data */ + if (ANIM_animdata_get_context(C, &ac) == 0) { + return OPERATOR_CANCELLED; + } + v2d = &ac.region->v2d; + + /* set the horizontal range, with an extra offset so that the extreme keys will be in view */ + get_nlastrip_extents(&ac, &v2d->cur.xmin, &v2d->cur.xmax, only_sel); + + extra = 0.1f * BLI_rctf_size_x(&v2d->cur); + v2d->cur.xmin -= extra; + v2d->cur.xmax += extra; + + /* set vertical range */ + if (only_sel == false) { + /* view all -> the summary channel is usually the shows everything, + * and resides right at the top... */ + v2d->cur.ymax = 0.0f; + v2d->cur.ymin = (float)-BLI_rcti_size_y(&v2d->mask); + } + else { + /* locate first selected channel (or the active one), and frame those */ + float ymin = v2d->cur.ymin; + float ymax = v2d->cur.ymax; + + if (nla_channels_get_selected_extents(&ac, &ymin, &ymax)) { + /* recenter the view so that this range is in the middle */ + float ymid = (ymax - ymin) / 2.0f + ymin; + float x_center; + + UI_view2d_center_get(v2d, &x_center, NULL); + UI_view2d_center_set(v2d, x_center, ymid); + } + } + + /* do View2D syncing */ + UI_view2d_sync(CTX_wm_screen(C), CTX_wm_area(C), v2d, V2D_LOCK_COPY); + + /* just redraw this view */ + ED_area_tag_redraw(CTX_wm_area(C)); + + return OPERATOR_FINISHED; +} + +/* ......... */ + +static int nlaedit_viewall_exec(bContext *C, wmOperator *UNUSED(op)) +{ + /* whole range */ + return nlaedit_viewall(C, false); +} + +static int nlaedit_viewsel_exec(bContext *C, wmOperator *UNUSED(op)) +{ + /* only selected */ + return nlaedit_viewall(C, true); +} + +void NLA_OT_view_all(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Frame All"; + ot->idname = "NLA_OT_view_all"; + ot->description = "Reset viewable area to show full strips range"; + + /* api callbacks */ + ot->exec = nlaedit_viewall_exec; + ot->poll = ED_operator_nla_active; + + /* flags */ + ot->flag = 0; +} + +void NLA_OT_view_selected(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Frame Selected"; + ot->idname = "NLA_OT_view_selected"; + ot->description = "Reset viewable area to show selected strips range"; + + /* api callbacks */ + ot->exec = nlaedit_viewsel_exec; + ot->poll = ED_operator_nla_active; + + /* flags */ + ot->flag = 0; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name View-Frame Operator + * \{ */ + +static int nlaedit_viewframe_exec(bContext *C, wmOperator *op) +{ + const int smooth_viewtx = WM_operator_smooth_viewtx_get(op); + ANIM_center_frame(C, smooth_viewtx); + return OPERATOR_FINISHED; +} + +void NLA_OT_view_frame(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Go to Current Frame"; + ot->idname = "NLA_OT_view_frame"; + ot->description = "Move the view to the current frame"; + + /* api callbacks */ + ot->exec = nlaedit_viewframe_exec; + ot->poll = ED_operator_nla_active; + + /* flags */ + ot->flag = 0; +} + +/** \} */ + +/* NLA Editing Operations (Constructive/Destructive) */ + +/* -------------------------------------------------------------------- */ +/** \name Add Action-Clip Operator + * + * Add a new Action-Clip strip to the active track + * (or the active block if no space in the track). + * \{ */ + +/* Get a list of the editable tracks being shown in the NLA. */ +static int nlaedit_get_editable_tracks(bAnimContext *ac, ListBase *anim_data) +{ + const int filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_ACTIVE | ANIMFILTER_FOREDIT | + ANIMFILTER_FCURVESONLY); + return ANIM_animdata_filter(ac, anim_data, filter, ac->data, ac->datatype); +} + +static int nlaedit_add_actionclip_invoke(bContext *C, wmOperator *op, const wmEvent *event) +{ + /* Get editor data. */ + bAnimContext ac; + if (ANIM_animdata_get_context(C, &ac) == 0) { + return OPERATOR_CANCELLED; + } + + ListBase anim_data = {NULL, NULL}; + const size_t items = nlaedit_get_editable_tracks(&ac, &anim_data); + + if (items == 0) { + BKE_report(op->reports, + RPT_ERROR, + "No active track(s) to add strip to, select an existing track or add one before " + "trying again"); + return OPERATOR_CANCELLED; + } + + return WM_enum_search_invoke(C, op, event); +} + +/* add the specified action as new strip */ +static int nlaedit_add_actionclip_exec(bContext *C, wmOperator *op) +{ + Main *bmain = CTX_data_main(C); + bAnimContext ac; + Scene *scene; + + ListBase anim_data = {NULL, NULL}; + bAnimListElem *ale; + + bAction *act; + + float cfra; + + /* get editor data */ + if (ANIM_animdata_get_context(C, &ac) == 0) { + return OPERATOR_CANCELLED; + } + + scene = ac.scene; + cfra = (float)scene->r.cfra; + + /* get action to use */ + act = BLI_findlink(&bmain->actions, RNA_enum_get(op->ptr, "action")); + + if (act == NULL) { + BKE_report(op->reports, RPT_ERROR, "No valid action to add"); + // printf("Add strip - actname = '%s'\n", actname); + return OPERATOR_CANCELLED; + } + if (act->idroot == 0) { + /* hopefully in this case (i.e. library of userless actions), + * the user knows what they're doing... */ + BKE_reportf(op->reports, + RPT_WARNING, + "Action '%s' does not specify what data-blocks it can be used on " + "(try setting the 'ID Root Type' setting from the data-blocks editor " + "for this action to avoid future problems)", + act->id.name + 2); + } + + /* add tracks to empty but selected animdata blocks so that strips can be added to those directly + * without having to manually add tracks first + */ + nlaedit_add_tracks_empty(&ac); + + nlaedit_get_editable_tracks(&ac, &anim_data); + + /* for every active track, + * try to add strip to free space in track or to the top of the stack if no space */ + for (ale = anim_data.first; ale; ale = ale->next) { + NlaTrack *nlt = (NlaTrack *)ale->data; + AnimData *adt = ale->adt; + NlaStrip *strip = NULL; + const bool is_liboverride = ID_IS_OVERRIDE_LIBRARY(ale->id); + + /* Sanity check: only apply actions of the right type for this ID. + * NOTE: in the case that this hasn't been set, + * we've already warned the user about this already + */ + if ((act->idroot) && (act->idroot != GS(ale->id->name))) { + BKE_reportf( + op->reports, + RPT_ERROR, + "Could not add action '%s' as it cannot be used relative to ID-blocks of type '%s'", + act->id.name + 2, + ale->id->name); + continue; + } + + /* create a new strip, and offset it to start on the current frame */ + strip = BKE_nlastrip_new(act); + + strip->end += (cfra - strip->start); + strip->start = cfra; + + /* firstly try adding strip to our current track, but if that fails, add to a new track */ + if (BKE_nlatrack_add_strip(nlt, strip, is_liboverride) == 0) { + /* trying to add to the current failed (no space), + * so add a new track to the stack, and add to that... + */ + nlt = BKE_nlatrack_add(adt, NULL, is_liboverride); + BKE_nlatrack_add_strip(nlt, strip, is_liboverride); + } + + /* auto-name it */ + BKE_nlastrip_validate_name(adt, strip); + } + + /* free temp data */ + ANIM_animdata_freelist(&anim_data); + + /* refresh auto strip properties */ + ED_nla_postop_refresh(&ac); + + DEG_relations_tag_update(ac.bmain); + + /* set notifier that things have changed */ + WM_event_add_notifier(C, NC_ANIMATION | ND_NLA | NA_ADDED, NULL); + + /* done */ + return OPERATOR_FINISHED; +} + +void NLA_OT_actionclip_add(wmOperatorType *ot) +{ + PropertyRNA *prop; + + /* identifiers */ + ot->name = "Add Action Strip"; + ot->idname = "NLA_OT_actionclip_add"; + ot->description = + "Add an Action-Clip strip (i.e. an NLA Strip referencing an Action) to the active track"; + + /* api callbacks */ + ot->invoke = nlaedit_add_actionclip_invoke; + ot->exec = nlaedit_add_actionclip_exec; + ot->poll = nlaop_poll_tweakmode_off; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + /* props */ + /* TODO: this would be nicer as an ID-pointer. */ + prop = RNA_def_enum(ot->srna, "action", DummyRNA_NULL_items, 0, "Action", ""); + RNA_def_enum_funcs(prop, RNA_action_itemf); + RNA_def_property_flag(prop, PROP_ENUM_NO_TRANSLATE); + ot->prop = prop; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Add Transition Operator + * + * Add a new transition strip between selected strips. + * \{ */ + +static int nlaedit_add_transition_exec(bContext *C, wmOperator *op) +{ + bAnimContext ac; + + ListBase anim_data = {NULL, NULL}; + bAnimListElem *ale; + int filter; + + bool done = false; + + /* get editor data */ + if (ANIM_animdata_get_context(C, &ac) == 0) { + return OPERATOR_CANCELLED; + } + + /* get a list of the editable tracks being shown in the NLA */ + filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE | ANIMFILTER_FOREDIT | + ANIMFILTER_FCURVESONLY); + ANIM_animdata_filter(&ac, &anim_data, filter, ac.data, ac.datatype); + + /* for each track, find pairs of strips to add transitions to */ + for (ale = anim_data.first; ale; ale = ale->next) { + NlaTrack *nlt = (NlaTrack *)ale->data; + AnimData *adt = ale->adt; + NlaStrip *s1, *s2; + + /* get initial pair of strips */ + if (ELEM(nlt->strips.first, NULL, nlt->strips.last)) { + continue; + } + s1 = nlt->strips.first; + s2 = s1->next; + + /* loop over strips */ + for (; s1 && s2; s1 = s2, s2 = s2->next) { + NlaStrip *strip; + + /* check if both are selected */ + if (ELEM(0, (s1->flag & NLASTRIP_FLAG_SELECT), (s2->flag & NLASTRIP_FLAG_SELECT))) { + continue; + } + /* check if there's space between the two */ + if (IS_EQF(s1->end, s2->start)) { + continue; + } + /* make sure neither one is a transition + * - although this is impossible to create with the standard tools, + * the user may have altered the settings + */ + if (ELEM(NLASTRIP_TYPE_TRANSITION, s1->type, s2->type)) { + continue; + } + /* also make sure neither one is a soundclip */ + if (ELEM(NLASTRIP_TYPE_SOUND, s1->type, s2->type)) { + continue; + } + + /* allocate new strip */ + strip = MEM_callocN(sizeof(NlaStrip), "NlaStrip"); + BLI_insertlinkafter(&nlt->strips, s1, strip); + + /* set the type */ + strip->type = NLASTRIP_TYPE_TRANSITION; + + /* generic settings + * - selected flag to highlight this to the user + * - auto-blends to ensure that blend in/out values are automatically + * determined by overlaps of strips + */ + strip->flag = NLASTRIP_FLAG_SELECT | NLASTRIP_FLAG_AUTO_BLENDS; + + /* range is simply defined as the endpoints of the adjacent strips */ + strip->start = s1->end; + strip->end = s2->start; + + /* scale and repeat aren't of any use, but shouldn't ever be 0 */ + strip->scale = 1.0f; + strip->repeat = 1.0f; + + /* auto-name it */ + BKE_nlastrip_validate_name(adt, strip); + + /* make note of this */ + done = true; + } + } + + /* free temp data */ + ANIM_animdata_freelist(&anim_data); + + /* was anything added? */ + if (done) { + /* refresh auto strip properties */ + ED_nla_postop_refresh(&ac); + + /* set notifier that things have changed */ + WM_event_add_notifier(C, NC_ANIMATION | ND_NLA | NA_ADDED, NULL); + + /* done */ + return OPERATOR_FINISHED; + } + + BKE_report(op->reports, + RPT_ERROR, + "Needs at least a pair of adjacent selected strips with a gap between them"); + return OPERATOR_CANCELLED; +} + +void NLA_OT_transition_add(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Add Transition"; + ot->idname = "NLA_OT_transition_add"; + ot->description = "Add a transition strip between two adjacent selected strips"; + + /* api callbacks */ + ot->exec = nlaedit_add_transition_exec; + ot->poll = nlaop_poll_tweakmode_off; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Add Sound Clip Operator + * \{ */ + +static int nlaedit_add_sound_exec(bContext *C, wmOperator *UNUSED(op)) +{ + Main *bmain = CTX_data_main(C); + bAnimContext ac; + + ListBase anim_data = {NULL, NULL}; + bAnimListElem *ale; + int filter; + + Scene *scene; + int cfra; + + /* get editor data */ + if (ANIM_animdata_get_context(C, &ac) == 0) { + return OPERATOR_CANCELLED; + } + + scene = ac.scene; + cfra = scene->r.cfra; + + /* get a list of the editable tracks being shown in the NLA */ + filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE | ANIMFILTER_SEL | + ANIMFILTER_FOREDIT | ANIMFILTER_FCURVESONLY); + ANIM_animdata_filter(&ac, &anim_data, filter, ac.data, ac.datatype); + + /* for each track, add sound clips if it belongs to a speaker */ + /* TODO: what happens if there aren't any tracks, + * well that's a more general problem for later. */ + for (ale = anim_data.first; ale; ale = ale->next) { + Object *ob = (Object *)ale->id; /* may not be object until we actually check! */ + + AnimData *adt = ale->adt; + NlaTrack *nlt = (NlaTrack *)ale->data; + NlaStrip *strip; + const bool is_liboverride = ID_IS_OVERRIDE_LIBRARY(ale->id); + + /* does this belong to speaker - assumed to live on Object level only */ + if ((GS(ale->id->name) != ID_OB) || (ob->type != OB_SPEAKER)) { + continue; + } + + /* create a new strip, and offset it to start on the current frame */ + strip = BKE_nla_add_soundstrip(bmain, ac.scene, ob->data); + + strip->start += cfra; + strip->end += cfra; + + /* firstly try adding strip to our current track, but if that fails, add to a new track */ + if (BKE_nlatrack_add_strip(nlt, strip, is_liboverride) == 0) { + /* trying to add to the current failed (no space), + * so add a new track to the stack, and add to that... + */ + nlt = BKE_nlatrack_add(adt, NULL, is_liboverride); + BKE_nlatrack_add_strip(nlt, strip, is_liboverride); + } + + /* auto-name it */ + BKE_nlastrip_validate_name(adt, strip); + } + + /* free temp data */ + ANIM_animdata_freelist(&anim_data); + + /* refresh auto strip properties */ + ED_nla_postop_refresh(&ac); + + /* set notifier that things have changed */ + WM_event_add_notifier(C, NC_ANIMATION | ND_NLA | NA_ADDED, NULL); + + /* done */ + return OPERATOR_FINISHED; +} + +void NLA_OT_soundclip_add(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Add Sound Clip"; + ot->idname = "NLA_OT_soundclip_add"; + ot->description = "Add a strip for controlling when speaker plays its sound clip"; + + /* api callbacks */ + ot->exec = nlaedit_add_sound_exec; + ot->poll = nlaop_poll_tweakmode_off; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Add Meta-Strip Operator + * + * Add new meta-strips incorporating the selected strips. + * \{ */ + +/* add the specified action as new strip */ +static int nlaedit_add_meta_exec(bContext *C, wmOperator *UNUSED(op)) +{ + bAnimContext ac; + + ListBase anim_data = {NULL, NULL}; + bAnimListElem *ale; + int filter; + + /* get editor data */ + if (ANIM_animdata_get_context(C, &ac) == 0) { + return OPERATOR_CANCELLED; + } + + /* get a list of the editable tracks being shown in the NLA */ + filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE | ANIMFILTER_FOREDIT | + ANIMFILTER_FCURVESONLY); + ANIM_animdata_filter(&ac, &anim_data, filter, ac.data, ac.datatype); + + /* for each track, find pairs of strips to add transitions to */ + for (ale = anim_data.first; ale; ale = ale->next) { + NlaTrack *nlt = (NlaTrack *)ale->data; + AnimData *adt = ale->adt; + NlaStrip *strip; + + if (BKE_nlatrack_is_nonlocal_in_liboverride(ale->id, nlt)) { + /* No making meta-strips in non-local tracks of override data. */ + continue; + } + + /* create meta-strips from the continuous chains of selected strips */ + BKE_nlastrips_make_metas(&nlt->strips, 0); + + /* name the metas */ + for (strip = nlt->strips.first; strip; strip = strip->next) { + /* auto-name this strip if selected (that means it is a meta) */ + if (strip->flag & NLASTRIP_FLAG_SELECT) { + BKE_nlastrip_validate_name(adt, strip); + } + } + + ale->update |= ANIM_UPDATE_DEPS; + } + + /* free temp data */ + ANIM_animdata_update(&ac, &anim_data); + ANIM_animdata_freelist(&anim_data); + + /* set notifier that things have changed */ + WM_event_add_notifier(C, NC_ANIMATION | ND_NLA | NA_ADDED, NULL); + + /* done */ + return OPERATOR_FINISHED; +} + +void NLA_OT_meta_add(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Add Meta-Strips"; + ot->idname = "NLA_OT_meta_add"; + ot->description = "Add new meta-strips incorporating the selected strips"; + + /* api callbacks */ + ot->exec = nlaedit_add_meta_exec; + ot->poll = nlaop_poll_tweakmode_off; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Remove Meta-Strip Operator + * + * Separate out the strips held by the selected meta-strips. + * \{ */ + +static int nlaedit_remove_meta_exec(bContext *C, wmOperator *UNUSED(op)) +{ + bAnimContext ac; + + ListBase anim_data = {NULL, NULL}; + bAnimListElem *ale; + int filter; + + /* get editor data */ + if (ANIM_animdata_get_context(C, &ac) == 0) { + return OPERATOR_CANCELLED; + } + + /* get a list of the editable tracks being shown in the NLA */ + filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE | ANIMFILTER_FOREDIT | + ANIMFILTER_FCURVESONLY); + ANIM_animdata_filter(&ac, &anim_data, filter, ac.data, ac.datatype); + + /* for each track, find pairs of strips to add transitions to */ + for (ale = anim_data.first; ale; ale = ale->next) { + NlaTrack *nlt = (NlaTrack *)ale->data; + + if (BKE_nlatrack_is_nonlocal_in_liboverride(ale->id, nlt)) { + /* No removing meta-strips from non-local tracks of override data. */ + continue; + } + + /* clear all selected meta-strips, regardless of whether they are temporary or not */ + BKE_nlastrips_clear_metas(&nlt->strips, 1, 0); + + ale->update |= ANIM_UPDATE_DEPS; + } + + /* free temp data */ + ANIM_animdata_update(&ac, &anim_data); + ANIM_animdata_freelist(&anim_data); + + /* set notifier that things have changed */ + WM_event_add_notifier(C, NC_ANIMATION | ND_NLA | NA_REMOVED, NULL); + + /* done */ + return OPERATOR_FINISHED; +} + +void NLA_OT_meta_remove(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Remove Meta-Strips"; + ot->idname = "NLA_OT_meta_remove"; + ot->description = "Separate out the strips held by the selected meta-strips"; + + /* api callbacks */ + ot->exec = nlaedit_remove_meta_exec; + ot->poll = nlaop_poll_tweakmode_off; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Duplicate Strips Operator + * + * Duplicates the selected NLA-Strips, + * putting them on new tracks above the one the originals were housed in. + * \{ */ + +static int nlaedit_duplicate_exec(bContext *C, wmOperator *op) +{ + bAnimContext ac; + + ListBase anim_data = {NULL, NULL}; + bAnimListElem *ale; + int filter; + + bool linked = RNA_boolean_get(op->ptr, "linked"); + bool done = false; + + /* get editor data */ + if (ANIM_animdata_get_context(C, &ac) == 0) { + return OPERATOR_CANCELLED; + } + + /* get a list of editable tracks being shown in the NLA */ + filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE | ANIMFILTER_FOREDIT | + ANIMFILTER_FCURVESONLY); + ANIM_animdata_filter(&ac, &anim_data, filter, ac.data, ac.datatype); + + /* duplicate strips in tracks starting from the last one so that we're + * less likely to duplicate strips we just duplicated... + */ + for (ale = anim_data.last; ale; ale = ale->prev) { + NlaTrack *nlt = (NlaTrack *)ale->data; + AnimData *adt = ale->adt; + NlaStrip *strip, *nstrip, *next; + NlaTrack *track; + + /* NOTE: We allow this operator in override context because it is almost always (from possible + * default user interactions) paired with the transform one, which will ensure that the new + * strip ends up in a valid (local) track. */ + + const bool is_liboverride = ID_IS_OVERRIDE_LIBRARY(ale->id); + for (strip = nlt->strips.first; strip; strip = next) { + next = strip->next; + + /* if selected, split the strip at its midpoint */ + if (strip->flag & NLASTRIP_FLAG_SELECT) { + /* make a copy (assume that this is possible) */ + nstrip = BKE_nlastrip_copy(ac.bmain, strip, linked, 0); + + /* in case there's no space in the track above, + * or we haven't got a reference to it yet, try adding */ + if (BKE_nlatrack_add_strip(nlt->next, nstrip, is_liboverride) == 0) { + /* need to add a new track above the one above the current one + * - if the current one is the last one, nlt->next will be NULL, which defaults to adding + * at the top of the stack anyway... + */ + track = BKE_nlatrack_add(adt, nlt->next, is_liboverride); + BKE_nlatrack_add_strip(track, nstrip, is_liboverride); + } + + /* deselect the original and the active flag */ + strip->flag &= ~(NLASTRIP_FLAG_SELECT | NLASTRIP_FLAG_ACTIVE); + + /* auto-name newly created strip */ + BKE_nlastrip_validate_name(adt, nstrip); + + done = true; + } + } + } + + /* free temp data */ + ANIM_animdata_freelist(&anim_data); + + if (done) { + /* refresh auto strip properties */ + ED_nla_postop_refresh(&ac); + + if (!linked) { + DEG_relations_tag_update(ac.bmain); + } + + /* set notifier that things have changed */ + WM_event_add_notifier(C, NC_ANIMATION | ND_NLA | NA_ADDED, NULL); + + /* done */ + return OPERATOR_FINISHED; + } + + return OPERATOR_CANCELLED; +} + +static int nlaedit_duplicate_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(event)) +{ + nlaedit_duplicate_exec(C, op); + + return OPERATOR_FINISHED; +} + +void NLA_OT_duplicate(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Duplicate Strips"; + ot->idname = "NLA_OT_duplicate"; + ot->description = + "Duplicate selected NLA-Strips, adding the new strips in new tracks above the originals"; + + /* api callbacks */ + ot->invoke = nlaedit_duplicate_invoke; + ot->exec = nlaedit_duplicate_exec; + ot->poll = nlaop_poll_tweakmode_off; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + /* own properties */ + ot->prop = RNA_def_boolean(ot->srna, + "linked", + false, + "Linked", + "When duplicating strips, assign new copies of the actions they use"); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Delete Strips Operator + * + * Deletes the selected NLA-Strips. + * \{ */ + +static int nlaedit_delete_exec(bContext *C, wmOperator *UNUSED(op)) +{ + bAnimContext ac; + + ListBase anim_data = {NULL, NULL}; + bAnimListElem *ale; + int filter; + + /* get editor data */ + if (ANIM_animdata_get_context(C, &ac) == 0) { + return OPERATOR_CANCELLED; + } + + /* get a list of the editable tracks being shown in the NLA */ + filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE | ANIMFILTER_FOREDIT | + ANIMFILTER_FCURVESONLY); + ANIM_animdata_filter(&ac, &anim_data, filter, ac.data, ac.datatype); + + /* for each NLA-Track, delete all selected strips */ + for (ale = anim_data.first; ale; ale = ale->next) { + NlaTrack *nlt = (NlaTrack *)ale->data; + NlaStrip *strip, *nstrip; + + if (BKE_nlatrack_is_nonlocal_in_liboverride(ale->id, nlt)) { + /* No deletion of strips in non-local tracks of override data. */ + continue; + } + + for (strip = nlt->strips.first; strip; strip = nstrip) { + nstrip = strip->next; + + /* if selected, delete */ + if (strip->flag & NLASTRIP_FLAG_SELECT) { + /* if a strip either side of this was a transition, delete those too */ + if ((strip->prev) && (strip->prev->type == NLASTRIP_TYPE_TRANSITION)) { + BKE_nlastrip_remove_and_free(&nlt->strips, strip->prev, true); + } + if ((nstrip) && (nstrip->type == NLASTRIP_TYPE_TRANSITION)) { + nstrip = nstrip->next; + BKE_nlastrip_remove_and_free(&nlt->strips, strip->next, true); + } + + /* finally, delete this strip */ + BKE_nlastrip_remove_and_free(&nlt->strips, strip, true); + } + } + } + + /* free temp data */ + ANIM_animdata_freelist(&anim_data); + + /* refresh auto strip properties */ + ED_nla_postop_refresh(&ac); + + DEG_relations_tag_update(ac.bmain); + + /* set notifier that things have changed */ + WM_event_add_notifier(C, NC_ANIMATION | ND_NLA | NA_REMOVED, NULL); + + /* done */ + return OPERATOR_FINISHED; +} + +void NLA_OT_delete(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Delete Strips"; + ot->idname = "NLA_OT_delete"; + ot->description = "Delete selected strips"; + + /* api callbacks */ + ot->exec = nlaedit_delete_exec; + ot->poll = nlaop_poll_tweakmode_off; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Split Strips Operator + * + * Splits the selected NLA-Strips into two strips at the midpoint of the strip. + * + * TODO's? + * - multiple splits + * - variable-length splits? + * \{ */ + +/* split a given Action-Clip strip */ +static void nlaedit_split_strip_actclip( + Main *bmain, AnimData *adt, NlaTrack *nlt, NlaStrip *strip, float cfra) +{ + NlaStrip *nstrip; + float splitframe, splitaframe; + + /* calculate the frames to do the splitting at + * - use current frame if within extents of strip + */ + if ((cfra > strip->start) && (cfra < strip->end)) { + /* use the current frame */ + splitframe = cfra; + splitaframe = nlastrip_get_frame(strip, cfra, NLATIME_CONVERT_UNMAP); + } + else { + /* split in the middle */ + float len; + + /* strip extents */ + len = strip->end - strip->start; + if (IS_EQF(len, 0.0f)) { + return; + } + + splitframe = strip->start + (len / 2.0f); + + /* action range */ + len = strip->actend - strip->actstart; + if (IS_EQF(len, 0.0f)) { + splitaframe = strip->actend; + } + else { + splitaframe = strip->actstart + (len / 2.0f); + } + } + + /* make a copy (assume that this is possible) and append + * it immediately after the current strip + */ + nstrip = BKE_nlastrip_copy(bmain, strip, true, 0); + BLI_insertlinkafter(&nlt->strips, strip, nstrip); + + /* Set the endpoint of the first strip and the start of the new strip + * to the split-frame values calculated above. + */ + strip->end = splitframe; + nstrip->start = splitframe; + + if ((splitaframe > strip->actstart) && (splitaframe < strip->actend)) { + /* only do this if we're splitting down the middle... */ + strip->actend = splitaframe; + nstrip->actstart = splitaframe; + } + + /* Make sure Sync Length is off. With that setting on, entering and exiting tweak mode would + * effectively undo the split, because both the old and the new strip will be at the length of + * the Action again. */ + strip->flag &= ~NLASTRIP_FLAG_SYNC_LENGTH; + nstrip->flag &= ~(NLASTRIP_FLAG_SYNC_LENGTH | NLASTRIP_FLAG_ACTIVE); + + /* auto-name the new strip */ + BKE_nlastrip_validate_name(adt, nstrip); +} + +/* split a given Meta strip */ +static void nlaedit_split_strip_meta(NlaTrack *nlt, NlaStrip *strip) +{ + /* simply ungroup it for now... */ + BKE_nlastrips_clear_metastrip(&nlt->strips, strip); +} + +/* ----- */ + +static int nlaedit_split_exec(bContext *C, wmOperator *UNUSED(op)) +{ + bAnimContext ac; + + ListBase anim_data = {NULL, NULL}; + bAnimListElem *ale; + int filter; + + /* get editor data */ + if (ANIM_animdata_get_context(C, &ac) == 0) { + return OPERATOR_CANCELLED; + } + + /* get a list of editable tracks being shown in the NLA */ + filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE | ANIMFILTER_FOREDIT | + ANIMFILTER_FCURVESONLY); + ANIM_animdata_filter(&ac, &anim_data, filter, ac.data, ac.datatype); + + /* for each NLA-Track, split all selected strips into two strips */ + for (ale = anim_data.first; ale; ale = ale->next) { + NlaTrack *nlt = (NlaTrack *)ale->data; + AnimData *adt = ale->adt; + NlaStrip *strip, *next; + + if (BKE_nlatrack_is_nonlocal_in_liboverride(ale->id, nlt)) { + /* No splitting of strips in non-local tracks of override data. */ + continue; + } + + for (strip = nlt->strips.first; strip; strip = next) { + next = strip->next; + + /* if selected, split the strip at its midpoint */ + if (strip->flag & NLASTRIP_FLAG_SELECT) { + /* splitting method depends on the type of strip */ + switch (strip->type) { + case NLASTRIP_TYPE_CLIP: /* action-clip */ + nlaedit_split_strip_actclip(ac.bmain, adt, nlt, strip, (float)ac.scene->r.cfra); + break; + + case NLASTRIP_TYPE_META: /* meta-strips need special handling */ + nlaedit_split_strip_meta(nlt, strip); + break; + + default: /* for things like Transitions, do not split! */ + break; + } + } + } + } + + /* free temp data */ + ANIM_animdata_freelist(&anim_data); + + /* refresh auto strip properties */ + ED_nla_postop_refresh(&ac); + + /* set notifier that things have changed */ + WM_event_add_notifier(C, NC_ANIMATION | ND_NLA | NA_ADDED, NULL); + + /* done */ + return OPERATOR_FINISHED; +} + +void NLA_OT_split(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Split Strips"; + ot->idname = "NLA_OT_split"; + ot->description = "Split selected strips at their midpoints"; + + /* api callbacks */ + ot->exec = nlaedit_split_exec; + ot->poll = nlaop_poll_tweakmode_off; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; +} + +/** \} */ + +/* NLA Editing Operations (Modifying) */ + +/* -------------------------------------------------------------------- */ +/** \name Toggle Muting Operator + * + * Toggles whether strips are muted or not. + * \{ */ + +static int nlaedit_toggle_mute_exec(bContext *C, wmOperator *UNUSED(op)) +{ + bAnimContext ac; + + ListBase anim_data = {NULL, NULL}; + bAnimListElem *ale; + int filter; + + /* get editor data */ + if (ANIM_animdata_get_context(C, &ac) == 0) { + return OPERATOR_CANCELLED; + } + + /* get a list of the editable tracks being shown in the NLA */ + filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE | ANIMFILTER_FOREDIT | + ANIMFILTER_FCURVESONLY); + ANIM_animdata_filter(&ac, &anim_data, filter, ac.data, ac.datatype); + + /* go over all selected strips */ + for (ale = anim_data.first; ale; ale = ale->next) { + NlaTrack *nlt = (NlaTrack *)ale->data; + NlaStrip *strip; + + /* For every selected strip, toggle muting. */ + for (strip = nlt->strips.first; strip; strip = strip->next) { + if (strip->flag & NLASTRIP_FLAG_SELECT) { + /* just flip the mute flag for now */ + /* TODO: have a pre-pass to check if mute all or unmute all? */ + strip->flag ^= NLASTRIP_FLAG_MUTED; + + /* tag AnimData to get recalculated */ + ale->update |= ANIM_UPDATE_DEPS; + } + } + } + + /* cleanup */ + ANIM_animdata_update(&ac, &anim_data); + ANIM_animdata_freelist(&anim_data); + + /* set notifier that things have changed */ + WM_event_add_notifier(C, NC_ANIMATION | ND_NLA | NA_EDITED, NULL); + + /* done */ + return OPERATOR_FINISHED; +} + +void NLA_OT_mute_toggle(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Toggle Muting"; + ot->idname = "NLA_OT_mute_toggle"; + ot->description = "Mute or un-mute selected strips"; + + /* api callbacks */ + ot->exec = nlaedit_toggle_mute_exec; + ot->poll = nlaop_poll_tweakmode_off; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Swap Strips Operator + * + * Tries to exchange strips within their owner tracks. + * \{ */ + +static int nlaedit_swap_exec(bContext *C, wmOperator *op) +{ + bAnimContext ac; + + ListBase anim_data = {NULL, NULL}; + bAnimListElem *ale; + int filter; + + /* get editor data */ + if (ANIM_animdata_get_context(C, &ac) == 0) { + return OPERATOR_CANCELLED; + } + + /* get a list of the editable tracks being shown in the NLA */ + filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE | ANIMFILTER_FOREDIT | + ANIMFILTER_FCURVESONLY); + ANIM_animdata_filter(&ac, &anim_data, filter, ac.data, ac.datatype); + + /* consider each track in turn */ + for (ale = anim_data.first; ale; ale = ale->next) { + NlaTrack *nlt = (NlaTrack *)ale->data; + + NlaStrip *strip, *stripN = NULL; + NlaStrip *area = NULL, *sb = NULL; + const bool is_liboverride = ID_IS_OVERRIDE_LIBRARY(ale->id); + + if (BKE_nlatrack_is_nonlocal_in_liboverride(ale->id, nlt)) { + /* No re-ordering of strips within non-local tracks of override data. */ + continue; + } + + /* Make temporary meta-strips so that entire islands of selections can be moved around. */ + BKE_nlastrips_make_metas(&nlt->strips, 1); + + /* special case: if there is only 1 island + * (i.e. temp meta BUT NOT unselected/normal/normal-meta strips) left after this, + * and this island has two strips inside it, then we should be able to just swap these still... + */ + if (BLI_listbase_is_empty(&nlt->strips) == false) { + NlaStrip *mstrip = (NlaStrip *)nlt->strips.first; + + if ((mstrip->flag & NLASTRIP_FLAG_TEMP_META) && + (BLI_listbase_count_at_most(&mstrip->strips, 3) == 2)) { + /* remove this temp meta, so that we can see the strips inside */ + BKE_nlastrips_clear_metas(&nlt->strips, 0, 1); + } + } + + /* get two selected strips only (these will be metas due to prev step) to operate on + * - only allow swapping 2, as with more the context becomes unclear + */ + for (strip = nlt->strips.first; strip; strip = stripN) { + stripN = strip->next; + + if (strip->flag & NLASTRIP_FLAG_SELECT) { + /* first or second strip? */ + if (area == NULL) { + /* store as first */ + area = strip; + } + else if (sb == NULL) { + /* store as second */ + sb = strip; + } + else { + /* too many selected */ + break; + } + } + } + + if (strip) { + /* too many selected warning */ + BKE_reportf( + op->reports, + RPT_WARNING, + "Too many clusters of strips selected in NLA Track (%s): needs exactly 2 to be selected", + nlt->name); + } + else if (area == NULL) { + /* no warning as this is just a common case, + * and it may get annoying when doing multiple tracks */ + } + else if (sb == NULL) { + /* too few selected warning */ + BKE_reportf( + op->reports, + RPT_WARNING, + "Too few clusters of strips selected in NLA Track (%s): needs exactly 2 to be selected", + nlt->name); + } + else { + float nsa[2], nsb[2]; + + /* remove these strips from the track, + * so that we can test if they can fit in the proposed places */ + BLI_remlink(&nlt->strips, area); + BLI_remlink(&nlt->strips, sb); + + /* calculate new extents for strips */ + /* a --> b */ + nsa[0] = sb->start; + nsa[1] = sb->start + (area->end - area->start); + /* b --> a */ + nsb[0] = area->start; + nsb[1] = area->start + (sb->end - sb->start); + + /* check if the track has room for the strips to be swapped */ + if (BKE_nlastrips_has_space(&nlt->strips, nsa[0], nsa[1]) && + BKE_nlastrips_has_space(&nlt->strips, nsb[0], nsb[1])) { + /* set new extents for strips then */ + area->start = nsa[0]; + area->end = nsa[1]; + BKE_nlameta_flush_transforms(area); + + sb->start = nsb[0]; + sb->end = nsb[1]; + BKE_nlameta_flush_transforms(sb); + } + else { + /* not enough room to swap, so show message */ + if ((area->flag & NLASTRIP_FLAG_TEMP_META) || (sb->flag & NLASTRIP_FLAG_TEMP_META)) { + BKE_report( + op->reports, + RPT_WARNING, + "Cannot swap selected strips as they will not be able to fit in their new places"); + } + else { + BKE_reportf(op->reports, + RPT_WARNING, + "Cannot swap '%s' and '%s' as one or both will not be able to fit in their " + "new places", + area->name, + sb->name); + } + } + + /* add strips back to track now */ + BKE_nlatrack_add_strip(nlt, area, is_liboverride); + BKE_nlatrack_add_strip(nlt, sb, is_liboverride); + } + + /* Clear (temp) meta-strips. */ + BKE_nlastrips_clear_metas(&nlt->strips, 0, 1); + } + + /* free temp data */ + ANIM_animdata_freelist(&anim_data); + + /* refresh auto strip properties */ + ED_nla_postop_refresh(&ac); + + /* set notifier that things have changed */ + WM_event_add_notifier(C, NC_ANIMATION | ND_NLA | NA_EDITED, NULL); + WM_event_add_notifier(C, NC_ANIMATION | ND_NLA_ORDER, NULL); + + /* done */ + return OPERATOR_FINISHED; +} + +void NLA_OT_swap(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Swap Strips"; + ot->idname = "NLA_OT_swap"; + ot->description = "Swap order of selected strips within tracks"; + + /* api callbacks */ + ot->exec = nlaedit_swap_exec; + ot->poll = nlaop_poll_tweakmode_off; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Move Strips Up Operator + * + * Tries to move the selected strips into the track above if possible. + * \{ */ + +static int nlaedit_move_up_exec(bContext *C, wmOperator *UNUSED(op)) +{ + bAnimContext ac; + + ListBase anim_data = {NULL, NULL}; + bAnimListElem *ale; + int filter; + + /* get editor data */ + if (ANIM_animdata_get_context(C, &ac) == 0) { + return OPERATOR_CANCELLED; + } + + /* get a list of the editable tracks being shown in the NLA */ + filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE | ANIMFILTER_FOREDIT | + ANIMFILTER_FCURVESONLY); + ANIM_animdata_filter(&ac, &anim_data, filter, ac.data, ac.datatype); + + /* since we're potentially moving strips from lower tracks to higher tracks, we should + * loop over the tracks in reverse order to avoid moving earlier strips up multiple tracks + */ + for (ale = anim_data.last; ale; ale = ale->prev) { + NlaTrack *nlt = (NlaTrack *)ale->data; + NlaTrack *nltn = nlt->next; + NlaStrip *strip, *stripn; + + const bool is_liboverride = ID_IS_OVERRIDE_LIBRARY(ale->id); + + /* if this track has no tracks after it, skip for now... */ + if (nltn == NULL) { + continue; + } + + if (BKE_nlatrack_is_nonlocal_in_liboverride(ale->id, nlt) || + BKE_nlatrack_is_nonlocal_in_liboverride(ale->id, nltn)) { + /* No moving of strips in non-local tracks of override data. */ + continue; + } + + /* for every selected strip, try to move */ + for (strip = nlt->strips.first; strip; strip = stripn) { + stripn = strip->next; + + if (strip->flag & NLASTRIP_FLAG_SELECT) { + /* check if the track above has room for this strip */ + if (BKE_nlatrack_has_space(nltn, strip->start, strip->end)) { + /* remove from its current track, and add to the one above + * (it 'should' work, so no need to worry) */ + BKE_nlatrack_remove_strip(nlt, strip); + BKE_nlatrack_add_strip(nltn, strip, is_liboverride); + } + } + } + } + + /* free temp data */ + ANIM_animdata_freelist(&anim_data); + + /* refresh auto strip properties */ + ED_nla_postop_refresh(&ac); + + /* set notifier that things have changed */ + WM_event_add_notifier(C, NC_ANIMATION | ND_NLA | NA_EDITED, NULL); + WM_event_add_notifier(C, NC_ANIMATION | ND_NLA_ORDER, NULL); + + /* done */ + return OPERATOR_FINISHED; +} + +void NLA_OT_move_up(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Move Strips Up"; + ot->idname = "NLA_OT_move_up"; + ot->description = "Move selected strips up a track if there's room"; + + /* api callbacks */ + ot->exec = nlaedit_move_up_exec; + ot->poll = nlaop_poll_tweakmode_off; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Move Strips Down Operator + * + * Tries to move the selected strips into the track above if possible. + * \{ */ + +static int nlaedit_move_down_exec(bContext *C, wmOperator *UNUSED(op)) +{ + bAnimContext ac; + + ListBase anim_data = {NULL, NULL}; + bAnimListElem *ale; + int filter; + + /* get editor data */ + if (ANIM_animdata_get_context(C, &ac) == 0) { + return OPERATOR_CANCELLED; + } + + /* get a list of the editable tracks being shown in the NLA */ + filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE | ANIMFILTER_FOREDIT | + ANIMFILTER_FCURVESONLY); + ANIM_animdata_filter(&ac, &anim_data, filter, ac.data, ac.datatype); + + /* loop through the tracks in normal order, since we're pushing strips down, + * strips won't get operated on twice + */ + for (ale = anim_data.first; ale; ale = ale->next) { + NlaTrack *nlt = (NlaTrack *)ale->data; + NlaTrack *nltp = nlt->prev; + NlaStrip *strip, *stripn; + + const bool is_liboverride = ID_IS_OVERRIDE_LIBRARY(ale->id); + + /* if this track has no tracks before it, skip for now... */ + if (nltp == NULL) { + continue; + } + + if (BKE_nlatrack_is_nonlocal_in_liboverride(ale->id, nlt) || + BKE_nlatrack_is_nonlocal_in_liboverride(ale->id, nltp)) { + /* No moving of strips in non-local tracks of override data. */ + continue; + } + + /* for every selected strip, try to move */ + for (strip = nlt->strips.first; strip; strip = stripn) { + stripn = strip->next; + + if (strip->flag & NLASTRIP_FLAG_SELECT) { + /* check if the track below has room for this strip */ + if (BKE_nlatrack_has_space(nltp, strip->start, strip->end)) { + /* remove from its current track, and add to the one above + * (it 'should' work, so no need to worry) */ + BKE_nlatrack_remove_strip(nlt, strip); + BKE_nlatrack_add_strip(nltp, strip, is_liboverride); + } + } + } + } + + /* free temp data */ + ANIM_animdata_freelist(&anim_data); + + /* refresh auto strip properties */ + ED_nla_postop_refresh(&ac); + + /* set notifier that things have changed */ + WM_event_add_notifier(C, NC_ANIMATION | ND_NLA | NA_EDITED, NULL); + WM_event_add_notifier(C, NC_ANIMATION | ND_NLA_ORDER, NULL); + + /* done */ + return OPERATOR_FINISHED; +} + +void NLA_OT_move_down(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Move Strips Down"; + ot->idname = "NLA_OT_move_down"; + ot->description = "Move selected strips down a track if there's room"; + + /* api callbacks */ + ot->exec = nlaedit_move_down_exec; + ot->poll = nlaop_poll_tweakmode_off; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Sync Action Length Operator + * + * Recalculate the extents of the action ranges used for the selected strips. + * \{ */ + +static int nlaedit_sync_actlen_exec(bContext *C, wmOperator *op) +{ + bAnimContext ac; + + ListBase anim_data = {NULL, NULL}; + bAnimListElem *ale; + int filter; + const bool active_only = RNA_boolean_get(op->ptr, "active"); + + /* get editor data */ + if (ANIM_animdata_get_context(C, &ac) == 0) { + return OPERATOR_CANCELLED; + } + + /* get a list of the editable tracks being shown in the NLA */ + filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE | ANIMFILTER_FOREDIT | + ANIMFILTER_FCURVESONLY); + if (active_only) { + filter |= ANIMFILTER_ACTIVE; + } + ANIM_animdata_filter(&ac, &anim_data, filter, ac.data, ac.datatype); + + /* for each NLA-Track, apply scale of all selected strips */ + for (ale = anim_data.first; ale; ale = ale->next) { + NlaTrack *nlt = (NlaTrack *)ale->data; + NlaStrip *strip; + + for (strip = nlt->strips.first; strip; strip = strip->next) { + /* strip selection/active status check */ + if (active_only) { + if ((strip->flag & NLASTRIP_FLAG_ACTIVE) == 0) { + continue; + } + } + else { + if ((strip->flag & NLASTRIP_FLAG_SELECT) == 0) { + continue; + } + } + + /* must be action-clip only (transitions don't have scale) */ + if (strip->type == NLASTRIP_TYPE_CLIP) { + if (strip->act == NULL) { + continue; + } + + BKE_nlastrip_recalculate_bounds_sync_action(strip); + + ale->update |= ANIM_UPDATE_DEPS; + } + } + } + + /* free temp data */ + ANIM_animdata_update(&ac, &anim_data); + ANIM_animdata_freelist(&anim_data); + + /* set notifier that things have changed */ + WM_event_add_notifier(C, NC_ANIMATION | ND_NLA | NA_EDITED, NULL); + + /* done */ + return OPERATOR_FINISHED; +} + +void NLA_OT_action_sync_length(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Sync Action Length"; + ot->idname = "NLA_OT_action_sync_length"; + ot->description = + "Synchronize the length of the referenced Action with the length used in the strip"; + + /* api callbacks */ + ot->exec = nlaedit_sync_actlen_exec; + ot->poll = nlaop_poll_tweakmode_off; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + /* properties */ + ot->prop = RNA_def_boolean(ot->srna, + "active", + 1, + "Active Strip Only", + "Only sync the active length for the active strip"); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Make Single User + * + * Ensure that each strip has its own action. + * \{ */ + +static int nlaedit_make_single_user_exec(bContext *C, wmOperator *UNUSED(op)) +{ + Main *bmain = CTX_data_main(C); + bAnimContext ac; + + ListBase anim_data = {NULL, NULL}; + bAnimListElem *ale; + int filter; + bool copied = false; + + /* get editor data */ + if (ANIM_animdata_get_context(C, &ac) == 0) { + return OPERATOR_CANCELLED; + } + + /* get a list of the editable tracks being shown in the NLA */ + filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE | ANIMFILTER_FOREDIT | + ANIMFILTER_FCURVESONLY); + ANIM_animdata_filter(&ac, &anim_data, filter, ac.data, ac.datatype); + + /* Ensure that each action used only has a single user + * - This is done in reverse order so that the original strips are + * likely to still get to keep their action + */ + for (ale = anim_data.last; ale; ale = ale->prev) { + NlaTrack *nlt = (NlaTrack *)ale->data; + NlaStrip *strip; + + for (strip = nlt->strips.last; strip; strip = strip->prev) { + /* must be action-clip only (as only these have actions) */ + if ((strip->flag & NLASTRIP_FLAG_SELECT) && (strip->type == NLASTRIP_TYPE_CLIP)) { + if (strip->act == NULL) { + continue; + } + + /* multi-user? */ + if (ID_REAL_USERS(strip->act) > 1) { + /* make a new copy of the action for us to use (it will have 1 user already) */ + bAction *new_action = (bAction *)BKE_id_copy(bmain, &strip->act->id); + + /* decrement user count of our existing action */ + id_us_min(&strip->act->id); + + /* switch to the new copy */ + strip->act = new_action; + + ale->update |= ANIM_UPDATE_DEPS; + copied = true; + } + } + } + } + + /* free temp data */ + ANIM_animdata_update(&ac, &anim_data); + ANIM_animdata_freelist(&anim_data); + + if (copied) { + DEG_relations_tag_update(ac.bmain); + } + + /* set notifier that things have changed */ + WM_event_add_notifier(C, NC_ANIMATION | ND_NLA | NA_ADDED, NULL); + + /* done */ + return OPERATOR_FINISHED; +} + +void NLA_OT_make_single_user(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Make Single User"; + ot->idname = "NLA_OT_make_single_user"; + ot->description = "Ensure that each action is only used once in the set of strips selected"; + + /* api callbacks */ + ot->invoke = WM_operator_confirm; + ot->exec = nlaedit_make_single_user_exec; + ot->poll = nlaop_poll_tweakmode_off; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Apply Scale Operator + * + * Reset the scaling of the selected strips to 1.0f. + * \{ */ + +/* apply scaling to keyframe */ +static short bezt_apply_nlamapping(KeyframeEditData *ked, BezTriple *bezt) +{ + /* NLA-strip which has this scaling is stored in ked->data */ + NlaStrip *strip = (NlaStrip *)ked->data; + + /* adjust all the times */ + bezt->vec[0][0] = nlastrip_get_frame(strip, bezt->vec[0][0], NLATIME_CONVERT_MAP); + bezt->vec[1][0] = nlastrip_get_frame(strip, bezt->vec[1][0], NLATIME_CONVERT_MAP); + bezt->vec[2][0] = nlastrip_get_frame(strip, bezt->vec[2][0], NLATIME_CONVERT_MAP); + + /* nothing to return or else we exit */ + return 0; +} + +static int nlaedit_apply_scale_exec(bContext *C, wmOperator *UNUSED(op)) +{ + Main *bmain = CTX_data_main(C); + bAnimContext ac; + + ListBase anim_data = {NULL, NULL}; + bAnimListElem *ale; + int filter; + bool copied = false; + + KeyframeEditData ked = {{NULL}}; + + /* get editor data */ + if (ANIM_animdata_get_context(C, &ac) == 0) { + return OPERATOR_CANCELLED; + } + + /* get a list of the editable tracks being shown in the NLA */ + filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE | ANIMFILTER_FOREDIT | + ANIMFILTER_FCURVESONLY); + ANIM_animdata_filter(&ac, &anim_data, filter, ac.data, ac.datatype); + + /* for each NLA-Track, apply scale of all selected strips */ + for (ale = anim_data.first; ale; ale = ale->next) { + NlaTrack *nlt = (NlaTrack *)ale->data; + NlaStrip *strip; + + for (strip = nlt->strips.first; strip; strip = strip->next) { + /* strip must be selected, and must be action-clip only + * (transitions don't have scale) */ + if ((strip->flag & NLASTRIP_FLAG_SELECT) && (strip->type == NLASTRIP_TYPE_CLIP)) { + if (strip->act == NULL || ID_IS_OVERRIDE_LIBRARY(strip->act) || ID_IS_LINKED(strip->act)) { + continue; + } + /* if the referenced action is used by other strips, + * make this strip use its own copy */ + if (strip->act->id.us > 1) { + /* make a copy of the Action to work on */ + bAction *act = (bAction *)BKE_id_copy(bmain, &strip->act->id); + + /* set this as the new referenced action, + * decrementing the users of the old one */ + id_us_min(&strip->act->id); + strip->act = act; + + copied = true; + } + + /* setup iterator, and iterate over all the keyframes in the action, + * applying this scaling */ + ked.data = strip; + ANIM_animchanneldata_keyframes_loop(&ked, + ac.ads, + strip->act, + ALE_ACT, + NULL, + bezt_apply_nlamapping, + BKE_fcurve_handles_recalc); + + /* clear scale of strip now that it has been applied, + * and recalculate the extents of the action now that it has been scaled + * but leave everything else alone + */ + const float start = nlastrip_get_frame(strip, strip->actstart, NLATIME_CONVERT_MAP); + const float end = nlastrip_get_frame(strip, strip->actend, NLATIME_CONVERT_MAP); + + if (strip->act->flag & ACT_FRAME_RANGE) { + strip->act->frame_start = nlastrip_get_frame( + strip, strip->act->frame_start, NLATIME_CONVERT_MAP); + strip->act->frame_end = nlastrip_get_frame( + strip, strip->act->frame_end, NLATIME_CONVERT_MAP); + } + + strip->scale = 1.0f; + strip->actstart = start; + strip->actend = end; + + ale->update |= ANIM_UPDATE_DEPS; + } + } + } + + /* free temp data */ + ANIM_animdata_update(&ac, &anim_data); + ANIM_animdata_freelist(&anim_data); + + if (copied) { + DEG_relations_tag_update(ac.bmain); + } + + /* set notifier that things have changed */ + WM_event_add_notifier(C, NC_ANIMATION | ND_NLA | NA_EDITED, NULL); + + /* done */ + return OPERATOR_FINISHED; +} + +void NLA_OT_apply_scale(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Apply Scale"; + ot->idname = "NLA_OT_apply_scale"; + ot->description = "Apply scaling of selected strips to their referenced Actions"; + + /* api callbacks */ + ot->exec = nlaedit_apply_scale_exec; + ot->poll = nlaop_poll_tweakmode_off; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Clear Scale Operator + * + * Reset the scaling of the selected strips to 1.0f. + * \{ */ + +static int nlaedit_clear_scale_exec(bContext *C, wmOperator *UNUSED(op)) +{ + bAnimContext ac; + + ListBase anim_data = {NULL, NULL}; + bAnimListElem *ale; + int filter; + + /* get editor data */ + if (ANIM_animdata_get_context(C, &ac) == 0) { + return OPERATOR_CANCELLED; + } + + /* get a list of the editable tracks being shown in the NLA */ + filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE | ANIMFILTER_FOREDIT | + ANIMFILTER_FCURVESONLY); + ANIM_animdata_filter(&ac, &anim_data, filter, ac.data, ac.datatype); + + /* for each NLA-Track, reset scale of all selected strips */ + for (ale = anim_data.first; ale; ale = ale->next) { + NlaTrack *nlt = (NlaTrack *)ale->data; + NlaStrip *strip; + + for (strip = nlt->strips.first; strip; strip = strip->next) { + /* strip must be selected, and must be action-clip only + * (transitions don't have scale) */ + if ((strip->flag & NLASTRIP_FLAG_SELECT) && (strip->type == NLASTRIP_TYPE_CLIP)) { + PointerRNA strip_ptr; + + RNA_pointer_create(NULL, &RNA_NlaStrip, strip, &strip_ptr); + RNA_float_set(&strip_ptr, "scale", 1.0f); + } + } + } + + /* free temp data */ + ANIM_animdata_freelist(&anim_data); + + /* refresh auto strip properties */ + ED_nla_postop_refresh(&ac); + + /* set notifier that things have changed */ + WM_event_add_notifier(C, NC_ANIMATION | ND_NLA | NA_EDITED, NULL); + + /* done */ + return OPERATOR_FINISHED; +} + +void NLA_OT_clear_scale(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Clear Scale"; + ot->idname = "NLA_OT_clear_scale"; + ot->description = "Reset scaling of selected strips"; + + /* api callbacks */ + ot->exec = nlaedit_clear_scale_exec; + ot->poll = nlaop_poll_tweakmode_off; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Snap Strips Operator + * + * Moves the start-point of the selected strips to the specified places. + * \{ */ + +/* defines for snap keyframes tool */ +static const EnumPropertyItem prop_nlaedit_snap_types[] = { + {NLAEDIT_SNAP_CFRA, "CFRA", 0, "Selection to Current Frame", ""}, + /* XXX as single entry? */ + {NLAEDIT_SNAP_NEAREST_FRAME, "NEAREST_FRAME", 0, "Selection to Nearest Frame", ""}, + /* XXX as single entry? */ + {NLAEDIT_SNAP_NEAREST_SECOND, "NEAREST_SECOND", 0, "Selection to Nearest Second", ""}, + {NLAEDIT_SNAP_NEAREST_MARKER, "NEAREST_MARKER", 0, "Selection to Nearest Marker", ""}, + {0, NULL, 0, NULL, NULL}, +}; + +static int nlaedit_snap_exec(bContext *C, wmOperator *op) +{ + bAnimContext ac; + + ListBase anim_data = {NULL, NULL}; + bAnimListElem *ale; + int filter; + + Scene *scene; + int mode = RNA_enum_get(op->ptr, "type"); + float secf; + + /* get editor data */ + if (ANIM_animdata_get_context(C, &ac) == 0) { + return OPERATOR_CANCELLED; + } + + /* get a list of the editable tracks being shown in the NLA */ + filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE | ANIMFILTER_FOREDIT | + ANIMFILTER_FCURVESONLY); + ANIM_animdata_filter(&ac, &anim_data, filter, ac.data, ac.datatype); + + /* get some necessary vars */ + scene = ac.scene; + secf = (float)FPS; + + bool any_added = false; + + /* since we may add tracks, perform this in reverse order */ + for (ale = anim_data.last; ale; ale = ale->prev) { + ListBase tmp_strips = {NULL, NULL}; + AnimData *adt = ale->adt; + NlaTrack *nlt = (NlaTrack *)ale->data; + NlaStrip *strip, *stripn; + NlaTrack *track; + + const bool is_liboverride = ID_IS_OVERRIDE_LIBRARY(ale->id); + + /* create meta-strips from the continuous chains of selected strips */ + BKE_nlastrips_make_metas(&nlt->strips, 1); + + /* apply the snapping to all the temp meta-strips, then put them in a separate list to be added + * back to the original only if they still fit + */ + for (strip = nlt->strips.first; strip; strip = stripn) { + stripn = strip->next; + + if (strip->flag & NLASTRIP_FLAG_TEMP_META) { + float start, end; + + /* get the existing end-points */ + start = strip->start; + end = strip->end; + + /* calculate new start position based on snapping mode */ + switch (mode) { + case NLAEDIT_SNAP_CFRA: /* to current frame */ + strip->start = (float)scene->r.cfra; + break; + case NLAEDIT_SNAP_NEAREST_FRAME: /* to nearest frame */ + strip->start = floorf(start + 0.5f); + break; + case NLAEDIT_SNAP_NEAREST_SECOND: /* to nearest second */ + strip->start = floorf(start / secf + 0.5f) * secf; + break; + case NLAEDIT_SNAP_NEAREST_MARKER: /* to nearest marker */ + strip->start = (float)ED_markers_find_nearest_marker_time(ac.markers, start); + break; + default: /* just in case... no snapping */ + strip->start = start; + break; + } + + /* get new endpoint based on start-point (and old length) */ + strip->end = strip->start + (end - start); + + /* apply transforms to meta-strip to its children */ + BKE_nlameta_flush_transforms(strip); + + /* remove strip from track, and add to the temp buffer */ + BLI_remlink(&nlt->strips, strip); + BLI_addtail(&tmp_strips, strip); + } + } + + /* try adding each meta-strip back to the track one at a time, to make sure they'll fit */ + for (strip = tmp_strips.first; strip; strip = stripn) { + stripn = strip->next; + + /* remove from temp-strips list */ + BLI_remlink(&tmp_strips, strip); + + /* in case there's no space in the current track, try adding */ + if (BKE_nlatrack_add_strip(nlt, strip, is_liboverride) == 0) { + /* need to add a new track above the current one */ + track = BKE_nlatrack_add(adt, nlt, is_liboverride); + BKE_nlatrack_add_strip(track, strip, is_liboverride); + + /* clear temp meta-strips on this new track, + * as we may not be able to get back to it */ + BKE_nlastrips_clear_metas(&track->strips, 0, 1); + + any_added = true; + } + } + + /* remove the meta-strips now that we're done */ + BKE_nlastrips_clear_metas(&nlt->strips, 0, 1); + + /* tag for recalculating the animation */ + ale->update |= ANIM_UPDATE_DEPS; + } + + /* cleanup */ + ANIM_animdata_update(&ac, &anim_data); + ANIM_animdata_freelist(&anim_data); + + /* refresh auto strip properties */ + ED_nla_postop_refresh(&ac); + + /* set notifier that things have changed */ + WM_event_add_notifier(C, NC_ANIMATION | ND_NLA | NA_EDITED, NULL); + if (any_added) { + WM_event_add_notifier(C, NC_ANIMATION | ND_NLA | NA_ADDED, NULL); + } + + /* done */ + return OPERATOR_FINISHED; +} + +void NLA_OT_snap(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Snap Strips"; + ot->idname = "NLA_OT_snap"; + ot->description = "Move start of strips to specified time"; + + /* api callbacks */ + ot->invoke = WM_menu_invoke; + ot->exec = nlaedit_snap_exec; + ot->poll = nlaop_poll_tweakmode_off; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + /* properties */ + ot->prop = RNA_def_enum(ot->srna, "type", prop_nlaedit_snap_types, 0, "Type", ""); +} + +/** \} */ + +/* NLA Modifiers */ + +/* -------------------------------------------------------------------- */ +/** \name Add F-Modifier Operator + * \{ */ + +static const EnumPropertyItem *nla_fmodifier_itemf(bContext *C, + PointerRNA *UNUSED(ptr), + PropertyRNA *UNUSED(prop), + bool *r_free) +{ + EnumPropertyItem *item = NULL; + int totitem = 0; + int i = 0; + + if (C == NULL) { + return rna_enum_fmodifier_type_items; + } + + /* start from 1 to skip the 'Invalid' modifier type */ + for (i = 1; i < FMODIFIER_NUM_TYPES; i++) { + const FModifierTypeInfo *fmi = get_fmodifier_typeinfo(i); + int index; + + /* check if modifier is valid for this context */ + if (fmi == NULL) { + continue; + } + if (i == FMODIFIER_TYPE_CYCLES) { /* we already have repeat... */ + continue; + } + + index = RNA_enum_from_value(rna_enum_fmodifier_type_items, fmi->type); + if (index != -1) { /* Not all types are implemented yet... */ + RNA_enum_item_add(&item, &totitem, &rna_enum_fmodifier_type_items[index]); + } + } + + RNA_enum_item_end(&item, &totitem); + *r_free = true; + + return item; +} + +static int nla_fmodifier_add_exec(bContext *C, wmOperator *op) +{ + bAnimContext ac; + + ListBase anim_data = {NULL, NULL}; + bAnimListElem *ale; + int filter; + + FModifier *fcm; + int type = RNA_enum_get(op->ptr, "type"); + const bool active_only = RNA_boolean_get(op->ptr, "only_active"); + + /* get editor data */ + if (ANIM_animdata_get_context(C, &ac) == 0) { + return OPERATOR_CANCELLED; + } + + /* get a list of the editable tracks being shown in the NLA */ + filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE | ANIMFILTER_FOREDIT | + ANIMFILTER_FCURVESONLY); + ANIM_animdata_filter(&ac, &anim_data, filter, ac.data, ac.datatype); + + /* for each NLA-Track, add the specified modifier to all selected strips */ + for (ale = anim_data.first; ale; ale = ale->next) { + NlaTrack *nlt = (NlaTrack *)ale->data; + NlaStrip *strip; + + if (BKE_nlatrack_is_nonlocal_in_liboverride(ale->id, nlt)) { + /* No adding f-modifiers to strips in non-local tracks of override data. */ + continue; + } + + for (strip = nlt->strips.first; strip; strip = strip->next) { + /* can F-Modifier be added to the current strip? */ + if (active_only) { + /* if not active, cannot add since we're only adding to active strip */ + if ((strip->flag & NLASTRIP_FLAG_ACTIVE) == 0) { + continue; + } + } + else { + /* strip must be selected, since we're not just doing active */ + if ((strip->flag & NLASTRIP_FLAG_SELECT) == 0) { + continue; + } + } + + /* sound clips are not affected by FModifiers */ + if (strip->type == NLASTRIP_TYPE_SOUND) { + continue; + } + + /* add F-Modifier of specified type to selected, and make it the active one */ + fcm = add_fmodifier(&strip->modifiers, type, NULL); + + if (fcm) { + set_active_fmodifier(&strip->modifiers, fcm); + ale->update |= ANIM_UPDATE_DEPS; + } + else { + BKE_reportf(op->reports, + RPT_ERROR, + "Modifier could not be added to (%s : %s) (see console for details)", + nlt->name, + strip->name); + } + } + } + + /* free temp data */ + ANIM_animdata_update(&ac, &anim_data); + ANIM_animdata_freelist(&anim_data); + + /* set notifier that things have changed */ + WM_event_add_notifier(C, NC_ANIMATION | ND_NLA | NA_EDITED, NULL); + + /* done */ + return OPERATOR_FINISHED; +} + +void NLA_OT_fmodifier_add(wmOperatorType *ot) +{ + PropertyRNA *prop; + + /* identifiers */ + ot->name = "Add F-Modifier"; + ot->idname = "NLA_OT_fmodifier_add"; + ot->description = "Add F-Modifier to the active/selected NLA-Strips"; + + /* api callbacks */ + ot->invoke = WM_menu_invoke; + ot->exec = nla_fmodifier_add_exec; + ot->poll = nlaop_poll_tweakmode_off; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + /* id-props */ + ot->prop = RNA_def_enum(ot->srna, "type", rna_enum_fmodifier_type_items, 0, "Type", ""); + RNA_def_property_translation_context(ot->prop, BLT_I18NCONTEXT_ID_ACTION); + RNA_def_enum_funcs(ot->prop, nla_fmodifier_itemf); + + prop = RNA_def_boolean(ot->srna, + "only_active", + true, + "Only Active", + "Only add a F-Modifier of the specified type to the active strip"); + RNA_def_property_translation_context(prop, BLT_I18NCONTEXT_ID_ACTION); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Copy F-Modifiers Operator + * \{ */ + +static int nla_fmodifier_copy_exec(bContext *C, wmOperator *op) +{ + bAnimContext ac; + ListBase anim_data = {NULL, NULL}; + bAnimListElem *ale; + int filter; + bool ok = false; + + /* get editor data */ + if (ANIM_animdata_get_context(C, &ac) == 0) { + return OPERATOR_CANCELLED; + } + + /* clear buffer first */ + ANIM_fmodifiers_copybuf_free(); + + /* get a list of the editable tracks being shown in the NLA */ + filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE | ANIMFILTER_FOREDIT | + ANIMFILTER_FCURVESONLY); + ANIM_animdata_filter(&ac, &anim_data, filter, ac.data, ac.datatype); + + /* for each NLA-Track, add the specified modifier to all selected strips */ + for (ale = anim_data.first; ale; ale = ale->next) { + NlaTrack *nlt = (NlaTrack *)ale->data; + NlaStrip *strip; + + for (strip = nlt->strips.first; strip; strip = strip->next) { + /* only add F-Modifier if on active strip? */ + if ((strip->flag & NLASTRIP_FLAG_ACTIVE) == 0) { + continue; + } + + /* TODO: when 'active' vs 'all' boolean is added, change last param! */ + ok |= ANIM_fmodifiers_copy_to_buf(&strip->modifiers, 0); + } + } + + /* free temp data */ + ANIM_animdata_freelist(&anim_data); + + /* successful or not? */ + if (ok == 0) { + BKE_report(op->reports, RPT_ERROR, "No F-Modifiers available to be copied"); + return OPERATOR_CANCELLED; + } + + /* no updates needed - copy is non-destructive operation */ + return OPERATOR_FINISHED; +} + +void NLA_OT_fmodifier_copy(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Copy F-Modifiers"; + ot->idname = "NLA_OT_fmodifier_copy"; + ot->description = "Copy the F-Modifier(s) of the active NLA-Strip"; + + /* api callbacks */ + ot->exec = nla_fmodifier_copy_exec; + ot->poll = nlaop_poll_tweakmode_off; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + /* id-props */ +#if 0 + ot->prop = RNA_def_boolean(ot->srna, + "all", + 1, + "All F-Modifiers", + "Copy all the F-Modifiers, instead of just the active one"); +#endif +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Paste F-Modifiers Operator + * \{ */ + +static int nla_fmodifier_paste_exec(bContext *C, wmOperator *op) +{ + bAnimContext ac; + ListBase anim_data = {NULL, NULL}; + bAnimListElem *ale; + int filter, ok = 0; + + const bool active_only = RNA_boolean_get(op->ptr, "only_active"); + const bool replace = RNA_boolean_get(op->ptr, "replace"); + + /* get editor data */ + if (ANIM_animdata_get_context(C, &ac) == 0) { + return OPERATOR_CANCELLED; + } + + /* get a list of the editable tracks being shown in the NLA */ + filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE | ANIMFILTER_FOREDIT | + ANIMFILTER_NODUPLIS | ANIMFILTER_FCURVESONLY); + ANIM_animdata_filter(&ac, &anim_data, filter, ac.data, ac.datatype); + + /* for each NLA-Track, add the specified modifier to all selected strips */ + for (ale = anim_data.first; ale; ale = ale->next) { + NlaTrack *nlt = (NlaTrack *)ale->data; + NlaStrip *strip; + + if (BKE_nlatrack_is_nonlocal_in_liboverride(ale->id, nlt)) { + /* No pasting in non-local tracks of override data. */ + continue; + } + + for (strip = nlt->strips.first; strip; strip = strip->next) { + /* can F-Modifier be added to the current strip? */ + if (active_only) { + /* if not active, cannot add since we're only adding to active strip */ + if ((strip->flag & NLASTRIP_FLAG_ACTIVE) == 0) { + continue; + } + } + else { + /* strip must be selected, since we're not just doing active */ + if ((strip->flag & NLASTRIP_FLAG_SELECT) == 0) { + continue; + } + } + + /* paste FModifiers from buffer */ + ok += ANIM_fmodifiers_paste_from_buf(&strip->modifiers, replace, NULL); + ale->update |= ANIM_UPDATE_DEPS; + } + } + + /* clean up */ + ANIM_animdata_update(&ac, &anim_data); + ANIM_animdata_freelist(&anim_data); + + /* successful or not? */ + if (ok) { + WM_event_add_notifier(C, NC_ANIMATION | ND_NLA | NA_EDITED, NULL); + return OPERATOR_FINISHED; + } + + BKE_report(op->reports, RPT_ERROR, "No F-Modifiers to paste"); + return OPERATOR_CANCELLED; +} + +void NLA_OT_fmodifier_paste(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Paste F-Modifiers"; + ot->idname = "NLA_OT_fmodifier_paste"; + ot->description = "Add copied F-Modifiers to the selected NLA-Strips"; + + /* api callbacks */ + ot->exec = nla_fmodifier_paste_exec; + ot->poll = nlaop_poll_tweakmode_off; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + /* properties */ + ot->prop = RNA_def_boolean( + ot->srna, "only_active", true, "Only Active", "Only paste F-Modifiers on active strip"); + RNA_def_property_translation_context(ot->prop, BLT_I18NCONTEXT_ID_ACTION); + + RNA_def_boolean( + ot->srna, + "replace", + false, + "Replace Existing", + "Replace existing F-Modifiers, instead of just appending to the end of the existing list"); +} + +/** \} */ diff --git a/source/blender/editors/space_nla/nla_edit.c.rej b/source/blender/editors/space_nla/nla_edit.c.rej new file mode 100644 index 00000000000..68706320397 --- /dev/null +++ b/source/blender/editors/space_nla/nla_edit.c.rej @@ -0,0 +1,71 @@ +*************** +*** 1310,1324 **** + if (strip->flag & NLASTRIP_FLAG_SELECT) { + /* if a strip either side of this was a transition, delete those too */ + if ((strip->prev) && (strip->prev->type == NLASTRIP_TYPE_TRANSITION)) { +- BKE_nlastrip_free(&nlt->strips, strip->prev, true); + } + if ((nstrip) && (nstrip->type == NLASTRIP_TYPE_TRANSITION)) { + nstrip = nstrip->next; +- BKE_nlastrip_free(&nlt->strips, strip->next, true); + } + + /* finally, delete this strip */ +- BKE_nlastrip_free(&nlt->strips, strip, true); + } + } + } +--- 1306,1320 ---- + if (strip->flag & NLASTRIP_FLAG_SELECT) { + /* if a strip either side of this was a transition, delete those too */ + if ((strip->prev) && (strip->prev->type == NLASTRIP_TYPE_TRANSITION)) { ++ BKE_nlastrip_remove_and_free(&nlt->strips, strip->prev, true); + } + if ((nstrip) && (nstrip->type == NLASTRIP_TYPE_TRANSITION)) { + nstrip = nstrip->next; ++ BKE_nlastrip_remove_and_free(&nlt->strips, strip->next, true); + } + + /* finally, delete this strip */ ++ BKE_nlastrip_remove_and_free(&nlt->strips, strip, true); + } + } + } +*************** +*** 1824,1831 **** + if (BKE_nlatrack_has_space(nltn, strip->start, strip->end)) { + /* remove from its current track, and add to the one above + * (it 'should' work, so no need to worry) */ +- BLI_remlink(&nlt->strips, strip); +- BKE_nlatrack_add_strip(nltn, strip, is_liboverride); + } + } + } +--- 1818,1825 ---- + if (BKE_nlatrack_has_space(nltn, strip->start, strip->end)) { + /* remove from its current track, and add to the one above + * (it 'should' work, so no need to worry) */ ++ BKE_nlatrack_remove_strip(nlt, strip); ++ BKE_nlatrack_add_strip(nltn, strip); + } + } + } +*************** +*** 1916,1923 **** + if (BKE_nlatrack_has_space(nltp, strip->start, strip->end)) { + /* remove from its current track, and add to the one above + * (it 'should' work, so no need to worry) */ +- BLI_remlink(&nlt->strips, strip); +- BKE_nlatrack_add_strip(nltp, strip, is_liboverride); + } + } + } +--- 1908,1915 ---- + if (BKE_nlatrack_has_space(nltp, strip->start, strip->end)) { + /* remove from its current track, and add to the one above + * (it 'should' work, so no need to worry) */ ++ BKE_nlatrack_remove_strip(nlt, strip); ++ BKE_nlatrack_add_strip(nltp, strip); + } + } + } diff --git a/source/blender/editors/transform/transform_convert_nla.c b/source/blender/editors/transform/transform_convert_nla.c index a9b1d07e7b4..341a6a69717 100644 --- a/source/blender/editors/transform/transform_convert_nla.c +++ b/source/blender/editors/transform/transform_convert_nla.c @@ -434,7 +434,6 @@ static void recalcData_nla(TransInfo *t) * as only one may have been altered by transform if only 1 handle moved. */ /* In LibOverride case, we cannot move strips across tracks that come from the linked data. */ - const bool is_liboverride = ID_IS_OVERRIDE_LIBRARY(tdn->id); if (BKE_nlatrack_is_nonlocal_in_liboverride(tdn->id, tdn->nlt)) { continue; } @@ -489,7 +488,7 @@ static void recalcData_nla(TransInfo *t) } } } - } + } } /** \} */ diff --git a/source/blender/editors/transform/transform_convert_nla.c.orig b/source/blender/editors/transform/transform_convert_nla.c.orig new file mode 100644 index 00000000000..a9b1d07e7b4 --- /dev/null +++ b/source/blender/editors/transform/transform_convert_nla.c.orig @@ -0,0 +1,549 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later + * Copyright 2001-2002 NaN Holding BV. All rights reserved. */ + +/** \file + * \ingroup edtransform + */ + +#include "DNA_anim_types.h" +#include "DNA_space_types.h" + +#include "MEM_guardedalloc.h" + +#include "BLI_listbase.h" +#include "BLI_math.h" + +#include "BKE_context.h" +#include "BKE_nla.h" + +#include "ED_anim_api.h" +#include "ED_markers.h" + +#include "WM_api.h" +#include "WM_types.h" + +#include "RNA_access.h" +#include "RNA_prototypes.h" + +#include "transform.h" +#include "transform_convert.h" +#include "transform_mode.h" +#include "transform_snap.h" + +/** Used for NLA transform (stored in #TransData.extra pointer). */ +typedef struct TransDataNla { + /** ID-block NLA-data is attached to. */ + ID *id; + + /** Original NLA-Track that the strip belongs to. */ + struct NlaTrack *oldTrack; + /** Current NLA-Track that the strip belongs to. */ + struct NlaTrack *nlt; + + /** NLA-strip this data represents. */ + struct NlaStrip *strip; + + /* dummy values for transform to write in - must have 3 elements... */ + /** start handle. */ + float h1[3]; + /** end handle. */ + float h2[3]; + + /** index of track that strip is currently in. */ + int trackIndex; + /** handle-index: 0 for dummy entry, -1 for start, 1 for end, 2 for both ends. */ + int handle; +} TransDataNla; + +/* -------------------------------------------------------------------- */ +/** \name Transform application to NLA strips + * \{ */ + +/** + * \brief Applies a translation to the given #NlaStrip. + * \param strip_rna_ptr: The RNA pointer of the NLA strip to modify. + * \param transdata: The transformation info structure. + */ +static void applyTransformNLA_translation(PointerRNA *strip_rna_ptr, const TransDataNla *transdata) +{ + /* NOTE: we write these twice to avoid truncation errors which can arise when + * moving the strips a large distance using numeric input #33852. + */ + RNA_float_set(strip_rna_ptr, "frame_start", transdata->h1[0]); + RNA_float_set(strip_rna_ptr, "frame_end", transdata->h2[0]); + + RNA_float_set(strip_rna_ptr, "frame_start", transdata->h1[0]); + RNA_float_set(strip_rna_ptr, "frame_end", transdata->h2[0]); +} + +static void applyTransformNLA_timeScale(PointerRNA *strip_rna_ptr, const float value) +{ + RNA_float_set(strip_rna_ptr, "scale", value); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name NLA Transform Creation + * \{ */ + +static void createTransNlaData(bContext *C, TransInfo *t) +{ + Scene *scene = t->scene; + SpaceNla *snla = NULL; + TransData *td = NULL; + TransDataNla *tdn = NULL; + + bAnimContext ac; + ListBase anim_data = {NULL, NULL}; + bAnimListElem *ale; + int filter; + + int count = 0; + + TransDataContainer *tc = TRANS_DATA_CONTAINER_FIRST_SINGLE(t); + + /* determine what type of data we are operating on */ + if (ANIM_animdata_get_context(C, &ac) == 0) { + return; + } + snla = (SpaceNla *)ac.sl; + + /* filter data */ + filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE | ANIMFILTER_FOREDIT | + ANIMFILTER_FCURVESONLY); + ANIM_animdata_filter(&ac, &anim_data, filter, ac.data, ac.datatype); + + /* which side of the current frame should be allowed */ + if (t->mode == TFM_TIME_EXTEND) { + t->frame_side = transform_convert_frame_side_dir_get(t, (float)scene->r.cfra); + } + else { + /* normal transform - both sides of current frame are considered */ + t->frame_side = 'B'; + } + + /* loop 1: count how many strips are selected (consider each strip as 2 points) */ + for (ale = anim_data.first; ale; ale = ale->next) { + NlaTrack *nlt = (NlaTrack *)ale->data; + NlaStrip *strip; + + /* make some meta-strips for chains of selected strips */ + BKE_nlastrips_make_metas(&nlt->strips, 1); + + /* only consider selected strips */ + for (strip = nlt->strips.first; strip; strip = strip->next) { + /* TODO: we can make strips have handles later on. */ + /* transition strips can't get directly transformed */ + if (strip->type != NLASTRIP_TYPE_TRANSITION) { + if (strip->flag & NLASTRIP_FLAG_SELECT) { + if (FrameOnMouseSide(t->frame_side, strip->start, (float)scene->r.cfra)) { + count++; + } + if (FrameOnMouseSide(t->frame_side, strip->end, (float)scene->r.cfra)) { + count++; + } + } + } + } + } + + /* stop if trying to build list if nothing selected */ + if (count == 0) { + /* clear temp metas that may have been created but aren't needed now + * because they fell on the wrong side of scene->r.cfra + */ + for (ale = anim_data.first; ale; ale = ale->next) { + NlaTrack *nlt = (NlaTrack *)ale->data; + BKE_nlastrips_clear_metas(&nlt->strips, 0, 1); + } + + /* cleanup temp list */ + ANIM_animdata_freelist(&anim_data); + return; + } + + /* allocate memory for data */ + tc->data_len = count; + + tc->data = MEM_callocN(tc->data_len * sizeof(TransData), "TransData(NLA Editor)"); + td = tc->data; + tc->custom.type.data = tdn = MEM_callocN(tc->data_len * sizeof(TransDataNla), + "TransDataNla (NLA Editor)"); + tc->custom.type.use_free = true; + + /* loop 2: build transdata array */ + for (ale = anim_data.first; ale; ale = ale->next) { + /* only if a real NLA-track */ + if (ale->type == ANIMTYPE_NLATRACK) { + AnimData *adt = ale->adt; + NlaTrack *nlt = (NlaTrack *)ale->data; + NlaStrip *strip; + + /* only consider selected strips */ + for (strip = nlt->strips.first; strip; strip = strip->next) { + /* TODO: we can make strips have handles later on. */ + /* transition strips can't get directly transformed */ + if (strip->type != NLASTRIP_TYPE_TRANSITION) { + if (strip->flag & NLASTRIP_FLAG_SELECT) { + /* our transform data is constructed as follows: + * - only the handles on the right side of the current-frame get included + * - td structs are transform-elements operated on by the transform system + * and represent a single handle. The storage/pointer used (val or loc) depends on + * whether we're scaling or transforming. Ultimately though, the handles + * the td writes to will simply be a dummy in tdn + * - for each strip being transformed, a single tdn struct is used, so in some + * cases, there will need to be 1 of these tdn elements in the array skipped... + */ + float center[3], yval; + + /* firstly, init tdn settings */ + tdn->id = ale->id; + tdn->oldTrack = tdn->nlt = nlt; + tdn->strip = strip; + tdn->trackIndex = BLI_findindex(&adt->nla_tracks, nlt); + + yval = (float)(tdn->trackIndex * NLACHANNEL_STEP(snla)); + + tdn->h1[0] = strip->start; + tdn->h1[1] = yval; + tdn->h2[0] = strip->end; + tdn->h2[1] = yval; + tdn->h1[2] = tdn->h2[2] = strip->scale; + + center[0] = (float)scene->r.cfra; + center[1] = yval; + center[2] = 0.0f; + + /* set td's based on which handles are applicable */ + if (FrameOnMouseSide(t->frame_side, strip->start, (float)scene->r.cfra)) { + /* just set tdn to assume that it only has one handle for now */ + tdn->handle = -1; + + /* Now, link the transform data up to this data. */ + td->loc = tdn->h1; + copy_v3_v3(td->iloc, tdn->h1); + + if (ELEM(t->mode, TFM_TRANSLATION, TFM_TIME_EXTEND)) { + /* Store all the other gunk that is required by transform. */ + copy_v3_v3(td->center, center); + td->axismtx[2][2] = 1.0f; + td->flag |= TD_SELECTED; + unit_m3(td->mtx); + unit_m3(td->smtx); + } + + td->extra = tdn; + td++; + } + if (FrameOnMouseSide(t->frame_side, strip->end, (float)scene->r.cfra)) { + /* if tdn is already holding the start handle, + * then we're doing both, otherwise, only end */ + tdn->handle = (tdn->handle) ? 2 : 1; + + /* Now, link the transform data up to this data. */ + td->loc = tdn->h2; + copy_v3_v3(td->iloc, tdn->h2); + + if (ELEM(t->mode, TFM_TRANSLATION, TFM_TIME_EXTEND)) { + /* Store all the other gunk that is required by transform. */ + copy_v3_v3(td->center, center); + td->axismtx[2][2] = 1.0f; + td->flag |= TD_SELECTED; + unit_m3(td->mtx); + unit_m3(td->smtx); + } + + td->extra = tdn; + td++; + } + + /* If both handles were used, skip the next tdn (i.e. leave it blank) + * since the counting code is dumb. + * Otherwise, just advance to the next one. + */ + if (tdn->handle == 2) { + tdn += 2; + } + else { + tdn++; + } + } + } + } + } + } + + /* cleanup temp list */ + ANIM_animdata_freelist(&anim_data); +} + +static void recalcData_nla(TransInfo *t) +{ + SpaceNla *snla = (SpaceNla *)t->area->spacedata.first; + + TransDataContainer *tc = TRANS_DATA_CONTAINER_FIRST_SINGLE(t); + + /* handle auto-snapping + * NOTE: only do this when transform is still running, or we can't restore + */ + if (t->state != TRANS_CANCEL) { + const short autosnap = getAnimEdit_SnapMode(t); + if (autosnap != SACTSNAP_OFF) { + TransData *td = tc->data; + for (int i = 0; i < tc->data_len; i++, td++) { + transform_snap_anim_flush_data(t, td, autosnap, td->loc); + } + } + } + + /* For each strip we've got, perform some additional validation of the values + * that got set before using RNA to set the value (which does some special + * operations when setting these values to make sure that everything works ok). + */ + TransDataNla *tdn = tc->custom.type.data; + for (int i = 0; i < tc->data_len; i++, tdn++) { + NlaStrip *strip = tdn->strip; + PointerRNA strip_ptr; + int delta_y1, delta_y2; + + /* if this tdn has no handles, that means it is just a dummy that should be skipped */ + if (tdn->handle == 0) { + continue; + } + + /* set refresh tags for objects using this animation, + * BUT only if realtime updates are enabled + */ + if ((snla->flag & SNLA_NOREALTIMEUPDATES) == 0) { + ANIM_id_update(CTX_data_main(t->context), tdn->id); + } + + /* if canceling transform, just write the values without validating, then move on */ + if (t->state == TRANS_CANCEL) { + /* clear the values by directly overwriting the originals, but also need to restore + * endpoints of neighboring transition-strips + */ + + /* start */ + strip->start = tdn->h1[0]; + + if ((strip->prev) && (strip->prev->type == NLASTRIP_TYPE_TRANSITION)) { + strip->prev->end = tdn->h1[0]; + } + + /* end */ + strip->end = tdn->h2[0]; + + if ((strip->next) && (strip->next->type == NLASTRIP_TYPE_TRANSITION)) { + strip->next->start = tdn->h2[0]; + } + + strip->scale = tdn->h1[2]; + + /* flush transforms to child strips (since this should be a meta) */ + BKE_nlameta_flush_transforms(strip); + + /* restore to original track (if needed) */ + if (tdn->oldTrack != tdn->nlt) { + /* Just append to end of list for now, + * since strips get sorted in special_aftertrans_update(). */ + BLI_remlink(&tdn->nlt->strips, strip); + BLI_addtail(&tdn->oldTrack->strips, strip); + } + + continue; + } + + /* firstly, check if the proposed transform locations would overlap with any neighboring strips + * (barring transitions) which are absolute barriers since they are not being moved + * + * this is done as a iterative procedure (done 5 times max for now) + */ + NlaStrip *prev = BKE_nlastrip_prev_in_track(strip, true); + NlaStrip *next = BKE_nlastrip_next_in_track(strip, true); + + for (short iter = 0; iter < 5; iter++) { + const bool pExceeded = (prev != NULL) && (tdn->h1[0] < prev->end); + const bool nExceeded = (next != NULL) && (tdn->h2[0] > next->start); + + if ((pExceeded && nExceeded) || (iter == 4)) { + /* both endpoints exceeded (or iteration ping-pong'd meaning that we need a + * compromise) + * - Simply crop strip to fit within the bounds of the strips bounding it + * - If there were no neighbors, clear the transforms + * (make it default to the strip's current values). + */ + if (prev && next) { + tdn->h1[0] = prev->end; + tdn->h2[0] = next->start; + } + else { + tdn->h1[0] = strip->start; + tdn->h2[0] = strip->end; + } + } + else if (nExceeded) { + /* move backwards */ + float offset = tdn->h2[0] - next->start; + + tdn->h1[0] -= offset; + tdn->h2[0] -= offset; + } + else if (pExceeded) { + /* more forwards */ + float offset = prev->end - tdn->h1[0]; + + tdn->h1[0] += offset; + tdn->h2[0] += offset; + } + else { /* all is fine and well */ + break; + } + } + + /* Use RNA to write the values to ensure that constraints on these are obeyed + * (e.g. for transition strips, the values are taken from the neighbors) + */ + RNA_pointer_create(NULL, &RNA_NlaStrip, strip, &strip_ptr); + + switch (t->mode) { + case TFM_TIME_EXTEND: + case TFM_TIME_SCALE: { + /* The final scale is the product of the original strip scale (from before the transform + * operation started) and the current scale value of this transform operation. */ + const float originalStripScale = tdn->h1[2]; + const float newStripScale = originalStripScale * t->values_final[0]; + applyTransformNLA_timeScale(&strip_ptr, newStripScale); + applyTransformNLA_translation(&strip_ptr, tdn); + break; + } + case TFM_TRANSLATION: + applyTransformNLA_translation(&strip_ptr, tdn); + break; + default: + printf("recalcData_nla: unsupported NLA transformation mode %d\n", t->mode); + continue; + } + + /* flush transforms to child strips (since this should be a meta) */ + BKE_nlameta_flush_transforms(strip); + + /* Now, check if we need to try and move track: + * - we need to calculate both, + * as only one may have been altered by transform if only 1 handle moved. + */ + /* In LibOverride case, we cannot move strips across tracks that come from the linked data. */ + const bool is_liboverride = ID_IS_OVERRIDE_LIBRARY(tdn->id); + if (BKE_nlatrack_is_nonlocal_in_liboverride(tdn->id, tdn->nlt)) { + continue; + } + + delta_y1 = ((int)tdn->h1[1] / NLACHANNEL_STEP(snla) - tdn->trackIndex); + delta_y2 = ((int)tdn->h2[1] / NLACHANNEL_STEP(snla) - tdn->trackIndex); + + if (delta_y1 || delta_y2) { + NlaTrack *track; + int delta = (delta_y2) ? delta_y2 : delta_y1; + int n; + + /* Move in the requested direction, + * checking at each layer if there's space for strip to pass through, + * stopping on the last track available or that we're able to fit in. + */ + if (delta > 0) { + for (track = tdn->nlt->next, n = 0; (track) && (n < delta); track = track->next, n++) { + /* check if space in this track for the strip */ + if (BKE_nlatrack_has_space(track, strip->start, strip->end) && + !BKE_nlatrack_is_nonlocal_in_liboverride(tdn->id, track)) { + /* move strip to this track */ + BKE_nlatrack_remove_strip(tdn->nlt, strip); + BKE_nlatrack_add_strip(track, strip, is_liboverride); + + tdn->nlt = track; + tdn->trackIndex++; + } + else { /* can't move any further */ + break; + } + } + } + else { + /* make delta 'positive' before using it, since we now know to go backwards */ + delta = -delta; + + for (track = tdn->nlt->prev, n = 0; (track) && (n < delta); track = track->prev, n++) { + /* check if space in this track for the strip */ + if (BKE_nlatrack_has_space(track, strip->start, strip->end) && + !BKE_nlatrack_is_nonlocal_in_liboverride(tdn->id, track)) { + /* move strip to this track */ + BKE_nlatrack_remove_strip(tdn->nlt, strip); + BKE_nlatrack_add_strip(track, strip, is_liboverride); + + tdn->nlt = track; + tdn->trackIndex--; + } + else { /* can't move any further */ + break; + } + } + } + } + } +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Special After Transform NLA + * \{ */ + +static void special_aftertrans_update__nla(bContext *C, TransInfo *UNUSED(t)) +{ + bAnimContext ac; + + /* initialize relevant anim-context 'context' data */ + if (ANIM_animdata_get_context(C, &ac) == 0) { + return; + } + + if (ac.datatype) { + ListBase anim_data = {NULL, NULL}; + bAnimListElem *ale; + short filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_FOREDIT | ANIMFILTER_FCURVESONLY); + + /* get channels to work on */ + ANIM_animdata_filter(&ac, &anim_data, filter, ac.data, ac.datatype); + + for (ale = anim_data.first; ale; ale = ale->next) { + NlaTrack *nlt = (NlaTrack *)ale->data; + + /* make sure strips are in order again */ + BKE_nlatrack_sort_strips(nlt); + + /* remove the temp metas */ + BKE_nlastrips_clear_metas(&nlt->strips, 0, 1); + } + + /* General refresh for the outliner because the following might have happened: + * - strips moved between tracks + * - strips swapped order + * - duplicate-move moves to different track. */ + WM_event_add_notifier(C, NC_ANIMATION | ND_NLA | NA_ADDED, NULL); + + /* free temp memory */ + ANIM_animdata_freelist(&anim_data); + + /* Perform after-transform validation. */ + ED_nla_postop_refresh(&ac); + } +} + +/** \} */ + +TransConvertTypeInfo TransConvertType_NLA = { + /*flags*/ (T_POINTS | T_2D_EDIT), + /*createTransData*/ createTransNlaData, + /*recalcData*/ recalcData_nla, + /*special_aftertrans_update*/ special_aftertrans_update__nla, +}; diff --git a/source/blender/editors/transform/transform_convert_nla.c.rej b/source/blender/editors/transform/transform_convert_nla.c.rej new file mode 100644 index 00000000000..1451c917798 --- /dev/null +++ b/source/blender/editors/transform/transform_convert_nla.c.rej @@ -0,0 +1,38 @@ +*************** +*** 457,464 **** + if (BKE_nlatrack_has_space(track, strip->start, strip->end) && + !BKE_nlatrack_is_nonlocal_in_liboverride(tdn->id, track)) { + /* move strip to this track */ +- BLI_remlink(&tdn->nlt->strips, strip); +- BKE_nlatrack_add_strip(track, strip, is_liboverride); + + tdn->nlt = track; + tdn->trackIndex++; +--- 456,463 ---- + if (BKE_nlatrack_has_space(track, strip->start, strip->end) && + !BKE_nlatrack_is_nonlocal_in_liboverride(tdn->id, track)) { + /* move strip to this track */ ++ BKE_nlatrack_remove_strip(tdn->nlt, strip); ++ BKE_nlatrack_add_strip(track, strip); + + tdn->nlt = track; + tdn->trackIndex++; +*************** +*** 477,484 **** + if (BKE_nlatrack_has_space(track, strip->start, strip->end) && + !BKE_nlatrack_is_nonlocal_in_liboverride(tdn->id, track)) { + /* move strip to this track */ +- BLI_remlink(&tdn->nlt->strips, strip); +- BKE_nlatrack_add_strip(track, strip, is_liboverride); + + tdn->nlt = track; + tdn->trackIndex--; +--- 476,483 ---- + if (BKE_nlatrack_has_space(track, strip->start, strip->end) && + !BKE_nlatrack_is_nonlocal_in_liboverride(tdn->id, track)) { + /* move strip to this track */ ++ BKE_nlatrack_remove_strip(tdn->nlt, strip); ++ BKE_nlatrack_add_strip(track, strip); + + tdn->nlt = track; + tdn->trackIndex--; diff --git a/source/blender/makesrna/intern/rna_animation.c b/source/blender/makesrna/intern/rna_animation.c index fa2ef98d3df..2837c9e08b7 100644 --- a/source/blender/makesrna/intern/rna_animation.c +++ b/source/blender/makesrna/intern/rna_animation.c @@ -573,7 +573,14 @@ static void rna_KeyingSet_paths_clear(KeyingSet *keyingset, ReportList *reports) /* needs wrapper function to push notifier */ static NlaTrack *rna_NlaTrack_new(ID *id, AnimData *adt, Main *bmain, bContext *C, NlaTrack *track) { - NlaTrack *new_track = BKE_nlatrack_add(adt, track, ID_IS_OVERRIDE_LIBRARY(id)); + NlaTrack *new_track; + if (track != NULL) { + new_track = BKE_nlatrack_new_after_and_set_active( + &adt->nla_tracks, track, ID_IS_OVERRIDE_LIBRARY(id)); + } + else { + new_track = BKE_nlatrack_new_tail_and_set_active(&adt->nla_tracks, ID_IS_OVERRIDE_LIBRARY(id)); + } WM_event_add_notifier(C, NC_ANIMATION | ND_NLA | NA_ADDED, NULL); @@ -593,7 +600,7 @@ static void rna_NlaTrack_remove( return; } - BKE_nlatrack_free(&adt->nla_tracks, track, true); + BKE_nlatrack_remove_and_free(&adt->nla_tracks, track, true); RNA_POINTER_INVALIDATE(track_ptr); WM_event_add_notifier(C, NC_ANIMATION | ND_NLA | NA_REMOVED, NULL); diff --git a/source/blender/makesrna/intern/rna_nla.c.orig b/source/blender/makesrna/intern/rna_nla.c.orig new file mode 100644 index 00000000000..4d284695ade --- /dev/null +++ b/source/blender/makesrna/intern/rna_nla.c.orig @@ -0,0 +1,1071 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +/** \file + * \ingroup RNA + */ + +#include + +#include "DNA_action_types.h" +#include "DNA_anim_types.h" +#include "DNA_scene_types.h" + +#include "BLI_utildefines.h" + +#include "MEM_guardedalloc.h" + +#include "RNA_access.h" +#include "RNA_define.h" +#include "RNA_enum_types.h" + +#include "rna_internal.h" + +#include "WM_api.h" +#include "WM_types.h" + +/* enum defines exported for rna_animation.c */ +const EnumPropertyItem rna_enum_nla_mode_blend_items[] = { + {NLASTRIP_MODE_REPLACE, + "REPLACE", + 0, + "Replace", + "The strip values replace the accumulated results by amount specified by influence"}, + {NLASTRIP_MODE_COMBINE, + "COMBINE", + 0, + "Combine", + "The strip values are combined with accumulated results by appropriately using addition, " + "multiplication, or quaternion math, based on channel type"}, + RNA_ENUM_ITEM_SEPR, + {NLASTRIP_MODE_ADD, + "ADD", + 0, + "Add", + "Weighted result of strip is added to the accumulated results"}, + {NLASTRIP_MODE_SUBTRACT, + "SUBTRACT", + 0, + "Subtract", + "Weighted result of strip is removed from the accumulated results"}, + {NLASTRIP_MODE_MULTIPLY, + "MULTIPLY", + 0, + "Multiply", + "Weighted result of strip is multiplied with the accumulated results"}, + {0, NULL, 0, NULL, NULL}, +}; + +const EnumPropertyItem rna_enum_nla_mode_extend_items[] = { + {NLASTRIP_EXTEND_NOTHING, "NOTHING", 0, "Nothing", "Strip has no influence past its extents"}, + {NLASTRIP_EXTEND_HOLD, + "HOLD", + 0, + "Hold", + "Hold the first frame if no previous strips in track, and always hold last frame"}, + {NLASTRIP_EXTEND_HOLD_FORWARD, "HOLD_FORWARD", 0, "Hold Forward", "Only hold last frame"}, + {0, NULL, 0, NULL, NULL}, +}; + +#ifdef RNA_RUNTIME + +# include +# include + +/* needed for some of the validation stuff... */ +# include "BKE_anim_data.h" +# include "BKE_fcurve.h" +# include "BKE_nla.h" + +# include "DNA_object_types.h" + +# include "ED_anim_api.h" + +# include "DEG_depsgraph.h" +# include "DEG_depsgraph_build.h" + +static void rna_NlaStrip_name_set(PointerRNA *ptr, const char *value) +{ + NlaStrip *data = (NlaStrip *)ptr->data; + + /* copy the name first */ + BLI_strncpy_utf8(data->name, value, sizeof(data->name)); + + /* validate if there's enough info to do so */ + if (ptr->owner_id) { + AnimData *adt = BKE_animdata_from_id(ptr->owner_id); + BKE_nlastrip_validate_name(adt, data); + } +} + +static char *rna_NlaStrip_path(const PointerRNA *ptr) +{ + NlaStrip *strip = (NlaStrip *)ptr->data; + AnimData *adt = BKE_animdata_from_id(ptr->owner_id); + + /* if we're attached to AnimData, try to resolve path back to AnimData */ + if (adt) { + NlaTrack *nlt; + NlaStrip *nls; + + for (nlt = adt->nla_tracks.first; nlt; nlt = nlt->next) { + for (nls = nlt->strips.first; nls; nls = nls->next) { + if (nls == strip) { + /* XXX but if we animate like this, the control will never work... */ + char name_esc_nlt[sizeof(nlt->name) * 2]; + char name_esc_strip[sizeof(strip->name) * 2]; + + BLI_str_escape(name_esc_nlt, nlt->name, sizeof(name_esc_nlt)); + BLI_str_escape(name_esc_strip, strip->name, sizeof(name_esc_strip)); + return BLI_sprintfN( + "animation_data.nla_tracks[\"%s\"].strips[\"%s\"]", name_esc_nlt, name_esc_strip); + } + } + } + } + + /* no path */ + return BLI_strdup(""); +} + +static void rna_NlaStrip_update(Main *bmain, Scene *UNUSED(scene), PointerRNA *ptr) +{ + ID *id = ptr->owner_id; + + ANIM_id_update(bmain, id); +} + +static void rna_NlaStrip_dependency_update(Main *bmain, Scene *scene, PointerRNA *ptr) +{ + DEG_relations_tag_update(bmain); + + rna_NlaStrip_update(bmain, scene, ptr); +} + +static void rna_NlaStrip_transform_update(Main *bmain, Scene *scene, PointerRNA *ptr) +{ + NlaStrip *strip = (NlaStrip *)ptr->data; + + BKE_nlameta_flush_transforms(strip); + + /* set the flag */ + if ((strip->flag & NLASTRIP_FLAG_AUTO_BLENDS) && ptr->owner_id) { + /* validate state to ensure that auto-blend gets applied immediately */ + IdAdtTemplate *iat = (IdAdtTemplate *)ptr->owner_id; + + if (iat->adt) { + BKE_nla_validate_state(iat->adt); + } + } + + BKE_nlastrip_recalculate_blend(strip); + + rna_NlaStrip_update(bmain, scene, ptr); +} + +static void rna_NlaStrip_start_frame_set(PointerRNA *ptr, float value) +{ + /* Simply set the frame start in a valid range : if there are any NLA strips before/after, clamp + * the start value. If the new start value is past-the-end, clamp it. Otherwise, set it. + * + * NOTE: Unless neighboring strips are transitions, NLASTRIP_MIN_LEN_THRESH is not needed, as + * strips can be 'glued' to one another. If they are however, ensure transitions have a bit of + * time allotted in order to be performed. + */ + NlaStrip *data = (NlaStrip *)ptr->data; + + const float limit_prev = BKE_nlastrip_compute_frame_from_previous_strip(data); + const float limit_next = BKE_nlastrip_compute_frame_to_next_strip(data); + CLAMP(value, limit_prev, limit_next); + + data->start = value; + + /* The ONLY case where we actively modify the value set by the user, is in case the start value + * value is past the old end frame (here delta = NLASTRIP_MIN_LEN_THRESH) : + * - if there's no "room" for the end frame to be placed at (new_start + delta), move old_end to + * the limit, and new_start to (limit - delta) + * - otherwise, do _not_ change the end frame. This property is not accessible from the UI, and + * can only be set via scripts. The script should be responsible of setting the end frame. + */ + if (data->start > (data->end - NLASTRIP_MIN_LEN_THRESH)) { + /* If past-the-allowed-end : */ + if ((data->start + NLASTRIP_MIN_LEN_THRESH) > limit_next) { + data->end = limit_next; + data->start = data->end - NLASTRIP_MIN_LEN_THRESH; + } + } + + /* Ensure transitions are kept 'glued' to the strip : */ + if (data->prev && data->prev->type == NLASTRIP_TYPE_TRANSITION) { + data->prev->end = data->start; + } +} + +static void rna_NlaStrip_frame_start_ui_set(PointerRNA *ptr, float value) +{ + NlaStrip *data = (NlaStrip *)ptr->data; + + /* Changing the NLA strip's start frame is exactly the same as translating it in the NLA editor. + * When 'translating' the clip, the length of it should stay identical. Se we also need to set + * this strip's end frame after modifying its start (to `start + (old_end - old_start)`). + * Of course, we might have a few other strips on this NLA track, so we have to respect the + * previous strip's end frame. + * + * Also, different types of NLA strips (*_CLIP, *_TRANSITION, *_META, *_SOUND) have their own + * properties to respect. Needs testing on a real-world use case for the transition, meta, and + * sound types. + */ + + /* The strip's total length before modifying it & also how long we'd like it to be afterwards. */ + const float striplen = data->end - data->start; + + /* We're only modifying one strip at a time. The start and end times of its neighbors should not + * change. As such, here are the 'bookends' (frame limits) for the start position to respect : + * - if a next strip exists, don't allow the strip to start after (next->end - striplen - delta), + * (delta being the min length of a Nla Strip : the NLASTRIP_MIN_THRESH macro) + * - if a previous strip exists, don't allow this strip to start before it (data->prev) ends + * - otherwise, limit to the program limit macros defined in DNA_scene_types.h : {MINA|MAX}FRAMEF + */ + const float limit_prev = BKE_nlastrip_compute_frame_from_previous_strip(data); + const float limit_next = BKE_nlastrip_compute_frame_to_next_strip(data) - striplen; + /* For above : we want to be able to fit the entire strip before the next frame limit, so shift + * the next limit by 'striplen' no matter the context. */ + + CLAMP(value, limit_prev, limit_next); + data->start = value; + + if (data->type != NLASTRIP_TYPE_TRANSITION) { + data->end = data->start + striplen; + } + + /* Update properties of the prev/next strips if they are transitions to ensure consistency : */ + if (data->prev && data->prev->type == NLASTRIP_TYPE_TRANSITION) { + data->prev->end = data->start; + } + if (data->next && data->next->type == NLASTRIP_TYPE_TRANSITION) { + data->next->start = data->end; + } +} + +static void rna_NlaStrip_end_frame_set(PointerRNA *ptr, float value) +{ + NlaStrip *data = (NlaStrip *)ptr->data; + + const float limit_prev = BKE_nlastrip_compute_frame_from_previous_strip(data); + const float limit_next = BKE_nlastrip_compute_frame_to_next_strip(data); + CLAMP(value, limit_prev, limit_next); + + data->end = value; + + /* The ONLY case where we actively modify the value set by the user, is in case the start value + * value is past the old end frame (here delta = NLASTRIP_MIN_LEN_THRESH): + * - if there's no "room" for the end frame to be placed at (new_start + delta), move old_end to + * the limit, and new_start to (limit - delta) + * - otherwise, do _not_ change the end frame. This property is not accessible from the UI, and + * can only be set via scripts. The script should be responsible for setting the end frame. + */ + if (data->end < (data->start + NLASTRIP_MIN_LEN_THRESH)) { + /* If before-the-allowed-start : */ + if ((data->end - NLASTRIP_MIN_LEN_THRESH) < limit_prev) { + data->start = limit_prev; + data->end = data->start + NLASTRIP_MIN_LEN_THRESH; + } + } + + /* Ensure transitions are kept "glued" to the strip: */ + if (data->next && data->next->type == NLASTRIP_TYPE_TRANSITION) { + data->next->start = data->end; + } +} + +static void rna_NlaStrip_frame_end_ui_set(PointerRNA *ptr, float value) +{ + NlaStrip *data = (NlaStrip *)ptr->data; + + /* Changing the strip's end frame will update its action 'range' (defined by actstart->actend) to + * accommodate the extra length of the strip. No other parameters of the strip will change. But + * this means we have to get the current strip's end frame right now : + */ + const float old_strip_end = data->end; + + /* clamp value to lie within valid limits + * - must not have zero or negative length strip, so cannot start before the first frame + * + some minimum-strip-length threshold + * - cannot end later than the start of the next strip (if present) + * -> relies on the BKE_nlastrip_compute_frame_to_next_strip() function + */ + const float limit_prev = data->start + NLASTRIP_MIN_LEN_THRESH; + const float limit_next = BKE_nlastrip_compute_frame_to_next_strip(data); + + CLAMP(value, limit_prev, limit_next); + data->end = value; + + /* Only adjust transitions at this stage : */ + if (data->next && data->next->type == NLASTRIP_TYPE_TRANSITION) { + data->next->start = value; + } + + /* calculate the lengths the strip and its action : * + * (Meta and transitions shouldn't be updated, but clip and sound should) */ + if (data->type == NLASTRIP_TYPE_CLIP || data->type == NLASTRIP_TYPE_SOUND) { + float actlen = data->actend - data->actstart; + if (IS_EQF(actlen, 0.0f)) { + actlen = 1.0f; /* Only sanity check needed : we use this as divisor later on. */ + } + + /* Modify the strip's action end frame, or repeat based on : + * - if data->repeat == 1.0f, modify the action end frame : + * - if the number of frames to subtract is the number of frames, set the action end frame + * to the action start + 1 and modify the end of the strip to add that frame + * - if the number of frames + * - otherwise, modify the repeat property to accommodate for the new length + */ + float action_length_delta = (old_strip_end - data->end) / data->scale; + /* If no repeats are used, then modify the action end frame : */ + if (IS_EQF(data->repeat, 1.0f)) { + /* If they're equal, strip has been reduced by the same amount as the whole strip length, + * so clamp the action clip length to 1 frame, and add a frame to end so that + * `len(strip) != 0`. */ + if (IS_EQF(action_length_delta, actlen)) { + data->actend = data->actstart + 1.0f; + data->end += 1.0f; + } + else if (action_length_delta < actlen) { + /* Now, adjust the new strip's actend to the value it's supposed to have : */ + data->actend = data->actend - action_length_delta; + } + /* The case where the delta is bigger than the action length should not be possible, since + * data->end is guaranteed to be clamped to data->start + threshold above. + */ + } + else { + data->repeat -= (action_length_delta / actlen); + } + } +} + +static void rna_NlaStrip_scale_set(PointerRNA *ptr, float value) +{ + NlaStrip *data = (NlaStrip *)ptr->data; + + /* set scale value */ + /* NOTE: these need to be synced with the values in the + * property definition in rna_def_nlastrip() */ + CLAMP(value, 0.0001f, 1000.0f); + data->scale = value; + + /* adjust the strip extents in response to this */ + BKE_nlastrip_recalculate_bounds(data); +} + +static void rna_NlaStrip_repeat_set(PointerRNA *ptr, float value) +{ + NlaStrip *data = (NlaStrip *)ptr->data; + + /* set repeat value */ + /* NOTE: these need to be synced with the values in the + * property definition in rna_def_nlastrip() */ + CLAMP(value, 0.01f, 1000.0f); + data->repeat = value; + + /* adjust the strip extents in response to this */ + BKE_nlastrip_recalculate_bounds(data); +} + +static void rna_NlaStrip_blend_in_set(PointerRNA *ptr, float value) +{ + NlaStrip *data = (NlaStrip *)ptr->data; + float len; + + /* blend-in is limited to the length of the strip, and also cannot overlap with blendout */ + len = (data->end - data->start) - data->blendout; + CLAMP(value, 0, len); + + data->blendin = value; +} + +static void rna_NlaStrip_blend_out_set(PointerRNA *ptr, float value) +{ + NlaStrip *data = (NlaStrip *)ptr->data; + float len; + + /* blend-out is limited to the length of the strip */ + len = (data->end - data->start); + CLAMP(value, 0, len); + + /* it also cannot overlap with blendin */ + if ((len - value) < data->blendin) { + value = len - data->blendin; + } + + data->blendout = value; +} + +static void rna_NlaStrip_use_auto_blend_set(PointerRNA *ptr, bool value) +{ + NlaStrip *data = (NlaStrip *)ptr->data; + + if (value) { + /* set the flag */ + data->flag |= NLASTRIP_FLAG_AUTO_BLENDS; + + /* validate state to ensure that auto-blend gets applied immediately */ + if (ptr->owner_id) { + IdAdtTemplate *iat = (IdAdtTemplate *)ptr->owner_id; + + if (iat->adt) { + BKE_nla_validate_state(iat->adt); + } + } + } + else { + /* clear the flag */ + data->flag &= ~NLASTRIP_FLAG_AUTO_BLENDS; + + /* clear the values too, so that it's clear that there has been an effect */ + /* TODO: it's somewhat debatable whether it's better to leave these in instead... */ + data->blendin = 0.0f; + data->blendout = 0.0f; + } +} + +static int rna_NlaStrip_action_editable(PointerRNA *ptr, const char **UNUSED(r_info)) +{ + NlaStrip *strip = (NlaStrip *)ptr->data; + + /* Strip actions shouldn't be editable if NLA tweak-mode is on. */ + if (ptr->owner_id) { + AnimData *adt = BKE_animdata_from_id(ptr->owner_id); + + if (adt) { + /* active action is only editable when it is not a tweaking strip */ + if ((adt->flag & ADT_NLA_EDIT_ON) || (adt->actstrip) || (adt->tmpact)) { + return 0; + } + } + } + + /* check for clues that strip probably shouldn't be used... */ + if (strip->flag & NLASTRIP_FLAG_TWEAKUSER) { + return 0; + } + + /* should be ok, though we may still miss some cases */ + return PROP_EDITABLE; +} + +static void rna_NlaStrip_action_start_frame_set(PointerRNA *ptr, float value) +{ + NlaStrip *data = (NlaStrip *)ptr->data; + + /* prevent start frame from occurring after end of action */ + CLAMP(value, MINAFRAME, data->actend); + data->actstart = value; + + /* adjust the strip extents in response to this */ + /* TODO: should the strip be moved backwards instead as a special case? */ + BKE_nlastrip_recalculate_bounds(data); +} + +static void rna_NlaStrip_action_end_frame_set(PointerRNA *ptr, float value) +{ + NlaStrip *data = (NlaStrip *)ptr->data; + + /* prevent end frame from starting before start of action */ + CLAMP(value, data->actstart, MAXFRAME); + data->actend = value; + + /* adjust the strip extents in response to this */ + BKE_nlastrip_recalculate_bounds(data); +} + +static void rna_NlaStrip_animated_influence_set(PointerRNA *ptr, bool value) +{ + NlaStrip *data = (NlaStrip *)ptr->data; + + if (value) { + /* set the flag, then make sure a curve for this exists */ + data->flag |= NLASTRIP_FLAG_USR_INFLUENCE; + BKE_nlastrip_validate_fcurves(data); + } + else { + data->flag &= ~NLASTRIP_FLAG_USR_INFLUENCE; + } +} + +static void rna_NlaStrip_animated_time_set(PointerRNA *ptr, bool value) +{ + NlaStrip *data = (NlaStrip *)ptr->data; + + if (value) { + /* set the flag, then make sure a curve for this exists */ + data->flag |= NLASTRIP_FLAG_USR_TIME; + BKE_nlastrip_validate_fcurves(data); + } + else { + data->flag &= ~NLASTRIP_FLAG_USR_TIME; + } +} + +static FCurve *rna_NlaStrip_fcurve_find(NlaStrip *strip, + ReportList *reports, + const char *data_path, + int index) +{ + if (data_path[0] == '\0') { + BKE_report(reports, RPT_ERROR, "F-Curve data path empty, invalid argument"); + return NULL; + } + + /* Returns NULL if not found. */ + return BKE_fcurve_find(&strip->fcurves, data_path, index); +} + +static NlaStrip *rna_NlaStrip_new(ID *id, + NlaTrack *track, + Main *bmain, + bContext *C, + ReportList *reports, + const char *UNUSED(name), + int start, + bAction *action) +{ + NlaStrip *strip = BKE_nlastrip_new(action); + + if (strip == NULL) { + BKE_report(reports, RPT_ERROR, "Unable to create new strip"); + return NULL; + } + + strip->end += (start - strip->start); + strip->start = start; + + if (!BKE_nlastrips_add_strip(&track->strips, strip)) { + BKE_report( + reports, + RPT_ERROR, + "Unable to add strip (the track does not have any space to accommodate this new strip)"); + BKE_nlastrip_free(strip, true); + return NULL; + } + + /* create dummy AnimData block so that BKE_nlastrip_validate_name() + * can be used to ensure a valid name, as we don't have one here... + * - only the nla_tracks list is needed there, which we aim to reverse engineer here... + */ + { + AnimData adt = {NULL}; + NlaTrack *nlt, *nlt_p; + + /* 'first' NLA track is found by going back up chain of given + * track's parents until we fall off. */ + nlt_p = track; + nlt = track; + while ((nlt = nlt->prev) != NULL) { + nlt_p = nlt; + } + adt.nla_tracks.first = nlt_p; + + /* do the same thing to find the last track */ + nlt_p = track; + nlt = track; + while ((nlt = nlt->next) != NULL) { + nlt_p = nlt; + } + adt.nla_tracks.last = nlt_p; + + /* now we can just auto-name as usual */ + BKE_nlastrip_validate_name(&adt, strip); + } + + WM_event_add_notifier(C, NC_ANIMATION | ND_NLA | NA_ADDED, NULL); + + DEG_relations_tag_update(bmain); + DEG_id_tag_update_ex(bmain, id, ID_RECALC_ANIMATION | ID_RECALC_COPY_ON_WRITE); + + return strip; +} + +static void rna_NlaStrip_remove( + ID *id, NlaTrack *track, Main *bmain, bContext *C, ReportList *reports, PointerRNA *strip_ptr) +{ + NlaStrip *strip = strip_ptr->data; + if (BLI_findindex(&track->strips, strip) == -1) { + BKE_reportf( + reports, RPT_ERROR, "NLA strip '%s' not found in track '%s'", strip->name, track->name); + return; + } + + BKE_nlastrip_remove_and_free(&track->strips, strip, true); + RNA_POINTER_INVALIDATE(strip_ptr); + + WM_event_add_notifier(C, NC_ANIMATION | ND_NLA | NA_REMOVED, NULL); + + DEG_relations_tag_update(bmain); + DEG_id_tag_update_ex(bmain, id, ID_RECALC_ANIMATION | ID_RECALC_COPY_ON_WRITE); +} + +/* Set the 'solo' setting for the given NLA-track, making sure that it is the only one + * that has this status in its AnimData block. + */ +static void rna_NlaTrack_solo_set(PointerRNA *ptr, bool value) +{ + NlaTrack *data = (NlaTrack *)ptr->data; + AnimData *adt = BKE_animdata_from_id(ptr->owner_id); + NlaTrack *nt; + + if (data == NULL) { + return; + } + + /* firstly, make sure 'solo' flag for all tracks is disabled */ + for (nt = data; nt; nt = nt->next) { + nt->flag &= ~NLATRACK_SOLO; + } + for (nt = data; nt; nt = nt->prev) { + nt->flag &= ~NLATRACK_SOLO; + } + + /* now, enable 'solo' for the given track if appropriate */ + if (value) { + /* set solo status */ + data->flag |= NLATRACK_SOLO; + + /* set solo-status on AnimData */ + adt->flag |= ADT_NLA_SOLO_TRACK; + } + else { + /* solo status was already cleared on track */ + + /* clear solo-status on AnimData */ + adt->flag &= ~ADT_NLA_SOLO_TRACK; + } +} + +#else + +static void rna_def_strip_fcurves(BlenderRNA *brna, PropertyRNA *cprop) +{ + StructRNA *srna; + + FunctionRNA *func; + PropertyRNA *parm; + + RNA_def_property_srna(cprop, "NlaStripFCurves"); + srna = RNA_def_struct(brna, "NlaStripFCurves", NULL); + RNA_def_struct_sdna(srna, "NlaStrip"); + RNA_def_struct_ui_text(srna, "NLA-Strip F-Curves", "Collection of NLA strip F-Curves"); + + /* Strip.fcurves.find(...) */ + func = RNA_def_function(srna, "find", "rna_NlaStrip_fcurve_find"); + RNA_def_function_ui_description( + func, + "Find an F-Curve. Note that this function performs a linear scan " + "of all F-Curves in the NLA strip."); + RNA_def_function_flag(func, FUNC_USE_REPORTS); + parm = RNA_def_string(func, "data_path", NULL, 0, "Data Path", "F-Curve data path"); + RNA_def_parameter_flags(parm, 0, PARM_REQUIRED); + RNA_def_int(func, "index", 0, 0, INT_MAX, "Index", "Array index", 0, INT_MAX); + + parm = RNA_def_pointer( + func, "fcurve", "FCurve", "", "The found F-Curve, or None if it doesn't exist"); + RNA_def_function_return(func, parm); +} + +static void rna_def_nlastrip(BlenderRNA *brna) +{ + StructRNA *srna; + PropertyRNA *prop; + + /* enum defs */ + static const EnumPropertyItem prop_type_items[] = { + {NLASTRIP_TYPE_CLIP, "CLIP", 0, "Action Clip", "NLA Strip references some Action"}, + {NLASTRIP_TYPE_TRANSITION, + "TRANSITION", + 0, + "Transition", + "NLA Strip 'transitions' between adjacent strips"}, + {NLASTRIP_TYPE_META, "META", 0, "Meta", "NLA Strip acts as a container for adjacent strips"}, + {NLASTRIP_TYPE_SOUND, + "SOUND", + 0, + "Sound Clip", + "NLA Strip representing a sound event for speakers"}, + {0, NULL, 0, NULL, NULL}, + }; + + /* struct definition */ + srna = RNA_def_struct(brna, "NlaStrip", NULL); + RNA_def_struct_ui_text(srna, "NLA Strip", "A container referencing an existing Action"); + RNA_def_struct_path_func(srna, "rna_NlaStrip_path"); + RNA_def_struct_ui_icon(srna, ICON_NLA); /* XXX */ + + RNA_define_lib_overridable(true); + + /* name property */ + prop = RNA_def_property(srna, "name", PROP_STRING, PROP_NONE); + RNA_def_property_ui_text(prop, "Name", ""); + RNA_def_property_string_funcs(prop, NULL, NULL, "rna_NlaStrip_name_set"); + RNA_def_struct_name_property(srna, prop); + RNA_def_property_update(prop, NC_ANIMATION | ND_NLA, NULL); /* this will do? */ + + /* Enums */ + prop = RNA_def_property(srna, "type", PROP_ENUM, PROP_NONE); + RNA_def_property_enum_sdna(prop, NULL, "type"); + RNA_def_property_clear_flag( + prop, PROP_EDITABLE); /* XXX for now, not editable, since this is dangerous */ + RNA_def_property_enum_items(prop, prop_type_items); + RNA_def_property_ui_text(prop, "Type", "Type of NLA Strip"); + RNA_def_property_update(prop, NC_ANIMATION | ND_NLA | NA_EDITED, "rna_NlaStrip_update"); + + prop = RNA_def_property(srna, "extrapolation", PROP_ENUM, PROP_NONE); + RNA_def_property_enum_sdna(prop, NULL, "extendmode"); + RNA_def_property_enum_items(prop, rna_enum_nla_mode_extend_items); + RNA_def_property_ui_text( + prop, "Extrapolation", "Action to take for gaps past the strip extents"); + RNA_def_property_update(prop, NC_ANIMATION | ND_NLA | NA_EDITED, "rna_NlaStrip_update"); + + prop = RNA_def_property(srna, "blend_type", PROP_ENUM, PROP_NONE); + RNA_def_property_enum_sdna(prop, NULL, "blendmode"); + RNA_def_property_enum_items(prop, rna_enum_nla_mode_blend_items); + RNA_def_property_ui_text( + prop, "Blending", "Method used for combining strip's result with accumulated result"); + RNA_def_property_update(prop, NC_ANIMATION | ND_NLA | NA_EDITED, "rna_NlaStrip_update"); + + /* Strip extents */ + prop = RNA_def_property(srna, "frame_start", PROP_FLOAT, PROP_TIME); + RNA_def_property_float_sdna(prop, NULL, "start"); + RNA_def_property_float_funcs(prop, NULL, "rna_NlaStrip_start_frame_set", NULL); + RNA_def_property_ui_text(prop, "Start Frame", ""); + RNA_def_property_update( + prop, NC_ANIMATION | ND_NLA | NA_EDITED, "rna_NlaStrip_transform_update"); + + prop = RNA_def_property(srna, "frame_end", PROP_FLOAT, PROP_TIME); + RNA_def_property_float_sdna(prop, NULL, "end"); + RNA_def_property_float_funcs(prop, NULL, "rna_NlaStrip_end_frame_set", NULL); + RNA_def_property_ui_text(prop, "End Frame", ""); + RNA_def_property_update( + prop, NC_ANIMATION | ND_NLA | NA_EDITED, "rna_NlaStrip_transform_update"); + + /* Strip extents, when called from UI elements : */ + prop = RNA_def_property(srna, "frame_start_ui", PROP_FLOAT, PROP_TIME); + RNA_def_property_float_sdna(prop, NULL, "start"); + RNA_def_property_float_funcs(prop, NULL, "rna_NlaStrip_frame_start_ui_set", NULL); + RNA_def_property_ui_text( + prop, + "Start Frame (manipulated from UI)", + "Start frame of the NLA strip. Note: changing this value also updates the value of " + "the strip's end frame. If only the start frame should be changed, see the \"frame_start\" " + "property instead"); + RNA_def_property_update( + prop, NC_ANIMATION | ND_NLA | NA_EDITED, "rna_NlaStrip_transform_update"); + + prop = RNA_def_property(srna, "frame_end_ui", PROP_FLOAT, PROP_TIME); + RNA_def_property_float_sdna(prop, NULL, "end"); + RNA_def_property_float_funcs(prop, NULL, "rna_NlaStrip_frame_end_ui_set", NULL); + RNA_def_property_ui_text( + prop, + "End Frame (manipulated from UI)", + "End frame of the NLA strip. Note: changing this value also updates the value of " + "the strip's repeats or its action's end frame. If only the end frame should be " + "changed, see the \"frame_end\" property instead"); + RNA_def_property_update( + prop, NC_ANIMATION | ND_NLA | NA_EDITED, "rna_NlaStrip_transform_update"); + + /* Blending */ + prop = RNA_def_property(srna, "blend_in", PROP_FLOAT, PROP_NONE); + RNA_def_property_float_sdna(prop, NULL, "blendin"); + RNA_def_property_float_funcs(prop, NULL, "rna_NlaStrip_blend_in_set", NULL); + RNA_def_property_ui_text( + prop, "Blend In", "Number of frames at start of strip to fade in influence"); + RNA_def_property_update(prop, NC_ANIMATION | ND_NLA | NA_EDITED, "rna_NlaStrip_update"); + + prop = RNA_def_property(srna, "blend_out", PROP_FLOAT, PROP_NONE); + RNA_def_property_float_sdna(prop, NULL, "blendout"); + RNA_def_property_float_funcs(prop, NULL, "rna_NlaStrip_blend_out_set", NULL); + RNA_def_property_ui_text(prop, "Blend Out", ""); + RNA_def_property_update(prop, NC_ANIMATION | ND_NLA | NA_EDITED, "rna_NlaStrip_update"); + + prop = RNA_def_property(srna, "use_auto_blend", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, NULL, "flag", NLASTRIP_FLAG_AUTO_BLENDS); + RNA_def_property_boolean_funcs(prop, NULL, "rna_NlaStrip_use_auto_blend_set"); + RNA_def_property_ui_text(prop, + "Auto Blend In/Out", + "Number of frames for Blending In/Out is automatically determined from " + "overlapping strips"); + RNA_def_property_update(prop, NC_ANIMATION | ND_NLA | NA_EDITED, "rna_NlaStrip_update"); + + /* Action */ + prop = RNA_def_property(srna, "action", PROP_POINTER, PROP_NONE); + RNA_def_property_pointer_sdna(prop, NULL, "act"); + RNA_def_property_pointer_funcs(prop, NULL, NULL, NULL, "rna_Action_id_poll"); + RNA_def_property_flag(prop, PROP_EDITABLE | PROP_ID_REFCOUNT); + RNA_def_property_editable_func(prop, "rna_NlaStrip_action_editable"); + RNA_def_property_ui_text(prop, "Action", "Action referenced by this strip"); + RNA_def_property_update( + prop, NC_ANIMATION | ND_NLA | NA_EDITED, "rna_NlaStrip_dependency_update"); + + /* Action extents */ + prop = RNA_def_property(srna, "action_frame_start", PROP_FLOAT, PROP_TIME); + RNA_def_property_float_sdna(prop, NULL, "actstart"); + RNA_def_property_float_funcs(prop, NULL, "rna_NlaStrip_action_start_frame_set", NULL); + RNA_def_property_ui_text(prop, "Action Start Frame", "First frame from action to use"); + RNA_def_property_update( + prop, NC_ANIMATION | ND_NLA | NA_EDITED, "rna_NlaStrip_transform_update"); + + prop = RNA_def_property(srna, "action_frame_end", PROP_FLOAT, PROP_TIME); + RNA_def_property_float_sdna(prop, NULL, "actend"); + RNA_def_property_float_funcs(prop, NULL, "rna_NlaStrip_action_end_frame_set", NULL); + RNA_def_property_ui_text(prop, "Action End Frame", "Last frame from action to use"); + RNA_def_property_update( + prop, NC_ANIMATION | ND_NLA | NA_EDITED, "rna_NlaStrip_transform_update"); + + /* Action Reuse */ + prop = RNA_def_property(srna, "repeat", PROP_FLOAT, PROP_NONE); + RNA_def_property_float_sdna(prop, NULL, "repeat"); + RNA_def_property_float_funcs(prop, NULL, "rna_NlaStrip_repeat_set", NULL); + /* these limits have currently be chosen arbitrarily, but could be extended + * (minimum should still be > 0 though) if needed... */ + RNA_def_property_float_default(prop, 1.0f); + RNA_def_property_range(prop, 0.1f, 1000.0f); + RNA_def_property_ui_text(prop, "Repeat", "Number of times to repeat the action range"); + RNA_def_property_update( + prop, NC_ANIMATION | ND_NLA | NA_EDITED, "rna_NlaStrip_transform_update"); + + prop = RNA_def_property(srna, "scale", PROP_FLOAT, PROP_NONE); + RNA_def_property_float_sdna(prop, NULL, "scale"); + RNA_def_property_float_funcs(prop, NULL, "rna_NlaStrip_scale_set", NULL); + /* these limits can be extended, but beyond this, we can get some crazy+annoying bugs + * due to numeric errors */ + RNA_def_property_float_default(prop, 1.0f); + RNA_def_property_range(prop, 0.0001f, 1000.0f); + RNA_def_property_ui_text(prop, "Scale", "Scaling factor for action"); + RNA_def_property_update( + prop, NC_ANIMATION | ND_NLA | NA_EDITED, "rna_NlaStrip_transform_update"); + + /* Strip's F-Curves */ + prop = RNA_def_property(srna, "fcurves", PROP_COLLECTION, PROP_NONE); + RNA_def_property_collection_sdna(prop, NULL, "fcurves", NULL); + RNA_def_property_struct_type(prop, "FCurve"); + RNA_def_property_ui_text( + prop, "F-Curves", "F-Curves for controlling the strip's influence and timing"); + rna_def_strip_fcurves(brna, prop); + + /* Strip's F-Modifiers */ + prop = RNA_def_property(srna, "modifiers", PROP_COLLECTION, PROP_NONE); + RNA_def_property_struct_type(prop, "FModifier"); + RNA_def_property_ui_text( + prop, "Modifiers", "Modifiers affecting all the F-Curves in the referenced Action"); + + /* Strip's Sub-Strips (for Meta-Strips) */ + prop = RNA_def_property(srna, "strips", PROP_COLLECTION, PROP_NONE); + RNA_def_property_struct_type(prop, "NlaStrip"); + RNA_def_property_ui_text( + prop, + "NLA Strips", + "NLA Strips that this strip acts as a container for (if it is of type Meta)"); + + /* Settings - Values necessary for evaluation */ + prop = RNA_def_property(srna, "influence", PROP_FLOAT, PROP_FACTOR); + RNA_def_property_range(prop, 0.0f, 1.0f); + RNA_def_property_ui_text( + prop, "Influence", "Amount the strip contributes to the current result"); + /* XXX: Update temporarily disabled so that the property can be edited at all! + * Even auto-key only applies after the curves have been re-evaluated, + * causing the unkeyed values to be lost. */ + RNA_def_property_update(prop, NC_ANIMATION | ND_NLA | NA_EDITED, /*"rna_NlaStrip_update"*/ NULL); + + prop = RNA_def_property(srna, "strip_time", PROP_FLOAT, PROP_TIME); + RNA_def_property_ui_text(prop, "Strip Time", "Frame of referenced Action to evaluate"); + /* XXX: Update temporarily disabled so that the property can be edited at all! + * Even auto-key only applies after the curves have been re-evaluated, + * causing the unkeyed values to be lost. */ + RNA_def_property_update(prop, NC_ANIMATION | ND_NLA | NA_EDITED, /*"rna_NlaStrip_update"*/ NULL); + + /* TODO: should the animated_influence/time settings be animatable themselves? */ + prop = RNA_def_property(srna, "use_animated_influence", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, NULL, "flag", NLASTRIP_FLAG_USR_INFLUENCE); + RNA_def_property_boolean_funcs(prop, NULL, "rna_NlaStrip_animated_influence_set"); + RNA_def_property_ui_text( + prop, + "Animated Influence", + "Influence setting is controlled by an F-Curve rather than automatically determined"); + RNA_def_property_update(prop, NC_ANIMATION | ND_NLA | NA_EDITED, "rna_NlaStrip_update"); + + prop = RNA_def_property(srna, "use_animated_time", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, NULL, "flag", NLASTRIP_FLAG_USR_TIME); + RNA_def_property_boolean_funcs(prop, NULL, "rna_NlaStrip_animated_time_set"); + RNA_def_property_ui_text( + prop, + "Animated Strip Time", + "Strip time is controlled by an F-Curve rather than automatically determined"); + RNA_def_property_update(prop, NC_ANIMATION | ND_NLA | NA_EDITED, "rna_NlaStrip_update"); + + prop = RNA_def_property(srna, "use_animated_time_cyclic", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, NULL, "flag", NLASTRIP_FLAG_USR_TIME_CYCLIC); + RNA_def_property_ui_text( + prop, "Cyclic Strip Time", "Cycle the animated time within the action start and end"); + RNA_def_property_update( + prop, NC_ANIMATION | ND_NLA | NA_EDITED, "rna_NlaStrip_transform_update"); + + /* settings */ + prop = RNA_def_property(srna, "active", PROP_BOOLEAN, PROP_NONE); + /* can be made editable by hooking it up to the necessary NLA API methods */ + RNA_def_property_clear_flag(prop, PROP_EDITABLE); + RNA_def_property_boolean_sdna(prop, NULL, "flag", NLASTRIP_FLAG_ACTIVE); + RNA_def_property_ui_text(prop, "Active", "NLA Strip is active"); + RNA_def_property_update(prop, NC_ANIMATION | ND_NLA, NULL); /* this will do? */ + + prop = RNA_def_property(srna, "select", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, NULL, "flag", NLASTRIP_FLAG_SELECT); + RNA_def_property_ui_text(prop, "Select", "NLA Strip is selected"); + RNA_def_property_update(prop, NC_ANIMATION | ND_NLA, NULL); /* this will do? */ + + prop = RNA_def_property(srna, "mute", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, NULL, "flag", NLASTRIP_FLAG_MUTED); + RNA_def_property_ui_icon(prop, ICON_CHECKBOX_HLT, -1); + RNA_def_property_ui_text(prop, "Mute", "Disable NLA Strip evaluation"); + RNA_def_property_update(prop, NC_ANIMATION | ND_NLA | NA_EDITED, "rna_NlaStrip_update"); + + prop = RNA_def_property(srna, "use_reverse", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, NULL, "flag", NLASTRIP_FLAG_REVERSE); + RNA_def_property_ui_text(prop, + "Reversed", + "NLA Strip is played back in reverse order (only when timing is " + "automatically determined)"); + RNA_def_property_update(prop, NC_ANIMATION | ND_NLA | NA_EDITED, "rna_NlaStrip_update"); + + prop = RNA_def_property(srna, "use_sync_length", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, NULL, "flag", NLASTRIP_FLAG_SYNC_LENGTH); + RNA_def_property_ui_text(prop, + "Sync Action Length", + "Update range of frames referenced from action " + "after tweaking strip and its keyframes"); + RNA_def_property_update(prop, NC_ANIMATION | ND_NLA | NA_EDITED, "rna_NlaStrip_update"); + + RNA_define_lib_overridable(false); +} + +static void rna_api_nlatrack_strips(BlenderRNA *brna, PropertyRNA *cprop) +{ + StructRNA *srna; + PropertyRNA *parm; + FunctionRNA *func; + + RNA_def_property_srna(cprop, "NlaStrips"); + srna = RNA_def_struct(brna, "NlaStrips", NULL); + RNA_def_struct_sdna(srna, "NlaTrack"); + RNA_def_struct_ui_text(srna, "NLA Strips", "Collection of NLA Strips"); + + func = RNA_def_function(srna, "new", "rna_NlaStrip_new"); + RNA_def_function_flag(func, + FUNC_USE_SELF_ID | FUNC_USE_MAIN | FUNC_USE_CONTEXT | FUNC_USE_REPORTS); + RNA_def_function_ui_description(func, "Add a new Action-Clip strip to the track"); + parm = RNA_def_string(func, "name", "NlaStrip", 0, "", "Name for the NLA Strips"); + RNA_def_parameter_flags(parm, 0, PARM_REQUIRED); + parm = RNA_def_int(func, + "start", + 0, + INT_MIN, + INT_MAX, + "Start Frame", + "Start frame for this strip", + INT_MIN, + INT_MAX); + RNA_def_parameter_flags(parm, 0, PARM_REQUIRED); + parm = RNA_def_pointer(func, "action", "Action", "", "Action to assign to this strip"); + RNA_def_parameter_flags(parm, PROP_NEVER_NULL, PARM_REQUIRED); + /* return type */ + parm = RNA_def_pointer(func, "strip", "NlaStrip", "", "New NLA Strip"); + RNA_def_function_return(func, parm); + + func = RNA_def_function(srna, "remove", "rna_NlaStrip_remove"); + RNA_def_function_flag(func, + FUNC_USE_SELF_ID | FUNC_USE_MAIN | FUNC_USE_CONTEXT | FUNC_USE_REPORTS); + RNA_def_function_ui_description(func, "Remove a NLA Strip"); + parm = RNA_def_pointer(func, "strip", "NlaStrip", "", "NLA Strip to remove"); + RNA_def_parameter_flags(parm, PROP_NEVER_NULL, PARM_REQUIRED | PARM_RNAPTR); + RNA_def_parameter_clear_flags(parm, PROP_THICK_WRAP, 0); +} + +static void rna_def_nlatrack(BlenderRNA *brna) +{ + StructRNA *srna; + PropertyRNA *prop; + + srna = RNA_def_struct(brna, "NlaTrack", NULL); + RNA_def_struct_ui_text( + srna, "NLA Track", "An animation layer containing Actions referenced as NLA strips"); + RNA_def_struct_ui_icon(srna, ICON_NLA); + + /* strips collection */ + prop = RNA_def_property(srna, "strips", PROP_COLLECTION, PROP_NONE); + RNA_def_property_struct_type(prop, "NlaStrip"); + /* We do not support inserting or removing strips in overrides of tracks for now. */ + RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY); + RNA_def_property_ui_text(prop, "NLA Strips", "NLA Strips on this NLA-track"); + + rna_api_nlatrack_strips(brna, prop); + + prop = RNA_def_boolean(srna, + "is_override_data", + false, + "Override Track", + "In a local override data, whether this NLA track comes from the linked " + "reference data, or is local to the override"); + RNA_def_property_clear_flag(prop, PROP_EDITABLE); + RNA_def_property_boolean_negative_sdna(prop, NULL, "flag", NLATRACK_OVERRIDELIBRARY_LOCAL); + + RNA_define_lib_overridable(true); + + /* name property */ + prop = RNA_def_property(srna, "name", PROP_STRING, PROP_NONE); + RNA_def_property_ui_text(prop, "Name", ""); + RNA_def_struct_name_property(srna, prop); + RNA_def_property_update(prop, NC_ANIMATION | ND_NLA, NULL); /* this will do? */ + + /* settings */ + prop = RNA_def_property(srna, "active", PROP_BOOLEAN, PROP_NONE); + /* can be made editable by hooking it up to the necessary NLA API methods */ + RNA_def_property_clear_flag(prop, PROP_EDITABLE); + RNA_def_property_boolean_sdna(prop, NULL, "flag", NLATRACK_ACTIVE); + RNA_def_property_ui_text(prop, "Active", "NLA Track is active"); + RNA_def_property_update(prop, NC_ANIMATION | ND_NLA, NULL); /* this will do? */ + + prop = RNA_def_property(srna, "is_solo", PROP_BOOLEAN, PROP_NONE); + /* can be made editable by hooking it up to the necessary NLA API methods */ + RNA_def_property_boolean_sdna(prop, NULL, "flag", NLATRACK_SOLO); + RNA_def_property_ui_text( + prop, + "Solo", + "NLA Track is evaluated itself (i.e. active Action and all other NLA Tracks in the " + "same AnimData block are disabled)"); + RNA_def_property_update(prop, NC_ANIMATION | ND_NLA | NA_EDITED, "rna_NlaStrip_update"); + RNA_def_property_boolean_funcs(prop, NULL, "rna_NlaTrack_solo_set"); + + prop = RNA_def_property(srna, "select", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, NULL, "flag", NLATRACK_SELECTED); + RNA_def_property_ui_text(prop, "Select", "NLA Track is selected"); + RNA_def_property_update(prop, NC_ANIMATION | ND_NLA, NULL); /* this will do? */ + + prop = RNA_def_property(srna, "mute", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, NULL, "flag", NLATRACK_MUTED); + RNA_def_property_ui_text(prop, "Muted", "Disable NLA Track evaluation"); + RNA_def_property_update(prop, NC_ANIMATION | ND_NLA | NA_EDITED, "rna_NlaStrip_update"); + + prop = RNA_def_property(srna, "lock", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, NULL, "flag", NLATRACK_PROTECTED); + RNA_def_property_ui_text(prop, "Locked", "NLA Track is locked"); + RNA_def_property_update(prop, NC_ANIMATION | ND_NLA, NULL); /* this will do? */ + + RNA_define_lib_overridable(false); +} + +/* --------- */ + +void RNA_def_nla(BlenderRNA *brna) +{ + rna_def_nlatrack(brna); + rna_def_nlastrip(brna); +} + +#endif diff --git a/source/blender/makesrna/intern/rna_nla.c.rej b/source/blender/makesrna/intern/rna_nla.c.rej new file mode 100644 index 00000000000..7a02ac9db9b --- /dev/null +++ b/source/blender/makesrna/intern/rna_nla.c.rej @@ -0,0 +1,44 @@ +*************** +*** 390,401 **** + strip->end += (start - strip->start); + strip->start = start; + +- if (BKE_nlastrips_add_strip(&track->strips, strip) == 0) { + BKE_report( + reports, + RPT_ERROR, + "Unable to add strip (the track does not have any space to accommodate this new strip)"); +- BKE_nlastrip_free(NULL, strip, true); + return NULL; + } + +--- 390,401 ---- + strip->end += (start - strip->start); + strip->start = start; + ++ if (!BKE_nlastrips_try_add_strip(&track->strips, strip)) { + BKE_report( + reports, + RPT_ERROR, + "Unable to add strip (the track does not have any space to accommodate this new strip)"); ++ BKE_nlastrip_free(strip, true); + return NULL; + } + +*************** +*** 446,452 **** + return; + } + +- BKE_nlastrip_free(&track->strips, strip, true); + RNA_POINTER_INVALIDATE(strip_ptr); + + WM_event_add_notifier(C, NC_ANIMATION | ND_NLA | NA_REMOVED, NULL); +--- 446,452 ---- + return; + } + ++ BKE_nlastrip_remove_and_free(&track->strips, strip, true); + RNA_POINTER_INVALIDATE(strip_ptr); + + WM_event_add_notifier(C, NC_ANIMATION | ND_NLA | NA_REMOVED, NULL); diff --git a/source/tools b/source/tools index e133fc08cd3..d50df97812e 160000 --- a/source/tools +++ b/source/tools @@ -1 +1 @@ -Subproject commit e133fc08cd3254bb3d3bd1345028c8486700bca4 +Subproject commit d50df97812e481c36ccae965e8fa3101ab8ab320 -- 2.30.2