diff options
author | Jeroen Bakker <jeroen@blender.org> | 2021-07-19 16:53:04 +0300 |
---|---|---|
committer | Jeroen Bakker <jeroen@blender.org> | 2021-07-19 16:53:04 +0300 |
commit | 132522cba894954406877eba9067b9be06c60cde (patch) | |
tree | 54089c726d3b2517637b1749dc841a23c148987f | |
parent | ceb612a79c7c49967fe6976c261ad3f9fa47adfb (diff) |
Cleanup: Separate keyframes_draw and keyframes_keylist.
The keylist functions are used in other places for none drawing related
stuff. Fe pose_slide uses it.
-rw-r--r-- | source/blender/editors/animation/CMakeLists.txt | 1 | ||||
-rw-r--r-- | source/blender/editors/animation/anim_draw.c | 1 | ||||
-rw-r--r-- | source/blender/editors/animation/anim_motion_paths.c | 2 | ||||
-rw-r--r-- | source/blender/editors/animation/keyframes_draw.c | 758 | ||||
-rw-r--r-- | source/blender/editors/animation/keyframes_keylist.c | 793 | ||||
-rw-r--r-- | source/blender/editors/armature/pose_lib.c | 2 | ||||
-rw-r--r-- | source/blender/editors/armature/pose_slide.c | 2 | ||||
-rw-r--r-- | source/blender/editors/include/ED_keyframes_draw.h | 146 | ||||
-rw-r--r-- | source/blender/editors/include/ED_keyframes_keylist.h | 195 | ||||
-rw-r--r-- | source/blender/editors/interface/interface_icons.c | 1 | ||||
-rw-r--r-- | source/blender/editors/screen/screen_ops.c | 2 | ||||
-rw-r--r-- | source/blender/editors/space_action/action_select.c | 2 | ||||
-rw-r--r-- | source/blender/editors/space_nla/nla_draw.c | 1 |
13 files changed, 998 insertions, 908 deletions
diff --git a/source/blender/editors/animation/CMakeLists.txt b/source/blender/editors/animation/CMakeLists.txt index f50a5ffbb5e..7a53b54b5a4 100644 --- a/source/blender/editors/animation/CMakeLists.txt +++ b/source/blender/editors/animation/CMakeLists.txt @@ -47,6 +47,7 @@ set(SRC keyframes_draw.c keyframes_edit.c keyframes_general.c + keyframes_keylist.c keyframing.c keyingsets.c time_scrub_ui.c diff --git a/source/blender/editors/animation/anim_draw.c b/source/blender/editors/animation/anim_draw.c index 2fcd59a1bbe..baf8adf28d0 100644 --- a/source/blender/editors/animation/anim_draw.c +++ b/source/blender/editors/animation/anim_draw.c @@ -48,6 +48,7 @@ #include "ED_anim_api.h" #include "ED_keyframes_draw.h" #include "ED_keyframes_edit.h" +#include "ED_keyframes_keylist.h" #include "RNA_access.h" diff --git a/source/blender/editors/animation/anim_motion_paths.c b/source/blender/editors/animation/anim_motion_paths.c index 51a897600e1..bddd5dbff55 100644 --- a/source/blender/editors/animation/anim_motion_paths.c +++ b/source/blender/editors/animation/anim_motion_paths.c @@ -43,7 +43,7 @@ #include "GPU_vertex_buffer.h" #include "ED_anim_api.h" -#include "ED_keyframes_draw.h" +#include "ED_keyframes_keylist.h" #include "CLG_log.h" diff --git a/source/blender/editors/animation/keyframes_draw.c b/source/blender/editors/animation/keyframes_draw.c index 06107b6fee6..4f512c9d7ca 100644 --- a/source/blender/editors/animation/keyframes_draw.c +++ b/source/blender/editors/animation/keyframes_draw.c @@ -24,28 +24,17 @@ /* System includes ----------------------------------------------------- */ #include <float.h> -#include <math.h> -#include <stdlib.h> -#include <string.h> - -#include "MEM_guardedalloc.h" #include "BLI_dlrbTree.h" #include "BLI_listbase.h" -#include "BLI_math.h" #include "BLI_rect.h" -#include "BLI_utildefines.h" #include "DNA_anim_types.h" -#include "DNA_brush_types.h" -#include "DNA_cachefile_types.h" #include "DNA_gpencil_types.h" #include "DNA_mask_types.h" #include "DNA_object_types.h" #include "DNA_scene_types.h" -#include "BKE_fcurve.h" - #include "GPU_immediate.h" #include "GPU_state.h" @@ -55,498 +44,7 @@ #include "ED_anim_api.h" #include "ED_keyframes_draw.h" - -/* *************************** Keyframe Processing *************************** */ - -/* ActKeyColumns (Keyframe Columns) ------------------------------------------ */ - -BLI_INLINE bool is_cfra_eq(float a, float b) -{ - return IS_EQT(a, b, BEZT_BINARYSEARCH_THRESH); -} - -BLI_INLINE bool is_cfra_lt(float a, float b) -{ - return (b - a) > BEZT_BINARYSEARCH_THRESH; -} - -/* Comparator callback used for ActKeyColumns and cframe float-value pointer */ -/* NOTE: this is exported to other modules that use the ActKeyColumns for finding keyframes */ -short compare_ak_cfraPtr(void *node, void *data) -{ - ActKeyColumn *ak = (ActKeyColumn *)node; - const float *cframe = data; - float val = *cframe; - - if (is_cfra_eq(val, ak->cfra)) { - return 0; - } - - if (val < ak->cfra) { - return -1; - } - return 1; -} - -/* --------------- */ - -/* Set of references to three logically adjacent keys. */ -typedef struct BezTripleChain { - /* Current keyframe. */ - BezTriple *cur; - - /* Logical neighbors. May be NULL. */ - BezTriple *prev, *next; -} BezTripleChain; - -/* Categorize the interpolation & handle type of the keyframe. */ -static eKeyframeHandleDrawOpts bezt_handle_type(BezTriple *bezt) -{ - if (bezt->h1 == HD_AUTO_ANIM && bezt->h2 == HD_AUTO_ANIM) { - return KEYFRAME_HANDLE_AUTO_CLAMP; - } - if (ELEM(bezt->h1, HD_AUTO_ANIM, HD_AUTO) && ELEM(bezt->h2, HD_AUTO_ANIM, HD_AUTO)) { - return KEYFRAME_HANDLE_AUTO; - } - if (bezt->h1 == HD_VECT && bezt->h2 == HD_VECT) { - return KEYFRAME_HANDLE_VECTOR; - } - if (ELEM(HD_FREE, bezt->h1, bezt->h2)) { - return KEYFRAME_HANDLE_FREE; - } - return KEYFRAME_HANDLE_ALIGNED; -} - -/* Determine if the keyframe is an extreme by comparing with neighbors. - * Ends of fixed-value sections and of the whole curve are also marked. - */ -static eKeyframeExtremeDrawOpts bezt_extreme_type(BezTripleChain *chain) -{ - if (chain->prev == NULL && chain->next == NULL) { - return KEYFRAME_EXTREME_NONE; - } - - /* Keyframe values for the current one and neighbors. */ - float cur_y = chain->cur->vec[1][1]; - float prev_y = cur_y, next_y = cur_y; - - if (chain->prev && !IS_EQF(cur_y, chain->prev->vec[1][1])) { - prev_y = chain->prev->vec[1][1]; - } - if (chain->next && !IS_EQF(cur_y, chain->next->vec[1][1])) { - next_y = chain->next->vec[1][1]; - } - - /* Static hold. */ - if (prev_y == cur_y && next_y == cur_y) { - return KEYFRAME_EXTREME_FLAT; - } - - /* Middle of an incline. */ - if ((prev_y < cur_y && next_y > cur_y) || (prev_y > cur_y && next_y < cur_y)) { - return KEYFRAME_EXTREME_NONE; - } - - /* Bezier handle values for the overshoot check. */ - bool l_bezier = chain->prev && chain->prev->ipo == BEZT_IPO_BEZ; - bool r_bezier = chain->next && chain->cur->ipo == BEZT_IPO_BEZ; - float handle_l = l_bezier ? chain->cur->vec[0][1] : cur_y; - float handle_r = r_bezier ? chain->cur->vec[2][1] : cur_y; - - /* Detect extremes. One of the neighbors is allowed to be equal to current. */ - if (prev_y < cur_y || next_y < cur_y) { - bool is_overshoot = (handle_l > cur_y || handle_r > cur_y); - - return KEYFRAME_EXTREME_MAX | (is_overshoot ? KEYFRAME_EXTREME_MIXED : 0); - } - - if (prev_y > cur_y || next_y > cur_y) { - bool is_overshoot = (handle_l < cur_y || handle_r < cur_y); - - return KEYFRAME_EXTREME_MIN | (is_overshoot ? KEYFRAME_EXTREME_MIXED : 0); - } - - return KEYFRAME_EXTREME_NONE; -} - -/* Comparator callback used for ActKeyColumns and BezTripleChain */ -static short compare_ak_bezt(void *node, void *data) -{ - BezTripleChain *chain = data; - - return compare_ak_cfraPtr(node, &chain->cur->vec[1][0]); -} - -/* New node callback used for building ActKeyColumns from BezTripleChain */ -static DLRBT_Node *nalloc_ak_bezt(void *data) -{ - ActKeyColumn *ak = MEM_callocN(sizeof(ActKeyColumn), "ActKeyColumn"); - BezTripleChain *chain = data; - BezTriple *bezt = chain->cur; - - /* store settings based on state of BezTriple */ - ak->cfra = bezt->vec[1][0]; - ak->sel = BEZT_ISSEL_ANY(bezt) ? SELECT : 0; - ak->key_type = BEZKEYTYPE(bezt); - ak->handle_type = bezt_handle_type(bezt); - ak->extreme_type = bezt_extreme_type(chain); - - /* count keyframes in this column */ - ak->totkey = 1; - - return (DLRBT_Node *)ak; -} - -/* Node updater callback used for building ActKeyColumns from BezTripleChain */ -static void nupdate_ak_bezt(void *node, void *data) -{ - ActKeyColumn *ak = node; - BezTripleChain *chain = data; - BezTriple *bezt = chain->cur; - - /* set selection status and 'touched' status */ - if (BEZT_ISSEL_ANY(bezt)) { - ak->sel = SELECT; - } - - /* count keyframes in this column */ - ak->totkey++; - - /* For keyframe type, 'proper' keyframes have priority over breakdowns - * (and other types for now). */ - if (BEZKEYTYPE(bezt) == BEZT_KEYTYPE_KEYFRAME) { - ak->key_type = BEZT_KEYTYPE_KEYFRAME; - } - - /* For interpolation type, select the highest value (enum is sorted). */ - ak->handle_type = MAX2(ak->handle_type, bezt_handle_type(bezt)); - - /* For extremes, detect when combining different states. */ - char new_extreme = bezt_extreme_type(chain); - - if (new_extreme != ak->extreme_type) { - /* Replace the flat status without adding mixed. */ - if (ak->extreme_type == KEYFRAME_EXTREME_FLAT) { - ak->extreme_type = new_extreme; - } - else if (new_extreme != KEYFRAME_EXTREME_FLAT) { - ak->extreme_type |= (new_extreme | KEYFRAME_EXTREME_MIXED); - } - } -} - -/* ......... */ - -/* Comparator callback used for ActKeyColumns and GPencil frame */ -static short compare_ak_gpframe(void *node, void *data) -{ - bGPDframe *gpf = (bGPDframe *)data; - - float frame = gpf->framenum; - return compare_ak_cfraPtr(node, &frame); -} - -/* New node callback used for building ActKeyColumns from GPencil frames */ -static DLRBT_Node *nalloc_ak_gpframe(void *data) -{ - ActKeyColumn *ak = MEM_callocN(sizeof(ActKeyColumn), "ActKeyColumnGPF"); - bGPDframe *gpf = (bGPDframe *)data; - - /* store settings based on state of BezTriple */ - ak->cfra = gpf->framenum; - ak->sel = (gpf->flag & GP_FRAME_SELECT) ? SELECT : 0; - ak->key_type = gpf->key_type; - - /* count keyframes in this column */ - ak->totkey = 1; - /* Set as visible block. */ - ak->totblock = 1; - ak->block.sel = ak->sel; - ak->block.flag |= ACTKEYBLOCK_FLAG_GPENCIL; - - return (DLRBT_Node *)ak; -} - -/* Node updater callback used for building ActKeyColumns from GPencil frames */ -static void nupdate_ak_gpframe(void *node, void *data) -{ - ActKeyColumn *ak = (ActKeyColumn *)node; - bGPDframe *gpf = (bGPDframe *)data; - - /* set selection status and 'touched' status */ - if (gpf->flag & GP_FRAME_SELECT) { - ak->sel = SELECT; - } - - /* count keyframes in this column */ - ak->totkey++; - - /* for keyframe type, 'proper' keyframes have priority over breakdowns - * (and other types for now). */ - if (gpf->key_type == BEZT_KEYTYPE_KEYFRAME) { - ak->key_type = BEZT_KEYTYPE_KEYFRAME; - } -} - -/* ......... */ - -/* Comparator callback used for ActKeyColumns and GPencil frame */ -static short compare_ak_masklayshape(void *node, void *data) -{ - MaskLayerShape *masklay_shape = (MaskLayerShape *)data; - - float frame = masklay_shape->frame; - return compare_ak_cfraPtr(node, &frame); -} - -/* New node callback used for building ActKeyColumns from GPencil frames */ -static DLRBT_Node *nalloc_ak_masklayshape(void *data) -{ - ActKeyColumn *ak = MEM_callocN(sizeof(ActKeyColumn), "ActKeyColumnGPF"); - MaskLayerShape *masklay_shape = (MaskLayerShape *)data; - - /* store settings based on state of BezTriple */ - ak->cfra = masklay_shape->frame; - ak->sel = (masklay_shape->flag & MASK_SHAPE_SELECT) ? SELECT : 0; - - /* count keyframes in this column */ - ak->totkey = 1; - - return (DLRBT_Node *)ak; -} - -/* Node updater callback used for building ActKeyColumns from GPencil frames */ -static void nupdate_ak_masklayshape(void *node, void *data) -{ - ActKeyColumn *ak = (ActKeyColumn *)node; - MaskLayerShape *masklay_shape = (MaskLayerShape *)data; - - /* set selection status and 'touched' status */ - if (masklay_shape->flag & MASK_SHAPE_SELECT) { - ak->sel = SELECT; - } - - /* count keyframes in this column */ - ak->totkey++; -} - -/* --------------- */ - -/* Add the given BezTriple to the given 'list' of Keyframes */ -static void add_bezt_to_keycolumns_list(DLRBT_Tree *keys, BezTripleChain *bezt) -{ - if (ELEM(NULL, keys, bezt)) { - return; - } - - BLI_dlrbTree_add(keys, compare_ak_bezt, nalloc_ak_bezt, nupdate_ak_bezt, bezt); -} - -/* Add the given GPencil Frame to the given 'list' of Keyframes */ -static void add_gpframe_to_keycolumns_list(DLRBT_Tree *keys, bGPDframe *gpf) -{ - if (ELEM(NULL, keys, gpf)) { - return; - } - - BLI_dlrbTree_add(keys, compare_ak_gpframe, nalloc_ak_gpframe, nupdate_ak_gpframe, gpf); -} - -/* Add the given MaskLayerShape Frame to the given 'list' of Keyframes */ -static void add_masklay_to_keycolumns_list(DLRBT_Tree *keys, MaskLayerShape *masklay_shape) -{ - if (ELEM(NULL, keys, masklay_shape)) { - return; - } - - BLI_dlrbTree_add(keys, - compare_ak_masklayshape, - nalloc_ak_masklayshape, - nupdate_ak_masklayshape, - masklay_shape); -} - -/* ActKeyBlocks (Long Keyframes) ------------------------------------------ */ - -static const ActKeyBlockInfo dummy_keyblock = {0}; - -static void compute_keyblock_data(ActKeyBlockInfo *info, BezTriple *prev, BezTriple *beztn) -{ - memset(info, 0, sizeof(ActKeyBlockInfo)); - - if (BEZKEYTYPE(beztn) == BEZT_KEYTYPE_MOVEHOLD) { - /* Animator tagged a "moving hold" - * - Previous key must also be tagged as a moving hold, otherwise - * we're just dealing with the first of a pair, and we don't - * want to be creating any phantom holds... - */ - if (BEZKEYTYPE(prev) == BEZT_KEYTYPE_MOVEHOLD) { - info->flag |= ACTKEYBLOCK_FLAG_MOVING_HOLD | ACTKEYBLOCK_FLAG_ANY_HOLD; - } - } - - /* Check for same values... - * - Handles must have same central value as each other - * - Handles which control that section of the curve must be constant - */ - if (IS_EQF(beztn->vec[1][1], prev->vec[1][1])) { - bool hold; - - /* Only check handles in case of actual bezier interpolation. */ - if (prev->ipo == BEZT_IPO_BEZ) { - hold = IS_EQF(beztn->vec[1][1], beztn->vec[0][1]) && - IS_EQF(prev->vec[1][1], prev->vec[2][1]); - } - /* This interpolation type induces movement even between identical keys. */ - else { - hold = !ELEM(prev->ipo, BEZT_IPO_ELASTIC); - } - - if (hold) { - info->flag |= ACTKEYBLOCK_FLAG_STATIC_HOLD | ACTKEYBLOCK_FLAG_ANY_HOLD; - } - } - - /* Remember non-bezier interpolation info. */ - if (prev->ipo != BEZT_IPO_BEZ) { - info->flag |= ACTKEYBLOCK_FLAG_NON_BEZIER; - } - - info->sel = BEZT_ISSEL_ANY(prev) || BEZT_ISSEL_ANY(beztn); -} - -static void add_keyblock_info(ActKeyColumn *col, const ActKeyBlockInfo *block) -{ - /* New curve and block. */ - if (col->totcurve <= 1 && col->totblock == 0) { - memcpy(&col->block, block, sizeof(ActKeyBlockInfo)); - } - /* Existing curve. */ - else { - col->block.conflict |= (col->block.flag ^ block->flag); - col->block.flag |= block->flag; - col->block.sel |= block->sel; - } - - if (block->flag) { - col->totblock++; - } -} - -static void add_bezt_to_keyblocks_list(DLRBT_Tree *keys, BezTriple *bezt, int bezt_len) -{ - ActKeyColumn *col = keys->first; - - if (bezt && bezt_len >= 2) { - ActKeyBlockInfo block; - - /* Find the first key column while inserting dummy blocks. */ - for (; col != NULL && is_cfra_lt(col->cfra, bezt[0].vec[1][0]); col = col->next) { - add_keyblock_info(col, &dummy_keyblock); - } - - BLI_assert(col != NULL); - - /* Insert real blocks. */ - for (int v = 1; col != NULL && v < bezt_len; v++, bezt++) { - /* Wrong order of bezier keys: resync position. */ - if (is_cfra_lt(bezt[1].vec[1][0], bezt[0].vec[1][0])) { - /* Backtrack to find the right location. */ - if (is_cfra_lt(bezt[1].vec[1][0], col->cfra)) { - ActKeyColumn *newcol = (ActKeyColumn *)BLI_dlrbTree_search_exact( - keys, compare_ak_cfraPtr, &bezt[1].vec[1][0]); - - if (newcol != NULL) { - col = newcol; - - /* The previous keyblock is garbage too. */ - if (col->prev != NULL) { - add_keyblock_info(col->prev, &dummy_keyblock); - } - } - else { - BLI_assert(false); - } - } - - continue; - } - - /* Normal sequence */ - BLI_assert(is_cfra_eq(col->cfra, bezt[0].vec[1][0])); - - compute_keyblock_data(&block, bezt, bezt + 1); - - for (; col != NULL && is_cfra_lt(col->cfra, bezt[1].vec[1][0]); col = col->next) { - add_keyblock_info(col, &block); - } - - BLI_assert(col != NULL); - } - } - - /* Insert dummy blocks at the end. */ - for (; col != NULL; col = col->next) { - add_keyblock_info(col, &dummy_keyblock); - } -} - -/* Walk through columns and propagate blocks and totcurve. - * - * This must be called even by animation sources that don't generate - * keyblocks to keep the data structure consistent after adding columns. - */ -static void update_keyblocks(DLRBT_Tree *keys, BezTriple *bezt, int bezt_len) -{ - /* Recompute the prev/next linked list. */ - BLI_dlrbTree_linkedlist_sync(keys); - - /* Find the curve count */ - int max_curve = 0; - - LISTBASE_FOREACH (ActKeyColumn *, col, keys) { - max_curve = MAX2(max_curve, col->totcurve); - } - - /* Propagate blocks to inserted keys */ - ActKeyColumn *prev_ready = NULL; - - LISTBASE_FOREACH (ActKeyColumn *, col, keys) { - /* Pre-existing column. */ - if (col->totcurve > 0) { - prev_ready = col; - } - /* Newly inserted column, so copy block data from previous. */ - else if (prev_ready != NULL) { - col->totblock = prev_ready->totblock; - memcpy(&col->block, &prev_ready->block, sizeof(ActKeyBlockInfo)); - } - - col->totcurve = max_curve + 1; - } - - /* Add blocks on top */ - add_bezt_to_keyblocks_list(keys, bezt, bezt_len); -} - -/* --------- */ - -bool actkeyblock_is_valid(ActKeyColumn *ac) -{ - return ac != NULL && ac->next != NULL && ac->totblock > 0; -} - -/* Checks if ActKeyBlock should exist... */ -int actkeyblock_get_valid_hold(ActKeyColumn *ac) -{ - /* check that block is valid */ - if (!actkeyblock_is_valid(ac)) { - return 0; - } - - const int hold_mask = (ACTKEYBLOCK_FLAG_ANY_HOLD | ACTKEYBLOCK_FLAG_STATIC_HOLD); - return (ac->block.flag & ~ac->block.conflict) & hold_mask; -} +#include "ED_keyframes_keylist.h" /* *************************** Keyframe Drawing *************************** */ @@ -1029,257 +527,3 @@ void draw_masklay_channel(View2D *v2d, BLI_dlrbTree_free(&keys); } - -/* *************************** Keyframe List Conversions *************************** */ - -void summary_to_keylist(bAnimContext *ac, DLRBT_Tree *keys, int saction_flag) -{ - if (ac) { - ListBase anim_data = {NULL, NULL}; - bAnimListElem *ale; - int filter; - - /* get F-Curves to take keyframes from */ - filter = ANIMFILTER_DATA_VISIBLE; - ANIM_animdata_filter(ac, &anim_data, filter, ac->data, ac->datatype); - - /* loop through each F-Curve, grabbing the keyframes */ - for (ale = anim_data.first; ale; ale = ale->next) { - /* Why not use all #eAnim_KeyType here? - * All of the other key types are actually "summaries" themselves, - * and will just end up duplicating stuff that comes up through - * standard filtering of just F-Curves. Given the way that these work, - * there isn't really any benefit at all from including them. - Aligorith */ - - switch (ale->datatype) { - case ALE_FCURVE: - fcurve_to_keylist(ale->adt, ale->data, keys, saction_flag); - break; - case ALE_MASKLAY: - mask_to_keylist(ac->ads, ale->data, keys); - break; - case ALE_GPFRAME: - gpl_to_keylist(ac->ads, ale->data, keys); - break; - default: - // printf("%s: datatype %d unhandled\n", __func__, ale->datatype); - break; - } - } - - ANIM_animdata_freelist(&anim_data); - } -} - -void scene_to_keylist(bDopeSheet *ads, Scene *sce, DLRBT_Tree *keys, int saction_flag) -{ - bAnimContext ac = {NULL}; - ListBase anim_data = {NULL, NULL}; - bAnimListElem *ale; - int filter; - - bAnimListElem dummychan = {NULL}; - - if (sce == NULL) { - return; - } - - /* create a dummy wrapper data to work with */ - dummychan.type = ANIMTYPE_SCENE; - dummychan.data = sce; - dummychan.id = &sce->id; - dummychan.adt = sce->adt; - - ac.ads = ads; - ac.data = &dummychan; - ac.datatype = ANIMCONT_CHANNEL; - - /* get F-Curves to take keyframes from */ - filter = ANIMFILTER_DATA_VISIBLE; /* curves only */ - ANIM_animdata_filter(&ac, &anim_data, filter, ac.data, ac.datatype); - - /* loop through each F-Curve, grabbing the keyframes */ - for (ale = anim_data.first; ale; ale = ale->next) { - fcurve_to_keylist(ale->adt, ale->data, keys, saction_flag); - } - - ANIM_animdata_freelist(&anim_data); -} - -void ob_to_keylist(bDopeSheet *ads, Object *ob, DLRBT_Tree *keys, int saction_flag) -{ - bAnimContext ac = {NULL}; - ListBase anim_data = {NULL, NULL}; - bAnimListElem *ale; - int filter; - - bAnimListElem dummychan = {NULL}; - Base dummybase = {NULL}; - - if (ob == NULL) { - return; - } - - /* create a dummy wrapper data to work with */ - dummybase.object = ob; - - dummychan.type = ANIMTYPE_OBJECT; - dummychan.data = &dummybase; - dummychan.id = &ob->id; - dummychan.adt = ob->adt; - - ac.ads = ads; - ac.data = &dummychan; - ac.datatype = ANIMCONT_CHANNEL; - - /* get F-Curves to take keyframes from */ - filter = ANIMFILTER_DATA_VISIBLE; /* curves only */ - ANIM_animdata_filter(&ac, &anim_data, filter, ac.data, ac.datatype); - - /* loop through each F-Curve, grabbing the keyframes */ - for (ale = anim_data.first; ale; ale = ale->next) { - fcurve_to_keylist(ale->adt, ale->data, keys, saction_flag); - } - - ANIM_animdata_freelist(&anim_data); -} - -void cachefile_to_keylist(bDopeSheet *ads, - CacheFile *cache_file, - DLRBT_Tree *keys, - int saction_flag) -{ - if (cache_file == NULL) { - return; - } - - /* create a dummy wrapper data to work with */ - bAnimListElem dummychan = {NULL}; - dummychan.type = ANIMTYPE_DSCACHEFILE; - dummychan.data = cache_file; - dummychan.id = &cache_file->id; - dummychan.adt = cache_file->adt; - - bAnimContext ac = {NULL}; - ac.ads = ads; - ac.data = &dummychan; - ac.datatype = ANIMCONT_CHANNEL; - - /* get F-Curves to take keyframes from */ - ListBase anim_data = {NULL, NULL}; - int filter = ANIMFILTER_DATA_VISIBLE; /* curves only */ - ANIM_animdata_filter(&ac, &anim_data, filter, ac.data, ac.datatype); - - /* loop through each F-Curve, grabbing the keyframes */ - LISTBASE_FOREACH (bAnimListElem *, ale, &anim_data) { - fcurve_to_keylist(ale->adt, ale->data, keys, saction_flag); - } - - ANIM_animdata_freelist(&anim_data); -} - -void fcurve_to_keylist(AnimData *adt, FCurve *fcu, DLRBT_Tree *keys, int saction_flag) -{ - if (fcu && fcu->totvert && fcu->bezt) { - /* apply NLA-mapping (if applicable) */ - if (adt) { - ANIM_nla_mapping_apply_fcurve(adt, fcu, 0, 0); - } - - /* Check if the curve is cyclic. */ - bool is_cyclic = BKE_fcurve_is_cyclic(fcu) && (fcu->totvert >= 2); - bool do_extremes = (saction_flag & SACTION_SHOW_EXTREMES) != 0; - - /* loop through beztriples, making ActKeysColumns */ - BezTripleChain chain = {0}; - - for (int v = 0; v < fcu->totvert; v++) { - chain.cur = &fcu->bezt[v]; - - /* Neighbor keys, accounting for being cyclic. */ - if (do_extremes) { - chain.prev = (v > 0) ? &fcu->bezt[v - 1] : is_cyclic ? &fcu->bezt[fcu->totvert - 2] : NULL; - chain.next = (v + 1 < fcu->totvert) ? &fcu->bezt[v + 1] : is_cyclic ? &fcu->bezt[1] : NULL; - } - - add_bezt_to_keycolumns_list(keys, &chain); - } - - /* Update keyblocks. */ - update_keyblocks(keys, fcu->bezt, fcu->totvert); - - /* unapply NLA-mapping if applicable */ - if (adt) { - ANIM_nla_mapping_apply_fcurve(adt, fcu, 1, 0); - } - } -} - -void agroup_to_keylist(AnimData *adt, bActionGroup *agrp, DLRBT_Tree *keys, int saction_flag) -{ - FCurve *fcu; - - if (agrp) { - /* loop through F-Curves */ - for (fcu = agrp->channels.first; fcu && fcu->grp == agrp; fcu = fcu->next) { - fcurve_to_keylist(adt, fcu, keys, saction_flag); - } - } -} - -void action_to_keylist(AnimData *adt, bAction *act, DLRBT_Tree *keys, int saction_flag) -{ - FCurve *fcu; - - if (act) { - /* loop through F-Curves */ - for (fcu = act->curves.first; fcu; fcu = fcu->next) { - fcurve_to_keylist(adt, fcu, keys, saction_flag); - } - } -} - -void gpencil_to_keylist(bDopeSheet *ads, bGPdata *gpd, DLRBT_Tree *keys, const bool active) -{ - bGPDlayer *gpl; - - if (gpd && keys) { - /* for now, just aggregate out all the frames, but only for visible layers */ - for (gpl = gpd->layers.last; gpl; gpl = gpl->prev) { - if ((gpl->flag & GP_LAYER_HIDE) == 0) { - if ((!active) || ((active) && (gpl->flag & GP_LAYER_SELECT))) { - gpl_to_keylist(ads, gpl, keys); - } - } - } - } -} - -void gpl_to_keylist(bDopeSheet *UNUSED(ads), bGPDlayer *gpl, DLRBT_Tree *keys) -{ - bGPDframe *gpf; - - if (gpl && keys) { - /* Although the frames should already be in an ordered list, - * they are not suitable for displaying yet. */ - for (gpf = gpl->frames.first; gpf; gpf = gpf->next) { - add_gpframe_to_keycolumns_list(keys, gpf); - } - - update_keyblocks(keys, NULL, 0); - } -} - -void mask_to_keylist(bDopeSheet *UNUSED(ads), MaskLayer *masklay, DLRBT_Tree *keys) -{ - MaskLayerShape *masklay_shape; - - if (masklay && keys) { - for (masklay_shape = masklay->splines_shapes.first; masklay_shape; - masklay_shape = masklay_shape->next) { - add_masklay_to_keycolumns_list(keys, masklay_shape); - } - - update_keyblocks(keys, NULL, 0); - } -} diff --git a/source/blender/editors/animation/keyframes_keylist.c b/source/blender/editors/animation/keyframes_keylist.c new file mode 100644 index 00000000000..47ed2b56300 --- /dev/null +++ b/source/blender/editors/animation/keyframes_keylist.c @@ -0,0 +1,793 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * The Original Code is Copyright (C) 2009 Blender Foundation, Joshua Leung + * All rights reserved. + */ + +/** \file + * \ingroup edanimation + */ + +/* System includes ----------------------------------------------------- */ + +#include <float.h> +#include <math.h> +#include <stdlib.h> +#include <string.h> + +#include "MEM_guardedalloc.h" + +#include "BLI_dlrbTree.h" +#include "BLI_listbase.h" +#include "BLI_utildefines.h" + +#include "DNA_anim_types.h" +#include "DNA_cachefile_types.h" +#include "DNA_gpencil_types.h" +#include "DNA_mask_types.h" +#include "DNA_object_types.h" +#include "DNA_scene_types.h" + +#include "BKE_fcurve.h" + +#include "ED_anim_api.h" +#include "ED_keyframes_keylist.h" + +/* *************************** Keyframe Processing *************************** */ + +/* ActKeyColumns (Keyframe Columns) ------------------------------------------ */ + +BLI_INLINE bool is_cfra_eq(float a, float b) +{ + return IS_EQT(a, b, BEZT_BINARYSEARCH_THRESH); +} + +BLI_INLINE bool is_cfra_lt(float a, float b) +{ + return (b - a) > BEZT_BINARYSEARCH_THRESH; +} + +/* Comparator callback used for ActKeyColumns and cframe float-value pointer */ +/* NOTE: this is exported to other modules that use the ActKeyColumns for finding keyframes */ +short compare_ak_cfraPtr(void *node, void *data) +{ + ActKeyColumn *ak = (ActKeyColumn *)node; + const float *cframe = data; + float val = *cframe; + + if (is_cfra_eq(val, ak->cfra)) { + return 0; + } + + if (val < ak->cfra) { + return -1; + } + return 1; +} + +/* --------------- */ + +/* Set of references to three logically adjacent keys. */ +typedef struct BezTripleChain { + /* Current keyframe. */ + BezTriple *cur; + + /* Logical neighbors. May be NULL. */ + BezTriple *prev, *next; +} BezTripleChain; + +/* Categorize the interpolation & handle type of the keyframe. */ +static eKeyframeHandleDrawOpts bezt_handle_type(BezTriple *bezt) +{ + if (bezt->h1 == HD_AUTO_ANIM && bezt->h2 == HD_AUTO_ANIM) { + return KEYFRAME_HANDLE_AUTO_CLAMP; + } + if (ELEM(bezt->h1, HD_AUTO_ANIM, HD_AUTO) && ELEM(bezt->h2, HD_AUTO_ANIM, HD_AUTO)) { + return KEYFRAME_HANDLE_AUTO; + } + if (bezt->h1 == HD_VECT && bezt->h2 == HD_VECT) { + return KEYFRAME_HANDLE_VECTOR; + } + if (ELEM(HD_FREE, bezt->h1, bezt->h2)) { + return KEYFRAME_HANDLE_FREE; + } + return KEYFRAME_HANDLE_ALIGNED; +} + +/* Determine if the keyframe is an extreme by comparing with neighbors. + * Ends of fixed-value sections and of the whole curve are also marked. + */ +static eKeyframeExtremeDrawOpts bezt_extreme_type(BezTripleChain *chain) +{ + if (chain->prev == NULL && chain->next == NULL) { + return KEYFRAME_EXTREME_NONE; + } + + /* Keyframe values for the current one and neighbors. */ + float cur_y = chain->cur->vec[1][1]; + float prev_y = cur_y, next_y = cur_y; + + if (chain->prev && !IS_EQF(cur_y, chain->prev->vec[1][1])) { + prev_y = chain->prev->vec[1][1]; + } + if (chain->next && !IS_EQF(cur_y, chain->next->vec[1][1])) { + next_y = chain->next->vec[1][1]; + } + + /* Static hold. */ + if (prev_y == cur_y && next_y == cur_y) { + return KEYFRAME_EXTREME_FLAT; + } + + /* Middle of an incline. */ + if ((prev_y < cur_y && next_y > cur_y) || (prev_y > cur_y && next_y < cur_y)) { + return KEYFRAME_EXTREME_NONE; + } + + /* Bezier handle values for the overshoot check. */ + bool l_bezier = chain->prev && chain->prev->ipo == BEZT_IPO_BEZ; + bool r_bezier = chain->next && chain->cur->ipo == BEZT_IPO_BEZ; + float handle_l = l_bezier ? chain->cur->vec[0][1] : cur_y; + float handle_r = r_bezier ? chain->cur->vec[2][1] : cur_y; + + /* Detect extremes. One of the neighbors is allowed to be equal to current. */ + if (prev_y < cur_y || next_y < cur_y) { + bool is_overshoot = (handle_l > cur_y || handle_r > cur_y); + + return KEYFRAME_EXTREME_MAX | (is_overshoot ? KEYFRAME_EXTREME_MIXED : 0); + } + + if (prev_y > cur_y || next_y > cur_y) { + bool is_overshoot = (handle_l < cur_y || handle_r < cur_y); + + return KEYFRAME_EXTREME_MIN | (is_overshoot ? KEYFRAME_EXTREME_MIXED : 0); + } + + return KEYFRAME_EXTREME_NONE; +} + +/* Comparator callback used for ActKeyColumns and BezTripleChain */ +static short compare_ak_bezt(void *node, void *data) +{ + BezTripleChain *chain = data; + + return compare_ak_cfraPtr(node, &chain->cur->vec[1][0]); +} + +/* New node callback used for building ActKeyColumns from BezTripleChain */ +static DLRBT_Node *nalloc_ak_bezt(void *data) +{ + ActKeyColumn *ak = MEM_callocN(sizeof(ActKeyColumn), "ActKeyColumn"); + BezTripleChain *chain = data; + BezTriple *bezt = chain->cur; + + /* store settings based on state of BezTriple */ + ak->cfra = bezt->vec[1][0]; + ak->sel = BEZT_ISSEL_ANY(bezt) ? SELECT : 0; + ak->key_type = BEZKEYTYPE(bezt); + ak->handle_type = bezt_handle_type(bezt); + ak->extreme_type = bezt_extreme_type(chain); + + /* count keyframes in this column */ + ak->totkey = 1; + + return (DLRBT_Node *)ak; +} + +/* Node updater callback used for building ActKeyColumns from BezTripleChain */ +static void nupdate_ak_bezt(void *node, void *data) +{ + ActKeyColumn *ak = node; + BezTripleChain *chain = data; + BezTriple *bezt = chain->cur; + + /* set selection status and 'touched' status */ + if (BEZT_ISSEL_ANY(bezt)) { + ak->sel = SELECT; + } + + /* count keyframes in this column */ + ak->totkey++; + + /* For keyframe type, 'proper' keyframes have priority over breakdowns + * (and other types for now). */ + if (BEZKEYTYPE(bezt) == BEZT_KEYTYPE_KEYFRAME) { + ak->key_type = BEZT_KEYTYPE_KEYFRAME; + } + + /* For interpolation type, select the highest value (enum is sorted). */ + ak->handle_type = MAX2(ak->handle_type, bezt_handle_type(bezt)); + + /* For extremes, detect when combining different states. */ + char new_extreme = bezt_extreme_type(chain); + + if (new_extreme != ak->extreme_type) { + /* Replace the flat status without adding mixed. */ + if (ak->extreme_type == KEYFRAME_EXTREME_FLAT) { + ak->extreme_type = new_extreme; + } + else if (new_extreme != KEYFRAME_EXTREME_FLAT) { + ak->extreme_type |= (new_extreme | KEYFRAME_EXTREME_MIXED); + } + } +} + +/* ......... */ + +/* Comparator callback used for ActKeyColumns and GPencil frame */ +static short compare_ak_gpframe(void *node, void *data) +{ + bGPDframe *gpf = (bGPDframe *)data; + + float frame = gpf->framenum; + return compare_ak_cfraPtr(node, &frame); +} + +/* New node callback used for building ActKeyColumns from GPencil frames */ +static DLRBT_Node *nalloc_ak_gpframe(void *data) +{ + ActKeyColumn *ak = MEM_callocN(sizeof(ActKeyColumn), "ActKeyColumnGPF"); + bGPDframe *gpf = (bGPDframe *)data; + + /* store settings based on state of BezTriple */ + ak->cfra = gpf->framenum; + ak->sel = (gpf->flag & GP_FRAME_SELECT) ? SELECT : 0; + ak->key_type = gpf->key_type; + + /* count keyframes in this column */ + ak->totkey = 1; + /* Set as visible block. */ + ak->totblock = 1; + ak->block.sel = ak->sel; + ak->block.flag |= ACTKEYBLOCK_FLAG_GPENCIL; + + return (DLRBT_Node *)ak; +} + +/* Node updater callback used for building ActKeyColumns from GPencil frames */ +static void nupdate_ak_gpframe(void *node, void *data) +{ + ActKeyColumn *ak = (ActKeyColumn *)node; + bGPDframe *gpf = (bGPDframe *)data; + + /* set selection status and 'touched' status */ + if (gpf->flag & GP_FRAME_SELECT) { + ak->sel = SELECT; + } + + /* count keyframes in this column */ + ak->totkey++; + + /* for keyframe type, 'proper' keyframes have priority over breakdowns + * (and other types for now). */ + if (gpf->key_type == BEZT_KEYTYPE_KEYFRAME) { + ak->key_type = BEZT_KEYTYPE_KEYFRAME; + } +} + +/* ......... */ + +/* Comparator callback used for ActKeyColumns and GPencil frame */ +static short compare_ak_masklayshape(void *node, void *data) +{ + MaskLayerShape *masklay_shape = (MaskLayerShape *)data; + + float frame = masklay_shape->frame; + return compare_ak_cfraPtr(node, &frame); +} + +/* New node callback used for building ActKeyColumns from GPencil frames */ +static DLRBT_Node *nalloc_ak_masklayshape(void *data) +{ + ActKeyColumn *ak = MEM_callocN(sizeof(ActKeyColumn), "ActKeyColumnGPF"); + MaskLayerShape *masklay_shape = (MaskLayerShape *)data; + + /* store settings based on state of BezTriple */ + ak->cfra = masklay_shape->frame; + ak->sel = (masklay_shape->flag & MASK_SHAPE_SELECT) ? SELECT : 0; + + /* count keyframes in this column */ + ak->totkey = 1; + + return (DLRBT_Node *)ak; +} + +/* Node updater callback used for building ActKeyColumns from GPencil frames */ +static void nupdate_ak_masklayshape(void *node, void *data) +{ + ActKeyColumn *ak = (ActKeyColumn *)node; + MaskLayerShape *masklay_shape = (MaskLayerShape *)data; + + /* set selection status and 'touched' status */ + if (masklay_shape->flag & MASK_SHAPE_SELECT) { + ak->sel = SELECT; + } + + /* count keyframes in this column */ + ak->totkey++; +} + +/* --------------- */ + +/* Add the given BezTriple to the given 'list' of Keyframes */ +static void add_bezt_to_keycolumns_list(DLRBT_Tree *keys, BezTripleChain *bezt) +{ + if (ELEM(NULL, keys, bezt)) { + return; + } + + BLI_dlrbTree_add(keys, compare_ak_bezt, nalloc_ak_bezt, nupdate_ak_bezt, bezt); +} + +/* Add the given GPencil Frame to the given 'list' of Keyframes */ +static void add_gpframe_to_keycolumns_list(DLRBT_Tree *keys, bGPDframe *gpf) +{ + if (ELEM(NULL, keys, gpf)) { + return; + } + + BLI_dlrbTree_add(keys, compare_ak_gpframe, nalloc_ak_gpframe, nupdate_ak_gpframe, gpf); +} + +/* Add the given MaskLayerShape Frame to the given 'list' of Keyframes */ +static void add_masklay_to_keycolumns_list(DLRBT_Tree *keys, MaskLayerShape *masklay_shape) +{ + if (ELEM(NULL, keys, masklay_shape)) { + return; + } + + BLI_dlrbTree_add(keys, + compare_ak_masklayshape, + nalloc_ak_masklayshape, + nupdate_ak_masklayshape, + masklay_shape); +} + +/* ActKeyBlocks (Long Keyframes) ------------------------------------------ */ + +static const ActKeyBlockInfo dummy_keyblock = {0}; + +static void compute_keyblock_data(ActKeyBlockInfo *info, BezTriple *prev, BezTriple *beztn) +{ + memset(info, 0, sizeof(ActKeyBlockInfo)); + + if (BEZKEYTYPE(beztn) == BEZT_KEYTYPE_MOVEHOLD) { + /* Animator tagged a "moving hold" + * - Previous key must also be tagged as a moving hold, otherwise + * we're just dealing with the first of a pair, and we don't + * want to be creating any phantom holds... + */ + if (BEZKEYTYPE(prev) == BEZT_KEYTYPE_MOVEHOLD) { + info->flag |= ACTKEYBLOCK_FLAG_MOVING_HOLD | ACTKEYBLOCK_FLAG_ANY_HOLD; + } + } + + /* Check for same values... + * - Handles must have same central value as each other + * - Handles which control that section of the curve must be constant + */ + if (IS_EQF(beztn->vec[1][1], prev->vec[1][1])) { + bool hold; + + /* Only check handles in case of actual bezier interpolation. */ + if (prev->ipo == BEZT_IPO_BEZ) { + hold = IS_EQF(beztn->vec[1][1], beztn->vec[0][1]) && + IS_EQF(prev->vec[1][1], prev->vec[2][1]); + } + /* This interpolation type induces movement even between identical keys. */ + else { + hold = !ELEM(prev->ipo, BEZT_IPO_ELASTIC); + } + + if (hold) { + info->flag |= ACTKEYBLOCK_FLAG_STATIC_HOLD | ACTKEYBLOCK_FLAG_ANY_HOLD; + } + } + + /* Remember non-bezier interpolation info. */ + if (prev->ipo != BEZT_IPO_BEZ) { + info->flag |= ACTKEYBLOCK_FLAG_NON_BEZIER; + } + + info->sel = BEZT_ISSEL_ANY(prev) || BEZT_ISSEL_ANY(beztn); +} + +static void add_keyblock_info(ActKeyColumn *col, const ActKeyBlockInfo *block) +{ + /* New curve and block. */ + if (col->totcurve <= 1 && col->totblock == 0) { + memcpy(&col->block, block, sizeof(ActKeyBlockInfo)); + } + /* Existing curve. */ + else { + col->block.conflict |= (col->block.flag ^ block->flag); + col->block.flag |= block->flag; + col->block.sel |= block->sel; + } + + if (block->flag) { + col->totblock++; + } +} + +static void add_bezt_to_keyblocks_list(DLRBT_Tree *keys, BezTriple *bezt, int bezt_len) +{ + ActKeyColumn *col = keys->first; + + if (bezt && bezt_len >= 2) { + ActKeyBlockInfo block; + + /* Find the first key column while inserting dummy blocks. */ + for (; col != NULL && is_cfra_lt(col->cfra, bezt[0].vec[1][0]); col = col->next) { + add_keyblock_info(col, &dummy_keyblock); + } + + BLI_assert(col != NULL); + + /* Insert real blocks. */ + for (int v = 1; col != NULL && v < bezt_len; v++, bezt++) { + /* Wrong order of bezier keys: resync position. */ + if (is_cfra_lt(bezt[1].vec[1][0], bezt[0].vec[1][0])) { + /* Backtrack to find the right location. */ + if (is_cfra_lt(bezt[1].vec[1][0], col->cfra)) { + ActKeyColumn *newcol = (ActKeyColumn *)BLI_dlrbTree_search_exact( + keys, compare_ak_cfraPtr, &bezt[1].vec[1][0]); + + if (newcol != NULL) { + col = newcol; + + /* The previous keyblock is garbage too. */ + if (col->prev != NULL) { + add_keyblock_info(col->prev, &dummy_keyblock); + } + } + else { + BLI_assert(false); + } + } + + continue; + } + + /* Normal sequence */ + BLI_assert(is_cfra_eq(col->cfra, bezt[0].vec[1][0])); + + compute_keyblock_data(&block, bezt, bezt + 1); + + for (; col != NULL && is_cfra_lt(col->cfra, bezt[1].vec[1][0]); col = col->next) { + add_keyblock_info(col, &block); + } + + BLI_assert(col != NULL); + } + } + + /* Insert dummy blocks at the end. */ + for (; col != NULL; col = col->next) { + add_keyblock_info(col, &dummy_keyblock); + } +} + +/* Walk through columns and propagate blocks and totcurve. + * + * This must be called even by animation sources that don't generate + * keyblocks to keep the data structure consistent after adding columns. + */ +static void update_keyblocks(DLRBT_Tree *keys, BezTriple *bezt, int bezt_len) +{ + /* Recompute the prev/next linked list. */ + BLI_dlrbTree_linkedlist_sync(keys); + + /* Find the curve count */ + int max_curve = 0; + + LISTBASE_FOREACH (ActKeyColumn *, col, keys) { + max_curve = MAX2(max_curve, col->totcurve); + } + + /* Propagate blocks to inserted keys */ + ActKeyColumn *prev_ready = NULL; + + LISTBASE_FOREACH (ActKeyColumn *, col, keys) { + /* Pre-existing column. */ + if (col->totcurve > 0) { + prev_ready = col; + } + /* Newly inserted column, so copy block data from previous. */ + else if (prev_ready != NULL) { + col->totblock = prev_ready->totblock; + memcpy(&col->block, &prev_ready->block, sizeof(ActKeyBlockInfo)); + } + + col->totcurve = max_curve + 1; + } + + /* Add blocks on top */ + add_bezt_to_keyblocks_list(keys, bezt, bezt_len); +} + +/* --------- */ + +bool actkeyblock_is_valid(ActKeyColumn *ac) +{ + return ac != NULL && ac->next != NULL && ac->totblock > 0; +} + +/* Checks if ActKeyBlock should exist... */ +int actkeyblock_get_valid_hold(ActKeyColumn *ac) +{ + /* check that block is valid */ + if (!actkeyblock_is_valid(ac)) { + return 0; + } + + const int hold_mask = (ACTKEYBLOCK_FLAG_ANY_HOLD | ACTKEYBLOCK_FLAG_STATIC_HOLD); + return (ac->block.flag & ~ac->block.conflict) & hold_mask; +} + +/* *************************** Keyframe List Conversions *************************** */ + +void summary_to_keylist(bAnimContext *ac, DLRBT_Tree *keys, int saction_flag) +{ + if (ac) { + ListBase anim_data = {NULL, NULL}; + bAnimListElem *ale; + int filter; + + /* get F-Curves to take keyframes from */ + filter = ANIMFILTER_DATA_VISIBLE; + ANIM_animdata_filter(ac, &anim_data, filter, ac->data, ac->datatype); + + /* loop through each F-Curve, grabbing the keyframes */ + for (ale = anim_data.first; ale; ale = ale->next) { + /* Why not use all #eAnim_KeyType here? + * All of the other key types are actually "summaries" themselves, + * and will just end up duplicating stuff that comes up through + * standard filtering of just F-Curves. Given the way that these work, + * there isn't really any benefit at all from including them. - Aligorith */ + + switch (ale->datatype) { + case ALE_FCURVE: + fcurve_to_keylist(ale->adt, ale->data, keys, saction_flag); + break; + case ALE_MASKLAY: + mask_to_keylist(ac->ads, ale->data, keys); + break; + case ALE_GPFRAME: + gpl_to_keylist(ac->ads, ale->data, keys); + break; + default: + // printf("%s: datatype %d unhandled\n", __func__, ale->datatype); + break; + } + } + + ANIM_animdata_freelist(&anim_data); + } +} + +void scene_to_keylist(bDopeSheet *ads, Scene *sce, DLRBT_Tree *keys, int saction_flag) +{ + bAnimContext ac = {NULL}; + ListBase anim_data = {NULL, NULL}; + bAnimListElem *ale; + int filter; + + bAnimListElem dummychan = {NULL}; + + if (sce == NULL) { + return; + } + + /* create a dummy wrapper data to work with */ + dummychan.type = ANIMTYPE_SCENE; + dummychan.data = sce; + dummychan.id = &sce->id; + dummychan.adt = sce->adt; + + ac.ads = ads; + ac.data = &dummychan; + ac.datatype = ANIMCONT_CHANNEL; + + /* get F-Curves to take keyframes from */ + filter = ANIMFILTER_DATA_VISIBLE; /* curves only */ + ANIM_animdata_filter(&ac, &anim_data, filter, ac.data, ac.datatype); + + /* loop through each F-Curve, grabbing the keyframes */ + for (ale = anim_data.first; ale; ale = ale->next) { + fcurve_to_keylist(ale->adt, ale->data, keys, saction_flag); + } + + ANIM_animdata_freelist(&anim_data); +} + +void ob_to_keylist(bDopeSheet *ads, Object *ob, DLRBT_Tree *keys, int saction_flag) +{ + bAnimContext ac = {NULL}; + ListBase anim_data = {NULL, NULL}; + bAnimListElem *ale; + int filter; + + bAnimListElem dummychan = {NULL}; + Base dummybase = {NULL}; + + if (ob == NULL) { + return; + } + + /* create a dummy wrapper data to work with */ + dummybase.object = ob; + + dummychan.type = ANIMTYPE_OBJECT; + dummychan.data = &dummybase; + dummychan.id = &ob->id; + dummychan.adt = ob->adt; + + ac.ads = ads; + ac.data = &dummychan; + ac.datatype = ANIMCONT_CHANNEL; + + /* get F-Curves to take keyframes from */ + filter = ANIMFILTER_DATA_VISIBLE; /* curves only */ + ANIM_animdata_filter(&ac, &anim_data, filter, ac.data, ac.datatype); + + /* loop through each F-Curve, grabbing the keyframes */ + for (ale = anim_data.first; ale; ale = ale->next) { + fcurve_to_keylist(ale->adt, ale->data, keys, saction_flag); + } + + ANIM_animdata_freelist(&anim_data); +} + +void cachefile_to_keylist(bDopeSheet *ads, + CacheFile *cache_file, + DLRBT_Tree *keys, + int saction_flag) +{ + if (cache_file == NULL) { + return; + } + + /* create a dummy wrapper data to work with */ + bAnimListElem dummychan = {NULL}; + dummychan.type = ANIMTYPE_DSCACHEFILE; + dummychan.data = cache_file; + dummychan.id = &cache_file->id; + dummychan.adt = cache_file->adt; + + bAnimContext ac = {NULL}; + ac.ads = ads; + ac.data = &dummychan; + ac.datatype = ANIMCONT_CHANNEL; + + /* get F-Curves to take keyframes from */ + ListBase anim_data = {NULL, NULL}; + int filter = ANIMFILTER_DATA_VISIBLE; /* curves only */ + ANIM_animdata_filter(&ac, &anim_data, filter, ac.data, ac.datatype); + + /* loop through each F-Curve, grabbing the keyframes */ + LISTBASE_FOREACH (bAnimListElem *, ale, &anim_data) { + fcurve_to_keylist(ale->adt, ale->data, keys, saction_flag); + } + + ANIM_animdata_freelist(&anim_data); +} + +void fcurve_to_keylist(AnimData *adt, FCurve *fcu, DLRBT_Tree *keys, int saction_flag) +{ + if (fcu && fcu->totvert && fcu->bezt) { + /* apply NLA-mapping (if applicable) */ + if (adt) { + ANIM_nla_mapping_apply_fcurve(adt, fcu, 0, 0); + } + + /* Check if the curve is cyclic. */ + bool is_cyclic = BKE_fcurve_is_cyclic(fcu) && (fcu->totvert >= 2); + bool do_extremes = (saction_flag & SACTION_SHOW_EXTREMES) != 0; + + /* loop through beztriples, making ActKeysColumns */ + BezTripleChain chain = {0}; + + for (int v = 0; v < fcu->totvert; v++) { + chain.cur = &fcu->bezt[v]; + + /* Neighbor keys, accounting for being cyclic. */ + if (do_extremes) { + chain.prev = (v > 0) ? &fcu->bezt[v - 1] : is_cyclic ? &fcu->bezt[fcu->totvert - 2] : NULL; + chain.next = (v + 1 < fcu->totvert) ? &fcu->bezt[v + 1] : is_cyclic ? &fcu->bezt[1] : NULL; + } + + add_bezt_to_keycolumns_list(keys, &chain); + } + + /* Update keyblocks. */ + update_keyblocks(keys, fcu->bezt, fcu->totvert); + + /* unapply NLA-mapping if applicable */ + if (adt) { + ANIM_nla_mapping_apply_fcurve(adt, fcu, 1, 0); + } + } +} + +void agroup_to_keylist(AnimData *adt, bActionGroup *agrp, DLRBT_Tree *keys, int saction_flag) +{ + FCurve *fcu; + + if (agrp) { + /* loop through F-Curves */ + for (fcu = agrp->channels.first; fcu && fcu->grp == agrp; fcu = fcu->next) { + fcurve_to_keylist(adt, fcu, keys, saction_flag); + } + } +} + +void action_to_keylist(AnimData *adt, bAction *act, DLRBT_Tree *keys, int saction_flag) +{ + FCurve *fcu; + + if (act) { + /* loop through F-Curves */ + for (fcu = act->curves.first; fcu; fcu = fcu->next) { + fcurve_to_keylist(adt, fcu, keys, saction_flag); + } + } +} + +void gpencil_to_keylist(bDopeSheet *ads, bGPdata *gpd, DLRBT_Tree *keys, const bool active) +{ + bGPDlayer *gpl; + + if (gpd && keys) { + /* for now, just aggregate out all the frames, but only for visible layers */ + for (gpl = gpd->layers.last; gpl; gpl = gpl->prev) { + if ((gpl->flag & GP_LAYER_HIDE) == 0) { + if ((!active) || ((active) && (gpl->flag & GP_LAYER_SELECT))) { + gpl_to_keylist(ads, gpl, keys); + } + } + } + } +} + +void gpl_to_keylist(bDopeSheet *UNUSED(ads), bGPDlayer *gpl, DLRBT_Tree *keys) +{ + bGPDframe *gpf; + + if (gpl && keys) { + /* Although the frames should already be in an ordered list, + * they are not suitable for displaying yet. */ + for (gpf = gpl->frames.first; gpf; gpf = gpf->next) { + add_gpframe_to_keycolumns_list(keys, gpf); + } + + update_keyblocks(keys, NULL, 0); + } +} + +void mask_to_keylist(bDopeSheet *UNUSED(ads), MaskLayer *masklay, DLRBT_Tree *keys) +{ + MaskLayerShape *masklay_shape; + + if (masklay && keys) { + for (masklay_shape = masklay->splines_shapes.first; masklay_shape; + masklay_shape = masklay_shape->next) { + add_masklay_to_keycolumns_list(keys, masklay_shape); + } + + update_keyblocks(keys, NULL, 0); + } +} diff --git a/source/blender/editors/armature/pose_lib.c b/source/blender/editors/armature/pose_lib.c index df3550d5db6..b727e1c176d 100644 --- a/source/blender/editors/armature/pose_lib.c +++ b/source/blender/editors/armature/pose_lib.c @@ -62,7 +62,7 @@ #include "ED_anim_api.h" #include "ED_armature.h" -#include "ED_keyframes_draw.h" +#include "ED_keyframes_keylist.h" #include "ED_keyframes_edit.h" #include "ED_keyframing.h" #include "ED_object.h" diff --git a/source/blender/editors/armature/pose_slide.c b/source/blender/editors/armature/pose_slide.c index 1a1685e4a01..675af6eada9 100644 --- a/source/blender/editors/armature/pose_slide.c +++ b/source/blender/editors/armature/pose_slide.c @@ -77,7 +77,7 @@ #include "UI_resources.h" #include "ED_armature.h" -#include "ED_keyframes_draw.h" +#include "ED_keyframes_keylist.h" #include "ED_markers.h" #include "ED_numinput.h" #include "ED_screen.h" diff --git a/source/blender/editors/include/ED_keyframes_draw.h b/source/blender/editors/include/ED_keyframes_draw.h index 2f8faf1b2bd..ac6dd7dce32 100644 --- a/source/blender/editors/include/ED_keyframes_draw.h +++ b/source/blender/editors/include/ED_keyframes_draw.h @@ -42,99 +42,6 @@ struct bAnimContext; struct bDopeSheet; struct bGPDlayer; -/* ****************************** Base Structs ****************************** */ - -/* Information about the stretch of time from current to the next column */ -typedef struct ActKeyBlockInfo { - /* Combination of flags from all curves. */ - short flag; - /* Mask of flags that differ between curves. */ - short conflict; - - /* Selection flag. */ - char sel; -} ActKeyBlockInfo; - -/* Keyframe Column Struct */ -typedef struct ActKeyColumn { - /* ListBase linkage */ - struct ActKeyColumn *next, *prev; - - /* sorting-tree linkage */ - /** 'children' of this node, less than and greater than it (respectively) */ - struct ActKeyColumn *left, *right; - /** parent of this node in the tree */ - struct ActKeyColumn *parent; - /** DLRB_BLACK or DLRB_RED */ - char tree_col; - - /* keyframe info */ - /** eBezTripe_KeyframeType */ - char key_type; - /** eKeyframeHandleDrawOpts */ - char handle_type; - /** eKeyframeExtremeDrawOpts */ - char extreme_type; - short sel; - float cfra; - - /* key-block info */ - ActKeyBlockInfo block; - - /* number of curves and keys in this column */ - short totcurve, totkey, totblock; -} ActKeyColumn; - -/* ActKeyBlockInfo - Flag */ -typedef enum eActKeyBlock_Hold { - /* Key block represents a moving hold */ - ACTKEYBLOCK_FLAG_MOVING_HOLD = (1 << 0), - /* Key block represents a static hold */ - ACTKEYBLOCK_FLAG_STATIC_HOLD = (1 << 1), - /* Key block represents any kind of hold */ - ACTKEYBLOCK_FLAG_ANY_HOLD = (1 << 2), - /* The curve segment uses non-bezier interpolation */ - ACTKEYBLOCK_FLAG_NON_BEZIER = (1 << 3), - /* The block is grease pencil */ - ACTKEYBLOCK_FLAG_GPENCIL = (1 << 4), -} eActKeyBlock_Flag; - -/* *********************** Keyframe Drawing ****************************** */ - -/* options for keyframe shape drawing */ -typedef enum eKeyframeShapeDrawOpts { - /* only the border */ - KEYFRAME_SHAPE_FRAME = 0, - /* only the inside filling */ - KEYFRAME_SHAPE_INSIDE, - /* the whole thing */ - KEYFRAME_SHAPE_BOTH, -} eKeyframeShapeDrawOpts; - -/* Handle type. */ -typedef enum eKeyframeHandleDrawOpts { - /* Don't draw */ - KEYFRAME_HANDLE_NONE = 0, - /* Various marks in order of increasing display priority. */ - KEYFRAME_HANDLE_AUTO_CLAMP, - KEYFRAME_HANDLE_AUTO, - KEYFRAME_HANDLE_VECTOR, - KEYFRAME_HANDLE_ALIGNED, - KEYFRAME_HANDLE_FREE, -} eKeyframeHandleDrawOpts; - -/* Extreme type. */ -typedef enum eKeyframeExtremeDrawOpts { - KEYFRAME_EXTREME_NONE = 0, - /* Minimum/maximum present. */ - KEYFRAME_EXTREME_MIN = (1 << 0), - KEYFRAME_EXTREME_MAX = (1 << 1), - /* Grouped keys have different states. */ - KEYFRAME_EXTREME_MIXED = (1 << 2), - /* Both neighbors are equal to this key. */ - KEYFRAME_EXTREME_FLAT = (1 << 3), -} eKeyframeExtremeDrawOpts; - /* draw simple diamond-shape keyframe */ /* caller should set up vertex format, bind GPU_SHADER_KEYFRAME_DIAMOND, * immBegin(GPU_PRIM_POINTS, n), then call this n times */ @@ -216,59 +123,6 @@ void draw_masklay_channel(struct View2D *v2d, float yscale_fac, int saction_flag); -/* Keydata Generation --------------- */ -/* F-Curve */ -void fcurve_to_keylist(struct AnimData *adt, - struct FCurve *fcu, - struct DLRBT_Tree *keys, - int saction_flag); -/* Action Group */ -void agroup_to_keylist(struct AnimData *adt, - struct bActionGroup *agrp, - struct DLRBT_Tree *keys, - int saction_flag); -/* Action */ -void action_to_keylist(struct AnimData *adt, - struct bAction *act, - struct DLRBT_Tree *keys, - int saction_flag); -/* Object */ -void ob_to_keylist(struct bDopeSheet *ads, - struct Object *ob, - struct DLRBT_Tree *keys, - int saction_flag); -/* Cache File */ -void cachefile_to_keylist(struct bDopeSheet *ads, - struct CacheFile *cache_file, - struct DLRBT_Tree *keys, - int saction_flag); -/* Scene */ -void scene_to_keylist(struct bDopeSheet *ads, - struct Scene *sce, - struct DLRBT_Tree *keys, - int saction_flag); -/* DopeSheet Summary */ -void summary_to_keylist(struct bAnimContext *ac, struct DLRBT_Tree *keys, int saction_flag); -/* Grease Pencil datablock summary */ -void gpencil_to_keylist(struct bDopeSheet *ads, - struct bGPdata *gpd, - struct DLRBT_Tree *keys, - const bool active); -/* Grease Pencil Layer */ -void gpl_to_keylist(struct bDopeSheet *ads, struct bGPDlayer *gpl, struct DLRBT_Tree *keys); -/* Mask */ -void mask_to_keylist(struct bDopeSheet *ads, struct MaskLayer *masklay, struct DLRBT_Tree *keys); - -/* ActKeyColumn API ---------------- */ -/* Comparator callback used for ActKeyColumns and cframe float-value pointer */ -short compare_ak_cfraPtr(void *node, void *data); - -/* Checks if ActKeyColumn has any block data */ -bool actkeyblock_is_valid(ActKeyColumn *ac); - -/* Checks if ActKeyColumn can be used as a block (i.e. drawn/used to detect "holds") */ -int actkeyblock_get_valid_hold(ActKeyColumn *ac); - #ifdef __cplusplus } #endif diff --git a/source/blender/editors/include/ED_keyframes_keylist.h b/source/blender/editors/include/ED_keyframes_keylist.h new file mode 100644 index 00000000000..3d5b70183df --- /dev/null +++ b/source/blender/editors/include/ED_keyframes_keylist.h @@ -0,0 +1,195 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * The Original Code is Copyright (C) (C) 2009 Blender Foundation, Joshua Leung + * All rights reserved. + */ + +/** \file + * \ingroup editors + */ + +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +struct AnimData; +struct CacheFile; +struct DLRBT_Tree; +struct FCurve; +struct ListBase; +struct MaskLayer; +struct Object; +struct Scene; +struct View2D; +struct bAction; +struct bActionGroup; +struct bAnimContext; +struct bDopeSheet; +struct bGPDlayer; + +/* ****************************** Base Structs ****************************** */ + +/* Information about the stretch of time from current to the next column */ +typedef struct ActKeyBlockInfo { + /* Combination of flags from all curves. */ + short flag; + /* Mask of flags that differ between curves. */ + short conflict; + + /* Selection flag. */ + char sel; +} ActKeyBlockInfo; + +/* Keyframe Column Struct */ +typedef struct ActKeyColumn { + /* ListBase linkage */ + struct ActKeyColumn *next, *prev; + + /* sorting-tree linkage */ + /** 'children' of this node, less than and greater than it (respectively) */ + struct ActKeyColumn *left, *right; + /** parent of this node in the tree */ + struct ActKeyColumn *parent; + /** DLRB_BLACK or DLRB_RED */ + char tree_col; + + /* keyframe info */ + /** eBezTripe_KeyframeType */ + char key_type; + /** eKeyframeHandleDrawOpts */ + char handle_type; + /** eKeyframeExtremeDrawOpts */ + char extreme_type; + short sel; + float cfra; + + /* key-block info */ + ActKeyBlockInfo block; + + /* number of curves and keys in this column */ + short totcurve, totkey, totblock; +} ActKeyColumn; + +/* ActKeyBlockInfo - Flag */ +typedef enum eActKeyBlock_Hold { + /* Key block represents a moving hold */ + ACTKEYBLOCK_FLAG_MOVING_HOLD = (1 << 0), + /* Key block represents a static hold */ + ACTKEYBLOCK_FLAG_STATIC_HOLD = (1 << 1), + /* Key block represents any kind of hold */ + ACTKEYBLOCK_FLAG_ANY_HOLD = (1 << 2), + /* The curve segment uses non-bezier interpolation */ + ACTKEYBLOCK_FLAG_NON_BEZIER = (1 << 3), + /* The block is grease pencil */ + ACTKEYBLOCK_FLAG_GPENCIL = (1 << 4), +} eActKeyBlock_Flag; + +/* *********************** Keyframe Drawing ****************************** */ + +/* options for keyframe shape drawing */ +typedef enum eKeyframeShapeDrawOpts { + /* only the border */ + KEYFRAME_SHAPE_FRAME = 0, + /* only the inside filling */ + KEYFRAME_SHAPE_INSIDE, + /* the whole thing */ + KEYFRAME_SHAPE_BOTH, +} eKeyframeShapeDrawOpts; + +/* Handle type. */ +typedef enum eKeyframeHandleDrawOpts { + /* Don't draw */ + KEYFRAME_HANDLE_NONE = 0, + /* Various marks in order of increasing display priority. */ + KEYFRAME_HANDLE_AUTO_CLAMP, + KEYFRAME_HANDLE_AUTO, + KEYFRAME_HANDLE_VECTOR, + KEYFRAME_HANDLE_ALIGNED, + KEYFRAME_HANDLE_FREE, +} eKeyframeHandleDrawOpts; + +/* Extreme type. */ +typedef enum eKeyframeExtremeDrawOpts { + KEYFRAME_EXTREME_NONE = 0, + /* Minimum/maximum present. */ + KEYFRAME_EXTREME_MIN = (1 << 0), + KEYFRAME_EXTREME_MAX = (1 << 1), + /* Grouped keys have different states. */ + KEYFRAME_EXTREME_MIXED = (1 << 2), + /* Both neighbors are equal to this key. */ + KEYFRAME_EXTREME_FLAT = (1 << 3), +} eKeyframeExtremeDrawOpts; + +/* ******************************* Methods ****************************** */ + +/* Keydata Generation --------------- */ +/* F-Curve */ +void fcurve_to_keylist(struct AnimData *adt, + struct FCurve *fcu, + struct DLRBT_Tree *keys, + int saction_flag); +/* Action Group */ +void agroup_to_keylist(struct AnimData *adt, + struct bActionGroup *agrp, + struct DLRBT_Tree *keys, + int saction_flag); +/* Action */ +void action_to_keylist(struct AnimData *adt, + struct bAction *act, + struct DLRBT_Tree *keys, + int saction_flag); +/* Object */ +void ob_to_keylist(struct bDopeSheet *ads, + struct Object *ob, + struct DLRBT_Tree *keys, + int saction_flag); +/* Cache File */ +void cachefile_to_keylist(struct bDopeSheet *ads, + struct CacheFile *cache_file, + struct DLRBT_Tree *keys, + int saction_flag); +/* Scene */ +void scene_to_keylist(struct bDopeSheet *ads, + struct Scene *sce, + struct DLRBT_Tree *keys, + int saction_flag); +/* DopeSheet Summary */ +void summary_to_keylist(struct bAnimContext *ac, struct DLRBT_Tree *keys, int saction_flag); +/* Grease Pencil datablock summary */ +void gpencil_to_keylist(struct bDopeSheet *ads, + struct bGPdata *gpd, + struct DLRBT_Tree *keys, + const bool active); +/* Grease Pencil Layer */ +void gpl_to_keylist(struct bDopeSheet *ads, struct bGPDlayer *gpl, struct DLRBT_Tree *keys); +/* Mask */ +void mask_to_keylist(struct bDopeSheet *ads, struct MaskLayer *masklay, struct DLRBT_Tree *keys); + +/* ActKeyColumn API ---------------- */ +/* Comparator callback used for ActKeyColumns and cframe float-value pointer */ +short compare_ak_cfraPtr(void *node, void *data); + +/* Checks if ActKeyColumn has any block data */ +bool actkeyblock_is_valid(ActKeyColumn *ac); + +/* Checks if ActKeyColumn can be used as a block (i.e. drawn/used to detect "holds") */ +int actkeyblock_get_valid_hold(ActKeyColumn *ac); + +#ifdef __cplusplus +} +#endif diff --git a/source/blender/editors/interface/interface_icons.c b/source/blender/editors/interface/interface_icons.c index 43ac646f053..975c86f3e71 100644 --- a/source/blender/editors/interface/interface_icons.c +++ b/source/blender/editors/interface/interface_icons.c @@ -66,6 +66,7 @@ #include "ED_datafiles.h" #include "ED_keyframes_draw.h" +#include "ED_keyframes_keylist.h" #include "ED_render.h" #include "UI_interface.h" diff --git a/source/blender/editors/screen/screen_ops.c b/source/blender/editors/screen/screen_ops.c index ffeaf514642..3d0d856b1c5 100644 --- a/source/blender/editors/screen/screen_ops.c +++ b/source/blender/editors/screen/screen_ops.c @@ -71,7 +71,7 @@ #include "ED_armature.h" #include "ED_clip.h" #include "ED_image.h" -#include "ED_keyframes_draw.h" +#include "ED_keyframes_keylist.h" #include "ED_mesh.h" #include "ED_object.h" #include "ED_screen.h" diff --git a/source/blender/editors/space_action/action_select.c b/source/blender/editors/space_action/action_select.c index 0d5b197ae93..677053ff577 100644 --- a/source/blender/editors/space_action/action_select.c +++ b/source/blender/editors/space_action/action_select.c @@ -51,7 +51,7 @@ #include "ED_anim_api.h" #include "ED_gpencil.h" -#include "ED_keyframes_draw.h" +#include "ED_keyframes_keylist.h" #include "ED_keyframes_edit.h" #include "ED_markers.h" #include "ED_mask.h" diff --git a/source/blender/editors/space_nla/nla_draw.c b/source/blender/editors/space_nla/nla_draw.c index f9fb386095d..c96047da0c8 100644 --- a/source/blender/editors/space_nla/nla_draw.c +++ b/source/blender/editors/space_nla/nla_draw.c @@ -44,6 +44,7 @@ #include "ED_anim_api.h" #include "ED_keyframes_draw.h" +#include "ED_keyframes_keylist.h" #include "GPU_immediate.h" #include "GPU_immediate_util.h" |