diff options
author | Christoph Lendenfeld <ChrisLend> | 2020-11-23 17:11:51 +0300 |
---|---|---|
committer | Sybren A. Stüvel <sybren@blender.org> | 2020-11-23 17:26:54 +0300 |
commit | 1f09dcc121ab31fe08c76947820ed59ec8f76788 (patch) | |
tree | 6a39d6c48b9c8cfa16b565c75d018e2f941d58d6 /source/blender | |
parent | 152754a477d1f8846f1a4b75f47b98c3f1af431c (diff) |
Cleanup: Animation, split `graph_edit.c` into separate files
Split some of the code of `graph_edit.c` into:
* `graph_view.c`: preview range, view all, view selected etc.
* `graph_slider_ops.c`: the decimate modal operator code.
The latter file will be extended later with more slider-based operators.
Maniphest Tasks: T81785
Reviewed By: sybren
Differential Revision: https://developer.blender.org/D9312
Diffstat (limited to 'source/blender')
-rw-r--r-- | source/blender/editors/space_graph/CMakeLists.txt | 2 | ||||
-rw-r--r-- | source/blender/editors/space_graph/graph_edit.c | 973 | ||||
-rw-r--r-- | source/blender/editors/space_graph/graph_slider_ops.c | 526 | ||||
-rw-r--r-- | source/blender/editors/space_graph/graph_view.c | 536 |
4 files changed, 1070 insertions, 967 deletions
diff --git a/source/blender/editors/space_graph/CMakeLists.txt b/source/blender/editors/space_graph/CMakeLists.txt index fd5c5863608..414e5c87f5a 100644 --- a/source/blender/editors/space_graph/CMakeLists.txt +++ b/source/blender/editors/space_graph/CMakeLists.txt @@ -34,8 +34,10 @@ set(SRC graph_draw.c graph_edit.c graph_ops.c + graph_slider_ops.c graph_select.c graph_utils.c + graph_view.c space_graph.c graph_intern.h diff --git a/source/blender/editors/space_graph/graph_edit.c b/source/blender/editors/space_graph/graph_edit.c index bcd3adec1ab..1647fd4a6a6 100644 --- a/source/blender/editors/space_graph/graph_edit.c +++ b/source/blender/editors/space_graph/graph_edit.c @@ -61,7 +61,6 @@ #include "ED_keyframes_edit.h" #include "ED_keyframing.h" #include "ED_markers.h" -#include "ED_numinput.h" #include "ED_screen.h" #include "ED_transform.h" @@ -71,497 +70,7 @@ #include "graph_intern.h" /* ************************************************************************** */ -/* KEYFRAME-RANGE STUFF */ - -/* *************************** Calculate Range ************************** */ - -/* Get the min/max keyframes. */ -/* Note: it should return total boundbox, filter for selection only can be argument... */ -void get_graph_keyframe_extents(bAnimContext *ac, - float *xmin, - float *xmax, - float *ymin, - float *ymax, - const bool do_sel_only, - const bool include_handles) -{ - Scene *scene = ac->scene; - SpaceGraph *sipo = (SpaceGraph *)ac->sl; - - ListBase anim_data = {NULL, NULL}; - bAnimListElem *ale; - int filter; - - /* Get data to filter, from Dopesheet. */ - filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_CURVE_VISIBLE | ANIMFILTER_NODUPLIS); - if (sipo->flag & SIPO_SELCUVERTSONLY) { - filter |= ANIMFILTER_SEL; - } - - ANIM_animdata_filter(ac, &anim_data, filter, ac->data, ac->datatype); - - /* Set large values initial values that will be easy to override. */ - if (xmin) { - *xmin = 999999999.0f; - } - if (xmax) { - *xmax = -999999999.0f; - } - if (ymin) { - *ymin = 999999999.0f; - } - if (ymax) { - *ymax = -999999999.0f; - } - - /* Check if any channels to set range with. */ - if (anim_data.first) { - bool foundBounds = false; - - /* Go through channels, finding max extents. */ - for (ale = anim_data.first; ale; ale = ale->next) { - AnimData *adt = ANIM_nla_mapping_get(ac, ale); - FCurve *fcu = (FCurve *)ale->key_data; - float txmin, txmax, tymin, tymax; - float unitFac, offset; - - /* Get range. */ - if (BKE_fcurve_calc_bounds( - fcu, &txmin, &txmax, &tymin, &tymax, do_sel_only, include_handles)) { - short mapping_flag = ANIM_get_normalization_flags(ac); - - /* Apply NLA scaling. */ - if (adt) { - txmin = BKE_nla_tweakedit_remap(adt, txmin, NLATIME_CONVERT_MAP); - txmax = BKE_nla_tweakedit_remap(adt, txmax, NLATIME_CONVERT_MAP); - } - - /* Apply unit corrections. */ - unitFac = ANIM_unit_mapping_get_factor(ac->scene, ale->id, fcu, mapping_flag, &offset); - tymin += offset; - tymax += offset; - tymin *= unitFac; - tymax *= unitFac; - - /* Try to set cur using these values, if they're more extreme than previously set values. - */ - if ((xmin) && (txmin < *xmin)) { - *xmin = txmin; - } - if ((xmax) && (txmax > *xmax)) { - *xmax = txmax; - } - if ((ymin) && (tymin < *ymin)) { - *ymin = tymin; - } - if ((ymax) && (tymax > *ymax)) { - *ymax = tymax; - } - - foundBounds = true; - } - } - - /* Ensure that the extents are not too extreme that view implodes...*/ - if (foundBounds) { - if ((xmin && xmax) && (fabsf(*xmax - *xmin) < 0.001f)) { - *xmin -= 0.0005f; - *xmax += 0.0005f; - } - if ((ymin && ymax) && (fabsf(*ymax - *ymin) < 0.001f)) { - *ymax -= 0.0005f; - *ymax += 0.0005f; - } - } - else { - if (xmin) { - *xmin = (float)PSFRA; - } - if (xmax) { - *xmax = (float)PEFRA; - } - if (ymin) { - *ymin = -5; - } - if (ymax) { - *ymax = 5; - } - } - - /* Free memory. */ - ANIM_animdata_freelist(&anim_data); - } - else { - /* Set default range. */ - if (ac->scene) { - if (xmin) { - *xmin = (float)PSFRA; - } - if (xmax) { - *xmax = (float)PEFRA; - } - } - else { - if (xmin) { - *xmin = -5; - } - if (xmax) { - *xmax = 100; - } - } - - if (ymin) { - *ymin = -5; - } - if (ymax) { - *ymax = 5; - } - } -} - -/* ****************** Automatic Preview-Range Operator ****************** */ - -static int graphkeys_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_graph_keyframe_extents(&ac, &min, &max, NULL, NULL, false, false); - 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 GRAPH_OT_previewrange_set(wmOperatorType *ot) -{ - /* Identifiers */ - ot->name = "Auto-Set Preview Range"; - ot->idname = "GRAPH_OT_previewrange_set"; - ot->description = "Automatically set Preview Range based on range of keyframes"; - - /* API callbacks */ - ot->exec = graphkeys_previewrange_exec; - /* XXX: unchecked poll to get fsamples working too, but makes modifier damage trickier. */ - ot->poll = ED_operator_graphedit_active; - - /* Flags */ - ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; -} - -/* ****************** View-All Operator ****************** */ - -static int graphkeys_viewall(bContext *C, - const bool do_sel_only, - const bool include_handles, - const int smooth_viewtx) -{ - bAnimContext ac; - rctf cur_new; - - /* Get editor data. */ - if (ANIM_animdata_get_context(C, &ac) == 0) { - return OPERATOR_CANCELLED; - } - - /* Set the horizontal range, with an extra offset so that the extreme keys will be in view. */ - get_graph_keyframe_extents(&ac, - &cur_new.xmin, - &cur_new.xmax, - &cur_new.ymin, - &cur_new.ymax, - do_sel_only, - include_handles); - - /* Give some more space at the borders. */ - BLI_rctf_scale(&cur_new, 1.1f); - - /* Take regions into account, that could block the view. - * Marker region is supposed to be larger than the scroll-bar, so prioritize it.*/ - float pad_top = UI_TIME_SCRUB_MARGIN_Y; - float pad_bottom = BLI_listbase_is_empty(ED_context_get_markers(C)) ? V2D_SCROLL_HANDLE_HEIGHT : - UI_MARKER_MARGIN_Y; - BLI_rctf_pad_y(&cur_new, ac.region->winy, pad_bottom, pad_top); - - UI_view2d_smooth_view(C, ac.region, &cur_new, smooth_viewtx); - return OPERATOR_FINISHED; -} - -/* ......... */ - -static int graphkeys_viewall_exec(bContext *C, wmOperator *op) -{ - const bool include_handles = RNA_boolean_get(op->ptr, "include_handles"); - const int smooth_viewtx = WM_operator_smooth_viewtx_get(op); - - /* Whole range */ - return graphkeys_viewall(C, false, include_handles, smooth_viewtx); -} - -static int graphkeys_view_selected_exec(bContext *C, wmOperator *op) -{ - const bool include_handles = RNA_boolean_get(op->ptr, "include_handles"); - const int smooth_viewtx = WM_operator_smooth_viewtx_get(op); - - /* Only selected. */ - return graphkeys_viewall(C, true, include_handles, smooth_viewtx); -} - -/* ......... */ - -void GRAPH_OT_view_all(wmOperatorType *ot) -{ - /* Identifiers */ - ot->name = "Frame All"; - ot->idname = "GRAPH_OT_view_all"; - ot->description = "Reset viewable area to show full keyframe range"; - - /* API callbacks */ - ot->exec = graphkeys_viewall_exec; - /* XXX: Unchecked poll to get fsamples working too, but makes modifier damage trickier... */ - ot->poll = ED_operator_graphedit_active; - - /* Flags */ - ot->flag = 0; - - /* Props */ - ot->prop = RNA_def_boolean(ot->srna, - "include_handles", - true, - "Include Handles", - "Include handles of keyframes when calculating extents"); -} - -void GRAPH_OT_view_selected(wmOperatorType *ot) -{ - /* Identifiers */ - ot->name = "Frame Selected"; - ot->idname = "GRAPH_OT_view_selected"; - ot->description = "Reset viewable area to show selected keyframe range"; - - /* API callbacks */ - ot->exec = graphkeys_view_selected_exec; - /* XXX: Unchecked poll to get fsamples working too, but makes modifier damage trickier... */ - ot->poll = ED_operator_graphedit_active; - - /* Flags */ - ot->flag = 0; - - /* Props */ - ot->prop = RNA_def_boolean(ot->srna, - "include_handles", - true, - "Include Handles", - "Include handles of keyframes when calculating extents"); -} - -/* ********************** View Frame Operator ****************************** */ - -static int graphkeys_view_frame_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 GRAPH_OT_view_frame(wmOperatorType *ot) -{ - /* Identifiers */ - ot->name = "Go to Current Frame"; - ot->idname = "GRAPH_OT_view_frame"; - ot->description = "Move the view to the current frame"; - - /* API callbacks */ - ot->exec = graphkeys_view_frame_exec; - ot->poll = ED_operator_graphedit_active; - - /* Flags */ - ot->flag = 0; -} - -/* ******************** Create Ghost-Curves Operator *********************** */ -/* This operator samples the data of the selected F-Curves to F-Points, storing them - * as 'ghost curves' in the active Graph Editor. - */ - -/* Bake each F-Curve into a set of samples, and store as a ghost curve. */ -static void create_ghost_curves(bAnimContext *ac, int start, int end) -{ - SpaceGraph *sipo = (SpaceGraph *)ac->sl; - ListBase anim_data = {NULL, NULL}; - bAnimListElem *ale; - int filter; - - /* Free existing ghost curves. */ - BKE_fcurves_free(&sipo->runtime.ghost_curves); - - /* Sanity check. */ - if (start >= end) { - printf("Error: Frame range for Ghost F-Curve creation is inappropriate\n"); - return; - } - - /* Filter data. */ - filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_CURVE_VISIBLE | ANIMFILTER_SEL | - ANIMFILTER_NODUPLIS); - ANIM_animdata_filter(ac, &anim_data, filter, ac->data, ac->datatype); - - /* Loop through filtered data and add keys between selected keyframes on every frame . */ - for (ale = anim_data.first; ale; ale = ale->next) { - FCurve *fcu = (FCurve *)ale->key_data; - FCurve *gcu = BKE_fcurve_create(); - AnimData *adt = ANIM_nla_mapping_get(ac, ale); - ChannelDriver *driver = fcu->driver; - FPoint *fpt; - float unitFac, offset; - int cfra; - short mapping_flag = ANIM_get_normalization_flags(ac); - - /* Disable driver so that it don't muck up the sampling process. */ - fcu->driver = NULL; - - /* Calculate unit-mapping factor. */ - unitFac = ANIM_unit_mapping_get_factor(ac->scene, ale->id, fcu, mapping_flag, &offset); - - /* Create samples, but store them in a new curve - * - we cannot use fcurve_store_samples() as that will only overwrite the original curve. - */ - gcu->fpt = fpt = MEM_callocN(sizeof(FPoint) * (end - start + 1), "Ghost FPoint Samples"); - gcu->totvert = end - start + 1; - - /* Use the sampling callback at 1-frame intervals from start to end frames. */ - for (cfra = start; cfra <= end; cfra++, fpt++) { - float cfrae = BKE_nla_tweakedit_remap(adt, cfra, NLATIME_CONVERT_UNMAP); - - fpt->vec[0] = cfrae; - fpt->vec[1] = (fcurve_samplingcb_evalcurve(fcu, NULL, cfrae) + offset) * unitFac; - } - - /* Set color of ghost curve - * - make the color slightly darker. - */ - gcu->color[0] = fcu->color[0] - 0.07f; - gcu->color[1] = fcu->color[1] - 0.07f; - gcu->color[2] = fcu->color[2] - 0.07f; - - /* Store new ghost curve. */ - BLI_addtail(&sipo->runtime.ghost_curves, gcu); - - /* Restore driver. */ - fcu->driver = driver; - } - - /* Admin and redraws. */ - ANIM_animdata_freelist(&anim_data); -} - -/* ------------------- */ - -static int graphkeys_create_ghostcurves_exec(bContext *C, wmOperator *UNUSED(op)) -{ - bAnimContext ac; - View2D *v2d; - int start, end; - - /* Get editor data. */ - if (ANIM_animdata_get_context(C, &ac) == 0) { - return OPERATOR_CANCELLED; - } - - /* Ghost curves are snapshots of the visible portions of the curves, - * so set range to be the visible range. */ - v2d = &ac.region->v2d; - start = (int)v2d->cur.xmin; - end = (int)v2d->cur.xmax; - - /* Bake selected curves into a ghost curve. */ - create_ghost_curves(&ac, start, end); - - /* Update this editor only. */ - ED_area_tag_redraw(CTX_wm_area(C)); - - return OPERATOR_FINISHED; -} - -void GRAPH_OT_ghost_curves_create(wmOperatorType *ot) -{ - /* Identifiers */ - ot->name = "Create Ghost Curves"; - ot->idname = "GRAPH_OT_ghost_curves_create"; - ot->description = - "Create snapshot (Ghosts) of selected F-Curves as background aid for active Graph Editor"; - - /* API callbacks */ - ot->exec = graphkeys_create_ghostcurves_exec; - ot->poll = graphop_visible_keyframes_poll; - - /* Flags */ - ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; - - /* TODO: add props for start/end frames */ -} - -/* ******************** Clear Ghost-Curves Operator *********************** */ -/* This operator clears the 'ghost curves' for the active Graph Editor */ - -static int graphkeys_clear_ghostcurves_exec(bContext *C, wmOperator *UNUSED(op)) -{ - bAnimContext ac; - SpaceGraph *sipo; - - /* Get editor data. */ - if (ANIM_animdata_get_context(C, &ac) == 0) { - return OPERATOR_CANCELLED; - } - sipo = (SpaceGraph *)ac.sl; - - /* If no ghost curves, don't do anything. */ - if (BLI_listbase_is_empty(&sipo->runtime.ghost_curves)) { - return OPERATOR_CANCELLED; - } - /* Free ghost curves. */ - BKE_fcurves_free(&sipo->runtime.ghost_curves); - - /* Update this editor only. */ - ED_area_tag_redraw(CTX_wm_area(C)); - - return OPERATOR_FINISHED; -} - -void GRAPH_OT_ghost_curves_clear(wmOperatorType *ot) -{ - /* Identifiers */ - ot->name = "Clear Ghost Curves"; - ot->idname = "GRAPH_OT_ghost_curves_clear"; - ot->description = "Clear F-Curve snapshots (Ghosts) for active Graph Editor"; - - /* API callbacks */ - ot->exec = graphkeys_clear_ghostcurves_exec; - ot->poll = ED_operator_graphedit_active; - - /* Flags */ - ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; -} - -/* ************************************************************************** */ -/* GENERAL STUFF */ +/* INSERT DUPLICATE AND BAKE KEYFRAMES */ /* ******************** Insert Keyframes Operator ************************* */ @@ -1307,479 +816,6 @@ void GRAPH_OT_clean(wmOperatorType *ot) RNA_def_boolean(ot->srna, "channels", false, "Channels", ""); } -/* ******************** Decimate Keyframes Operator ************************* */ - -static void decimate_graph_keys(bAnimContext *ac, float remove_ratio, float error_sq_max) -{ - ListBase anim_data = {NULL, NULL}; - bAnimListElem *ale; - int filter; - - /* Filter data. */ - filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_CURVE_VISIBLE | ANIMFILTER_FOREDIT | - ANIMFILTER_SEL | ANIMFILTER_NODUPLIS); - ANIM_animdata_filter(ac, &anim_data, filter, ac->data, ac->datatype); - - /* Loop through filtered data and clean curves. */ - for (ale = anim_data.first; ale; ale = ale->next) { - if (!decimate_fcurve(ale, remove_ratio, error_sq_max)) { - /* The selection contains unsupported keyframe types! */ - WM_report(RPT_WARNING, "Decimate: Skipping non linear/bezier keyframes!"); - } - - ale->update |= ANIM_UPDATE_DEFAULT; - } - - ANIM_animdata_update(ac, &anim_data); - ANIM_animdata_freelist(&anim_data); -} - -/* ------------------- */ - -/* This data type is only used for modal operation. */ -typedef struct tDecimateGraphOp { - bAnimContext ac; - Scene *scene; - ScrArea *area; - ARegion *region; - - /** A 0-1 value for determining how much we should decimate. */ - PropertyRNA *percentage_prop; - - /** The original bezt curve data (used for restoring fcurves).*/ - ListBase bezt_arr_list; - - NumInput num; -} tDecimateGraphOp; - -typedef struct tBeztCopyData { - int tot_vert; - BezTriple *bezt; -} tBeztCopyData; - -typedef enum tDecimModes { - DECIM_RATIO = 1, - DECIM_ERROR, -} tDecimModes; - -/* Overwrite the current bezts arrays with the original data. */ -static void decimate_reset_bezts(tDecimateGraphOp *dgo) -{ - ListBase anim_data = {NULL, NULL}; - LinkData *link_bezt; - bAnimListElem *ale; - int filter; - - bAnimContext *ac = &dgo->ac; - - /* Filter data. */ - filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_CURVE_VISIBLE | ANIMFILTER_FOREDIT | - ANIMFILTER_SEL | ANIMFILTER_NODUPLIS); - ANIM_animdata_filter(ac, &anim_data, filter, ac->data, ac->datatype); - - /* Loop through filtered data and reset bezts. */ - for (ale = anim_data.first, link_bezt = dgo->bezt_arr_list.first; ale; ale = ale->next) { - FCurve *fcu = (FCurve *)ale->key_data; - - if (fcu->bezt == NULL) { - /* This curve is baked, skip it. */ - continue; - } - - tBeztCopyData *data = link_bezt->data; - - const int arr_size = sizeof(BezTriple) * data->tot_vert; - - MEM_freeN(fcu->bezt); - - fcu->bezt = MEM_mallocN(arr_size, __func__); - fcu->totvert = data->tot_vert; - - memcpy(fcu->bezt, data->bezt, arr_size); - - link_bezt = link_bezt->next; - } - - ANIM_animdata_freelist(&anim_data); -} - -static void decimate_exit(bContext *C, wmOperator *op) -{ - tDecimateGraphOp *dgo = op->customdata; - wmWindow *win = CTX_wm_window(C); - - /* If data exists, clear its data and exit. */ - if (dgo == NULL) { - return; - } - - ScrArea *area = dgo->area; - LinkData *link; - - for (link = dgo->bezt_arr_list.first; link != NULL; link = link->next) { - tBeztCopyData *copy = link->data; - MEM_freeN(copy->bezt); - MEM_freeN(link->data); - } - - BLI_freelistN(&dgo->bezt_arr_list); - MEM_freeN(dgo); - - /* Return to normal cursor and header status. */ - WM_cursor_modal_restore(win); - ED_area_status_text(area, NULL); - - /* Cleanup. */ - op->customdata = NULL; -} - -/* Draw a percentage indicator in header. */ -static void decimate_draw_status_header(wmOperator *op, tDecimateGraphOp *dgo) -{ - char status_str[UI_MAX_DRAW_STR]; - char mode_str[32]; - - strcpy(mode_str, TIP_("Decimate Keyframes")); - - if (hasNumInput(&dgo->num)) { - char str_offs[NUM_STR_REP_LEN]; - - outputNumInput(&dgo->num, str_offs, &dgo->scene->unit); - - BLI_snprintf(status_str, sizeof(status_str), "%s: %s", mode_str, str_offs); - } - else { - float percentage = RNA_property_float_get(op->ptr, dgo->percentage_prop); - BLI_snprintf( - status_str, sizeof(status_str), "%s: %d %%", mode_str, (int)(percentage * 100.0f)); - } - - ED_area_status_text(dgo->area, status_str); -} - -/* Calculate percentage based on position of mouse (we only use x-axis for now. - * Since this is more convenient for users to do), and store new percentage value. - */ -static void decimate_mouse_update_percentage(tDecimateGraphOp *dgo, - wmOperator *op, - const wmEvent *event) -{ - float percentage = (event->x - dgo->region->winrct.xmin) / ((float)dgo->region->winx); - RNA_property_float_set(op->ptr, dgo->percentage_prop, percentage); -} - -static int graphkeys_decimate_invoke(bContext *C, wmOperator *op, const wmEvent *event) -{ - tDecimateGraphOp *dgo; - - WM_cursor_modal_set(CTX_wm_window(C), WM_CURSOR_EW_SCROLL); - - /* Init slide-op data. */ - dgo = op->customdata = MEM_callocN(sizeof(tDecimateGraphOp), "tDecimateGraphOp"); - - /* Get editor data. */ - if (ANIM_animdata_get_context(C, &dgo->ac) == 0) { - decimate_exit(C, op); - return OPERATOR_CANCELLED; - } - - dgo->percentage_prop = RNA_struct_find_property(op->ptr, "remove_ratio"); - - dgo->scene = CTX_data_scene(C); - dgo->area = CTX_wm_area(C); - dgo->region = CTX_wm_region(C); - - /* Initialize percentage so that it will have the correct value before the first mouse move. */ - decimate_mouse_update_percentage(dgo, op, event); - - decimate_draw_status_header(op, dgo); - - /* Construct a list with the original bezt arrays so we can restore them during modal operation. - */ - { - ListBase anim_data = {NULL, NULL}; - bAnimContext *ac = &dgo->ac; - bAnimListElem *ale; - - int filter; - - /* Filter data. */ - filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_CURVE_VISIBLE | ANIMFILTER_FOREDIT | - ANIMFILTER_SEL | ANIMFILTER_NODUPLIS); - ANIM_animdata_filter(ac, &anim_data, filter, ac->data, ac->datatype); - - /* Loop through filtered data and copy the curves. */ - for (ale = anim_data.first; ale; ale = ale->next) { - FCurve *fcu = (FCurve *)ale->key_data; - - if (fcu->bezt == NULL) { - /* This curve is baked, skip it. */ - continue; - } - - const int arr_size = sizeof(BezTriple) * fcu->totvert; - - tBeztCopyData *copy = MEM_mallocN(sizeof(tBeztCopyData), "bezts_copy"); - BezTriple *bezts_copy = MEM_mallocN(arr_size, "bezts_copy_array"); - - copy->tot_vert = fcu->totvert; - memcpy(bezts_copy, fcu->bezt, arr_size); - - copy->bezt = bezts_copy; - - LinkData *link = NULL; - - link = MEM_callocN(sizeof(LinkData), "Bezt Link"); - link->data = copy; - - BLI_addtail(&dgo->bezt_arr_list, link); - } - - ANIM_animdata_freelist(&anim_data); - } - - if (dgo->bezt_arr_list.first == NULL) { - WM_report(RPT_WARNING, - "Fcurve Decimate: Can't decimate baked channels. Unbake them and try again."); - decimate_exit(C, op); - return OPERATOR_CANCELLED; - } - - WM_event_add_modal_handler(C, op); - return OPERATOR_RUNNING_MODAL; -} - -static void graphkeys_decimate_modal_update(bContext *C, wmOperator *op) -{ - /* Perform decimate updates - in response to some user action - * (e.g. pressing a key or moving the mouse). */ - tDecimateGraphOp *dgo = op->customdata; - - decimate_draw_status_header(op, dgo); - - /* Reset keyframe data (so we get back to the original state). */ - decimate_reset_bezts(dgo); - - /* Apply... */ - float remove_ratio = RNA_property_float_get(op->ptr, dgo->percentage_prop); - /* We don't want to limit the decimation to a certain error margin. */ - const float error_sq_max = FLT_MAX; - decimate_graph_keys(&dgo->ac, remove_ratio, error_sq_max); - WM_event_add_notifier(C, NC_ANIMATION | ND_KEYFRAME | NA_EDITED, NULL); -} - -static int graphkeys_decimate_modal(bContext *C, wmOperator *op, const wmEvent *event) -{ - /* This assumes that we are in "DECIM_RATIO" mode. This is because the error margin is very hard - * and finicky to control with this modal mouse grab method. Therefore, it is expected that the - * error margin mode is not adjusted by the modal operator but instead tweaked via the redo - * panel.*/ - tDecimateGraphOp *dgo = op->customdata; - - const bool has_numinput = hasNumInput(&dgo->num); - - switch (event->type) { - case LEFTMOUSE: /* Confirm */ - case EVT_RETKEY: - case EVT_PADENTER: { - if (event->val == KM_PRESS) { - decimate_exit(C, op); - - return OPERATOR_FINISHED; - } - break; - } - - case EVT_ESCKEY: /* Cancel */ - case RIGHTMOUSE: { - if (event->val == KM_PRESS) { - decimate_reset_bezts(dgo); - - WM_event_add_notifier(C, NC_ANIMATION | ND_KEYFRAME | NA_EDITED, NULL); - - decimate_exit(C, op); - - return OPERATOR_CANCELLED; - } - break; - } - - /* Percentage Change... */ - case MOUSEMOVE: /* Calculate new position. */ - { - if (has_numinput == false) { - /* Update percentage based on position of mouse. */ - decimate_mouse_update_percentage(dgo, op, event); - - /* Update pose to reflect the new values. */ - graphkeys_decimate_modal_update(C, op); - } - break; - } - default: { - if ((event->val == KM_PRESS) && handleNumInput(C, &dgo->num, event)) { - float value; - float percentage = RNA_property_float_get(op->ptr, dgo->percentage_prop); - - /* Grab percentage from numeric input, and store this new value for redo - * NOTE: users see ints, while internally we use a 0-1 float. - */ - value = percentage * 100.0f; - applyNumInput(&dgo->num, &value); - - percentage = value / 100.0f; - RNA_property_float_set(op->ptr, dgo->percentage_prop, percentage); - - /* Update decimate output to reflect the new values. */ - graphkeys_decimate_modal_update(C, op); - break; - } - - /* Unhandled event - maybe it was some view manip? */ - /* Allow to pass through. */ - return OPERATOR_RUNNING_MODAL | OPERATOR_PASS_THROUGH; - } - } - - return OPERATOR_RUNNING_MODAL; -} - -static int graphkeys_decimate_exec(bContext *C, wmOperator *op) -{ - bAnimContext ac; - - /* Get editor data. */ - if (ANIM_animdata_get_context(C, &ac) == 0) { - return OPERATOR_CANCELLED; - } - - tDecimModes mode = RNA_enum_get(op->ptr, "mode"); - /* We want to be able to work on all available keyframes. */ - float remove_ratio = 1.0f; - /* We don't want to limit the decimation to a certain error margin. */ - float error_sq_max = FLT_MAX; - - switch (mode) { - case DECIM_RATIO: - remove_ratio = RNA_float_get(op->ptr, "remove_ratio"); - break; - case DECIM_ERROR: - error_sq_max = RNA_float_get(op->ptr, "remove_error_margin"); - /* The decimate algorithm expects the error to be squared. */ - error_sq_max *= error_sq_max; - - break; - } - - if (remove_ratio == 0.0f || error_sq_max == 0.0f) { - /* Nothing to remove. */ - return OPERATOR_FINISHED; - } - - decimate_graph_keys(&ac, remove_ratio, error_sq_max); - - /* Set notifier that keyframes have changed. */ - WM_event_add_notifier(C, NC_ANIMATION | ND_KEYFRAME | NA_EDITED, NULL); - - return OPERATOR_FINISHED; -} - -static bool graphkeys_decimate_poll_property(const bContext *UNUSED(C), - wmOperator *op, - const PropertyRNA *prop) -{ - const char *prop_id = RNA_property_identifier(prop); - - if (STRPREFIX(prop_id, "remove")) { - int mode = RNA_enum_get(op->ptr, "mode"); - - if (STREQ(prop_id, "remove_ratio") && mode != DECIM_RATIO) { - return false; - } - if (STREQ(prop_id, "remove_error_margin") && mode != DECIM_ERROR) { - return false; - } - } - - return true; -} - -static char *graphkeys_decimate_desc(bContext *UNUSED(C), - wmOperatorType *UNUSED(op), - PointerRNA *ptr) -{ - - if (RNA_enum_get(ptr, "mode") == DECIM_ERROR) { - return BLI_strdup( - "Decimate F-Curves by specifying how much it can deviate from the original curve"); - } - - /* Use default description. */ - return NULL; -} - -static const EnumPropertyItem decimate_mode_items[] = { - {DECIM_RATIO, - "RATIO", - 0, - "Ratio", - "Use a percentage to specify how many keyframes you want to remove"}, - {DECIM_ERROR, - "ERROR", - 0, - "Error Margin", - "Use an error margin to specify how much the curve is allowed to deviate from the original " - "path"}, - {0, NULL, 0, NULL, NULL}, -}; - -void GRAPH_OT_decimate(wmOperatorType *ot) -{ - /* Identifiers */ - ot->name = "Decimate Keyframes"; - ot->idname = "GRAPH_OT_decimate"; - ot->description = - "Decimate F-Curves by removing keyframes that influence the curve shape the least"; - - /* API callbacks */ - ot->poll_property = graphkeys_decimate_poll_property; - ot->get_description = graphkeys_decimate_desc; - ot->invoke = graphkeys_decimate_invoke; - ot->modal = graphkeys_decimate_modal; - ot->exec = graphkeys_decimate_exec; - ot->poll = graphop_editable_keyframes_poll; - - /* Flags */ - ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; - - /* Properties */ - RNA_def_enum(ot->srna, - "mode", - decimate_mode_items, - DECIM_RATIO, - "Mode", - "Which mode to use for decimation"); - - RNA_def_float_percentage(ot->srna, - "remove_ratio", - 1.0f / 3.0f, - 0.0f, - 1.0f, - "Remove", - "The percentage of keyframes to remove", - 0.0f, - 1.0f); - RNA_def_float(ot->srna, - "remove_error_margin", - 0.0f, - 0.0f, - FLT_MAX, - "Max Error Margin", - "How much the new decimated curve is allowed to deviate from the original", - 0.0f, - 10.0f); -} - /* ******************** Bake F-Curve Operator *********************** */ /* This operator bakes the data of the selected F-Curves to F-Points */ @@ -2232,7 +1268,7 @@ void GRAPH_OT_sample(wmOperatorType *ot) } /* ************************************************************************** */ -/* SETTINGS STUFF */ +/* EXTRAPOLATION MODE AND KEYFRAME HANDLE SETTINGS */ /* ******************** Set Extrapolation-Type Operator *********************** */ @@ -2589,7 +1625,7 @@ void GRAPH_OT_handle_type(wmOperatorType *ot) } /* ************************************************************************** */ -/* TRANSFORM STUFF */ +/* EULER FILTER */ /* ***************** 'Euler Filter' Operator **************************** */ /* Euler filter tools (as seen in Maya), are necessary for working with 'baked' @@ -2919,6 +1955,9 @@ void GRAPH_OT_euler_filter(wmOperatorType *ot) ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; } +/* ************************************************************************** */ +/* SNAPPING */ + /* ***************** Jump to Selected Frames Operator *********************** */ static bool graphkeys_framejump_poll(bContext *C) diff --git a/source/blender/editors/space_graph/graph_slider_ops.c b/source/blender/editors/space_graph/graph_slider_ops.c new file mode 100644 index 00000000000..4cda6b34a01 --- /dev/null +++ b/source/blender/editors/space_graph/graph_slider_ops.c @@ -0,0 +1,526 @@ +/* + * 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) 2020 Blender Foundation. + * All rights reserved. + */ + +#include <float.h> +#include <string.h> + +#include "MEM_guardedalloc.h" + +#include "BLI_listbase.h" +#include "BLI_string.h" + +#include "DNA_anim_types.h" +#include "DNA_scene_types.h" + +#include "RNA_access.h" +#include "RNA_define.h" + +#include "BLT_translation.h" + +#include "BKE_context.h" + +#include "UI_interface.h" + +#include "ED_anim_api.h" +#include "ED_keyframes_edit.h" +#include "ED_numinput.h" +#include "ED_screen.h" + +#include "WM_api.h" +#include "WM_types.h" + +#include "graph_intern.h" + +/* ******************** GRAPH SLIDER OPERATORS ************************* */ +/* This file contains a collection of operators to modify keyframes in the graph editor. All + * operators are modal and use a slider that allows the user to define a percentage to modify the + * operator.*/ + +/* ******************** Decimate Keyframes Operator ************************* */ + +static void decimate_graph_keys(bAnimContext *ac, float remove_ratio, float error_sq_max) +{ + ListBase anim_data = {NULL, NULL}; + bAnimListElem *ale; + int filter; + + /* Filter data. */ + filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_CURVE_VISIBLE | ANIMFILTER_FOREDIT | + ANIMFILTER_SEL | ANIMFILTER_NODUPLIS); + ANIM_animdata_filter(ac, &anim_data, filter, ac->data, ac->datatype); + + /* Loop through filtered data and clean curves. */ + for (ale = anim_data.first; ale; ale = ale->next) { + if (!decimate_fcurve(ale, remove_ratio, error_sq_max)) { + /* The selection contains unsupported keyframe types! */ + WM_report(RPT_WARNING, "Decimate: Skipping non linear/bezier keyframes!"); + } + + ale->update |= ANIM_UPDATE_DEFAULT; + } + + ANIM_animdata_update(ac, &anim_data); + ANIM_animdata_freelist(&anim_data); +} + +/* ------------------- */ + +/* This data type is only used for modal operation. */ +typedef struct tDecimateGraphOp { + bAnimContext ac; + Scene *scene; + ScrArea *area; + ARegion *region; + + /** A 0-1 value for determining how much we should decimate. */ + PropertyRNA *percentage_prop; + + /** The original bezt curve data (used for restoring fcurves).*/ + ListBase bezt_arr_list; + + NumInput num; +} tDecimateGraphOp; + +typedef struct tBeztCopyData { + int tot_vert; + BezTriple *bezt; +} tBeztCopyData; + +typedef enum tDecimModes { + DECIM_RATIO = 1, + DECIM_ERROR, +} tDecimModes; + +/* Overwrite the current bezts arrays with the original data. */ +static void decimate_reset_bezts(tDecimateGraphOp *dgo) +{ + ListBase anim_data = {NULL, NULL}; + LinkData *link_bezt; + bAnimListElem *ale; + int filter; + + bAnimContext *ac = &dgo->ac; + + /* Filter data. */ + filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_CURVE_VISIBLE | ANIMFILTER_FOREDIT | + ANIMFILTER_SEL | ANIMFILTER_NODUPLIS); + ANIM_animdata_filter(ac, &anim_data, filter, ac->data, ac->datatype); + + /* Loop through filtered data and reset bezts. */ + for (ale = anim_data.first, link_bezt = dgo->bezt_arr_list.first; ale; ale = ale->next) { + FCurve *fcu = (FCurve *)ale->key_data; + + if (fcu->bezt == NULL) { + /* This curve is baked, skip it. */ + continue; + } + + tBeztCopyData *data = link_bezt->data; + + const int arr_size = sizeof(BezTriple) * data->tot_vert; + + MEM_freeN(fcu->bezt); + + fcu->bezt = MEM_mallocN(arr_size, __func__); + fcu->totvert = data->tot_vert; + + memcpy(fcu->bezt, data->bezt, arr_size); + + link_bezt = link_bezt->next; + } + + ANIM_animdata_freelist(&anim_data); +} + +static void decimate_exit(bContext *C, wmOperator *op) +{ + tDecimateGraphOp *dgo = op->customdata; + wmWindow *win = CTX_wm_window(C); + + /* If data exists, clear its data and exit. */ + if (dgo == NULL) { + return; + } + + ScrArea *area = dgo->area; + LinkData *link; + + for (link = dgo->bezt_arr_list.first; link != NULL; link = link->next) { + tBeztCopyData *copy = link->data; + MEM_freeN(copy->bezt); + MEM_freeN(link->data); + } + + BLI_freelistN(&dgo->bezt_arr_list); + MEM_freeN(dgo); + + /* Return to normal cursor and header status. */ + WM_cursor_modal_restore(win); + ED_area_status_text(area, NULL); + + /* Cleanup. */ + op->customdata = NULL; +} + +/* Draw a percentage indicator in header. */ +static void decimate_draw_status_header(wmOperator *op, tDecimateGraphOp *dgo) +{ + char status_str[UI_MAX_DRAW_STR]; + char mode_str[32]; + + strcpy(mode_str, TIP_("Decimate Keyframes")); + + if (hasNumInput(&dgo->num)) { + char str_offs[NUM_STR_REP_LEN]; + + outputNumInput(&dgo->num, str_offs, &dgo->scene->unit); + + BLI_snprintf(status_str, sizeof(status_str), "%s: %s", mode_str, str_offs); + } + else { + float percentage = RNA_property_float_get(op->ptr, dgo->percentage_prop); + BLI_snprintf( + status_str, sizeof(status_str), "%s: %d %%", mode_str, (int)(percentage * 100.0f)); + } + + ED_area_status_text(dgo->area, status_str); +} + +/* Calculate percentage based on position of mouse (we only use x-axis for now. + * Since this is more convenient for users to do), and store new percentage value. + */ +static void decimate_mouse_update_percentage(tDecimateGraphOp *dgo, + wmOperator *op, + const wmEvent *event) +{ + float percentage = (event->x - dgo->region->winrct.xmin) / ((float)dgo->region->winx); + RNA_property_float_set(op->ptr, dgo->percentage_prop, percentage); +} + +static int graphkeys_decimate_invoke(bContext *C, wmOperator *op, const wmEvent *event) +{ + tDecimateGraphOp *dgo; + + WM_cursor_modal_set(CTX_wm_window(C), WM_CURSOR_EW_SCROLL); + + /* Init slide-op data. */ + dgo = op->customdata = MEM_callocN(sizeof(tDecimateGraphOp), "tDecimateGraphOp"); + + /* Get editor data. */ + if (ANIM_animdata_get_context(C, &dgo->ac) == 0) { + decimate_exit(C, op); + return OPERATOR_CANCELLED; + } + + dgo->percentage_prop = RNA_struct_find_property(op->ptr, "remove_ratio"); + + dgo->scene = CTX_data_scene(C); + dgo->area = CTX_wm_area(C); + dgo->region = CTX_wm_region(C); + + /* Initialize percentage so that it will have the correct value before the first mouse move. */ + decimate_mouse_update_percentage(dgo, op, event); + + decimate_draw_status_header(op, dgo); + + /* Construct a list with the original bezt arrays so we can restore them during modal operation. + */ + { + ListBase anim_data = {NULL, NULL}; + bAnimContext *ac = &dgo->ac; + bAnimListElem *ale; + + int filter; + + /* Filter data. */ + filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_CURVE_VISIBLE | ANIMFILTER_FOREDIT | + ANIMFILTER_SEL | ANIMFILTER_NODUPLIS); + ANIM_animdata_filter(ac, &anim_data, filter, ac->data, ac->datatype); + + /* Loop through filtered data and copy the curves. */ + for (ale = anim_data.first; ale; ale = ale->next) { + FCurve *fcu = (FCurve *)ale->key_data; + + if (fcu->bezt == NULL) { + /* This curve is baked, skip it. */ + continue; + } + + const int arr_size = sizeof(BezTriple) * fcu->totvert; + + tBeztCopyData *copy = MEM_mallocN(sizeof(tBeztCopyData), "bezts_copy"); + BezTriple *bezts_copy = MEM_mallocN(arr_size, "bezts_copy_array"); + + copy->tot_vert = fcu->totvert; + memcpy(bezts_copy, fcu->bezt, arr_size); + + copy->bezt = bezts_copy; + + LinkData *link = NULL; + + link = MEM_callocN(sizeof(LinkData), "Bezt Link"); + link->data = copy; + + BLI_addtail(&dgo->bezt_arr_list, link); + } + + ANIM_animdata_freelist(&anim_data); + } + + if (dgo->bezt_arr_list.first == NULL) { + WM_report(RPT_WARNING, + "Fcurve Decimate: Can't decimate baked channels. Unbake them and try again."); + decimate_exit(C, op); + return OPERATOR_CANCELLED; + } + + WM_event_add_modal_handler(C, op); + return OPERATOR_RUNNING_MODAL; +} + +static void graphkeys_decimate_modal_update(bContext *C, wmOperator *op) +{ + /* Perform decimate updates - in response to some user action + * (e.g. pressing a key or moving the mouse). */ + tDecimateGraphOp *dgo = op->customdata; + + decimate_draw_status_header(op, dgo); + + /* Reset keyframe data (so we get back to the original state). */ + decimate_reset_bezts(dgo); + + /* Apply... */ + float remove_ratio = RNA_property_float_get(op->ptr, dgo->percentage_prop); + /* We don't want to limit the decimation to a certain error margin. */ + const float error_sq_max = FLT_MAX; + decimate_graph_keys(&dgo->ac, remove_ratio, error_sq_max); + WM_event_add_notifier(C, NC_ANIMATION | ND_KEYFRAME | NA_EDITED, NULL); +} + +static int graphkeys_decimate_modal(bContext *C, wmOperator *op, const wmEvent *event) +{ + /* This assumes that we are in "DECIM_RATIO" mode. This is because the error margin is very hard + * and finicky to control with this modal mouse grab method. Therefore, it is expected that the + * error margin mode is not adjusted by the modal operator but instead tweaked via the redo + * panel.*/ + tDecimateGraphOp *dgo = op->customdata; + + const bool has_numinput = hasNumInput(&dgo->num); + + switch (event->type) { + case LEFTMOUSE: /* Confirm */ + case EVT_RETKEY: + case EVT_PADENTER: { + if (event->val == KM_PRESS) { + decimate_exit(C, op); + + return OPERATOR_FINISHED; + } + break; + } + + case EVT_ESCKEY: /* Cancel */ + case RIGHTMOUSE: { + if (event->val == KM_PRESS) { + decimate_reset_bezts(dgo); + + WM_event_add_notifier(C, NC_ANIMATION | ND_KEYFRAME | NA_EDITED, NULL); + + decimate_exit(C, op); + + return OPERATOR_CANCELLED; + } + break; + } + + /* Percentage Change... */ + case MOUSEMOVE: /* Calculate new position. */ + { + if (has_numinput == false) { + /* Update percentage based on position of mouse. */ + decimate_mouse_update_percentage(dgo, op, event); + + /* Update pose to reflect the new values. */ + graphkeys_decimate_modal_update(C, op); + } + break; + } + default: { + if ((event->val == KM_PRESS) && handleNumInput(C, &dgo->num, event)) { + float value; + float percentage = RNA_property_float_get(op->ptr, dgo->percentage_prop); + + /* Grab percentage from numeric input, and store this new value for redo + * NOTE: users see ints, while internally we use a 0-1 float. + */ + value = percentage * 100.0f; + applyNumInput(&dgo->num, &value); + + percentage = value / 100.0f; + RNA_property_float_set(op->ptr, dgo->percentage_prop, percentage); + + /* Update decimate output to reflect the new values. */ + graphkeys_decimate_modal_update(C, op); + break; + } + + /* Unhandled event - maybe it was some view manip? */ + /* Allow to pass through. */ + return OPERATOR_RUNNING_MODAL | OPERATOR_PASS_THROUGH; + } + } + + return OPERATOR_RUNNING_MODAL; +} + +static int graphkeys_decimate_exec(bContext *C, wmOperator *op) +{ + bAnimContext ac; + + /* Get editor data. */ + if (ANIM_animdata_get_context(C, &ac) == 0) { + return OPERATOR_CANCELLED; + } + + tDecimModes mode = RNA_enum_get(op->ptr, "mode"); + /* We want to be able to work on all available keyframes. */ + float remove_ratio = 1.0f; + /* We don't want to limit the decimation to a certain error margin. */ + float error_sq_max = FLT_MAX; + + switch (mode) { + case DECIM_RATIO: + remove_ratio = RNA_float_get(op->ptr, "remove_ratio"); + break; + case DECIM_ERROR: + error_sq_max = RNA_float_get(op->ptr, "remove_error_margin"); + /* The decimate algorithm expects the error to be squared. */ + error_sq_max *= error_sq_max; + + break; + } + + if (remove_ratio == 0.0f || error_sq_max == 0.0f) { + /* Nothing to remove. */ + return OPERATOR_FINISHED; + } + + decimate_graph_keys(&ac, remove_ratio, error_sq_max); + + /* Set notifier that keyframes have changed. */ + WM_event_add_notifier(C, NC_ANIMATION | ND_KEYFRAME | NA_EDITED, NULL); + + return OPERATOR_FINISHED; +} + +static bool graphkeys_decimate_poll_property(const bContext *UNUSED(C), + wmOperator *op, + const PropertyRNA *prop) +{ + const char *prop_id = RNA_property_identifier(prop); + + if (STRPREFIX(prop_id, "remove")) { + int mode = RNA_enum_get(op->ptr, "mode"); + + if (STREQ(prop_id, "remove_ratio") && mode != DECIM_RATIO) { + return false; + } + if (STREQ(prop_id, "remove_error_margin") && mode != DECIM_ERROR) { + return false; + } + } + + return true; +} + +static char *graphkeys_decimate_desc(bContext *UNUSED(C), + wmOperatorType *UNUSED(op), + PointerRNA *ptr) +{ + + if (RNA_enum_get(ptr, "mode") == DECIM_ERROR) { + return BLI_strdup( + "Decimate F-Curves by specifying how much it can deviate from the original curve"); + } + + /* Use default description. */ + return NULL; +} + +static const EnumPropertyItem decimate_mode_items[] = { + {DECIM_RATIO, + "RATIO", + 0, + "Ratio", + "Use a percentage to specify how many keyframes you want to remove"}, + {DECIM_ERROR, + "ERROR", + 0, + "Error Margin", + "Use an error margin to specify how much the curve is allowed to deviate from the original " + "path"}, + {0, NULL, 0, NULL, NULL}, +}; + +void GRAPH_OT_decimate(wmOperatorType *ot) +{ + /* Identifiers */ + ot->name = "Decimate Keyframes"; + ot->idname = "GRAPH_OT_decimate"; + ot->description = + "Decimate F-Curves by removing keyframes that influence the curve shape the least"; + + /* API callbacks */ + ot->poll_property = graphkeys_decimate_poll_property; + ot->get_description = graphkeys_decimate_desc; + ot->invoke = graphkeys_decimate_invoke; + ot->modal = graphkeys_decimate_modal; + ot->exec = graphkeys_decimate_exec; + ot->poll = graphop_editable_keyframes_poll; + + /* Flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + /* Properties */ + RNA_def_enum(ot->srna, + "mode", + decimate_mode_items, + DECIM_RATIO, + "Mode", + "Which mode to use for decimation"); + + RNA_def_float_percentage(ot->srna, + "remove_ratio", + 1.0f / 3.0f, + 0.0f, + 1.0f, + "Remove", + "The percentage of keyframes to remove", + 0.0f, + 1.0f); + RNA_def_float(ot->srna, + "remove_error_margin", + 0.0f, + 0.0f, + FLT_MAX, + "Max Error Margin", + "How much the new decimated curve is allowed to deviate from the original", + 0.0f, + 10.0f); +} diff --git a/source/blender/editors/space_graph/graph_view.c b/source/blender/editors/space_graph/graph_view.c new file mode 100644 index 00000000000..0b9ba3762a3 --- /dev/null +++ b/source/blender/editors/space_graph/graph_view.c @@ -0,0 +1,536 @@ +/* + * 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) 2020 Blender Foundation. + * All rights reserved. + */ + +#include <math.h> + +#include "MEM_guardedalloc.h" + +#include "BLI_listbase.h" +#include "BLI_math.h" +#include "BLI_rect.h" + +#include "DNA_anim_types.h" +#include "DNA_scene_types.h" +#include "DNA_space_types.h" + +#include "RNA_access.h" +#include "RNA_define.h" + +#include "BKE_context.h" +#include "BKE_fcurve.h" +#include "BKE_nla.h" + +#include "UI_interface.h" +#include "UI_view2d.h" + +#include "ED_anim_api.h" +#include "ED_markers.h" +#include "ED_screen.h" + +#include "WM_api.h" +#include "WM_types.h" + +#include "graph_intern.h" + +/* *************************** Calculate Range ************************** */ + +/* Get the min/max keyframes. */ +/* Note: it should return total boundbox, filter for selection only can be argument... */ +void get_graph_keyframe_extents(bAnimContext *ac, + float *xmin, + float *xmax, + float *ymin, + float *ymax, + const bool do_sel_only, + const bool include_handles) +{ + Scene *scene = ac->scene; + SpaceGraph *sipo = (SpaceGraph *)ac->sl; + + ListBase anim_data = {NULL, NULL}; + bAnimListElem *ale; + int filter; + + /* Get data to filter, from Dopesheet. */ + filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_CURVE_VISIBLE | ANIMFILTER_NODUPLIS); + if (sipo->flag & SIPO_SELCUVERTSONLY) { + filter |= ANIMFILTER_SEL; + } + + ANIM_animdata_filter(ac, &anim_data, filter, ac->data, ac->datatype); + + /* Set large values initial values that will be easy to override. */ + if (xmin) { + *xmin = 999999999.0f; + } + if (xmax) { + *xmax = -999999999.0f; + } + if (ymin) { + *ymin = 999999999.0f; + } + if (ymax) { + *ymax = -999999999.0f; + } + + /* Check if any channels to set range with. */ + if (anim_data.first) { + bool foundBounds = false; + + /* Go through channels, finding max extents. */ + for (ale = anim_data.first; ale; ale = ale->next) { + AnimData *adt = ANIM_nla_mapping_get(ac, ale); + FCurve *fcu = (FCurve *)ale->key_data; + float txmin, txmax, tymin, tymax; + float unitFac, offset; + + /* Get range. */ + if (BKE_fcurve_calc_bounds( + fcu, &txmin, &txmax, &tymin, &tymax, do_sel_only, include_handles)) { + short mapping_flag = ANIM_get_normalization_flags(ac); + + /* Apply NLA scaling. */ + if (adt) { + txmin = BKE_nla_tweakedit_remap(adt, txmin, NLATIME_CONVERT_MAP); + txmax = BKE_nla_tweakedit_remap(adt, txmax, NLATIME_CONVERT_MAP); + } + + /* Apply unit corrections. */ + unitFac = ANIM_unit_mapping_get_factor(ac->scene, ale->id, fcu, mapping_flag, &offset); + tymin += offset; + tymax += offset; + tymin *= unitFac; + tymax *= unitFac; + + /* Try to set cur using these values, if they're more extreme than previously set values. + */ + if ((xmin) && (txmin < *xmin)) { + *xmin = txmin; + } + if ((xmax) && (txmax > *xmax)) { + *xmax = txmax; + } + if ((ymin) && (tymin < *ymin)) { + *ymin = tymin; + } + if ((ymax) && (tymax > *ymax)) { + *ymax = tymax; + } + + foundBounds = true; + } + } + + /* Ensure that the extents are not too extreme that view implodes...*/ + if (foundBounds) { + if ((xmin && xmax) && (fabsf(*xmax - *xmin) < 0.001f)) { + *xmin -= 0.0005f; + *xmax += 0.0005f; + } + if ((ymin && ymax) && (fabsf(*ymax - *ymin) < 0.001f)) { + *ymax -= 0.0005f; + *ymax += 0.0005f; + } + } + else { + if (xmin) { + *xmin = (float)PSFRA; + } + if (xmax) { + *xmax = (float)PEFRA; + } + if (ymin) { + *ymin = -5; + } + if (ymax) { + *ymax = 5; + } + } + + /* Free memory. */ + ANIM_animdata_freelist(&anim_data); + } + else { + /* Set default range. */ + if (ac->scene) { + if (xmin) { + *xmin = (float)PSFRA; + } + if (xmax) { + *xmax = (float)PEFRA; + } + } + else { + if (xmin) { + *xmin = -5; + } + if (xmax) { + *xmax = 100; + } + } + + if (ymin) { + *ymin = -5; + } + if (ymax) { + *ymax = 5; + } + } +} + +/* ****************** Automatic Preview-Range Operator ****************** */ + +static int graphkeys_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_graph_keyframe_extents(&ac, &min, &max, NULL, NULL, false, false); + 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 GRAPH_OT_previewrange_set(wmOperatorType *ot) +{ + /* Identifiers */ + ot->name = "Auto-Set Preview Range"; + ot->idname = "GRAPH_OT_previewrange_set"; + ot->description = "Automatically set Preview Range based on range of keyframes"; + + /* API callbacks */ + ot->exec = graphkeys_previewrange_exec; + /* XXX: unchecked poll to get fsamples working too, but makes modifier damage trickier. */ + ot->poll = ED_operator_graphedit_active; + + /* Flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; +} + +/* ****************** View-All Operator ****************** */ + +static int graphkeys_viewall(bContext *C, + const bool do_sel_only, + const bool include_handles, + const int smooth_viewtx) +{ + bAnimContext ac; + rctf cur_new; + + /* Get editor data. */ + if (ANIM_animdata_get_context(C, &ac) == 0) { + return OPERATOR_CANCELLED; + } + + /* Set the horizontal range, with an extra offset so that the extreme keys will be in view. */ + get_graph_keyframe_extents(&ac, + &cur_new.xmin, + &cur_new.xmax, + &cur_new.ymin, + &cur_new.ymax, + do_sel_only, + include_handles); + + /* Give some more space at the borders. */ + BLI_rctf_scale(&cur_new, 1.1f); + + /* Take regions into account, that could block the view. + * Marker region is supposed to be larger than the scroll-bar, so prioritize it.*/ + float pad_top = UI_TIME_SCRUB_MARGIN_Y; + float pad_bottom = BLI_listbase_is_empty(ED_context_get_markers(C)) ? V2D_SCROLL_HANDLE_HEIGHT : + UI_MARKER_MARGIN_Y; + BLI_rctf_pad_y(&cur_new, ac.region->winy, pad_bottom, pad_top); + + UI_view2d_smooth_view(C, ac.region, &cur_new, smooth_viewtx); + return OPERATOR_FINISHED; +} + +/* ......... */ + +static int graphkeys_viewall_exec(bContext *C, wmOperator *op) +{ + const bool include_handles = RNA_boolean_get(op->ptr, "include_handles"); + const int smooth_viewtx = WM_operator_smooth_viewtx_get(op); + + /* Whole range */ + return graphkeys_viewall(C, false, include_handles, smooth_viewtx); +} + +static int graphkeys_view_selected_exec(bContext *C, wmOperator *op) +{ + const bool include_handles = RNA_boolean_get(op->ptr, "include_handles"); + const int smooth_viewtx = WM_operator_smooth_viewtx_get(op); + + /* Only selected. */ + return graphkeys_viewall(C, true, include_handles, smooth_viewtx); +} + +/* ......... */ + +void GRAPH_OT_view_all(wmOperatorType *ot) +{ + /* Identifiers */ + ot->name = "Frame All"; + ot->idname = "GRAPH_OT_view_all"; + ot->description = "Reset viewable area to show full keyframe range"; + + /* API callbacks */ + ot->exec = graphkeys_viewall_exec; + /* XXX: Unchecked poll to get fsamples working too, but makes modifier damage trickier... */ + ot->poll = ED_operator_graphedit_active; + + /* Flags */ + ot->flag = 0; + + /* Props */ + ot->prop = RNA_def_boolean(ot->srna, + "include_handles", + true, + "Include Handles", + "Include handles of keyframes when calculating extents"); +} + +void GRAPH_OT_view_selected(wmOperatorType *ot) +{ + /* Identifiers */ + ot->name = "Frame Selected"; + ot->idname = "GRAPH_OT_view_selected"; + ot->description = "Reset viewable area to show selected keyframe range"; + + /* API callbacks */ + ot->exec = graphkeys_view_selected_exec; + /* XXX: Unchecked poll to get fsamples working too, but makes modifier damage trickier... */ + ot->poll = ED_operator_graphedit_active; + + /* Flags */ + ot->flag = 0; + + /* Props */ + ot->prop = RNA_def_boolean(ot->srna, + "include_handles", + true, + "Include Handles", + "Include handles of keyframes when calculating extents"); +} + +/* ********************** View Frame Operator ****************************** */ + +static int graphkeys_view_frame_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 GRAPH_OT_view_frame(wmOperatorType *ot) +{ + /* Identifiers */ + ot->name = "Go to Current Frame"; + ot->idname = "GRAPH_OT_view_frame"; + ot->description = "Move the view to the current frame"; + + /* API callbacks */ + ot->exec = graphkeys_view_frame_exec; + ot->poll = ED_operator_graphedit_active; + + /* Flags */ + ot->flag = 0; +} + +/* ******************** Create Ghost-Curves Operator *********************** */ +/* This operator samples the data of the selected F-Curves to F-Points, storing them + * as 'ghost curves' in the active Graph Editor. + */ + +/* Bake each F-Curve into a set of samples, and store as a ghost curve. */ +static void create_ghost_curves(bAnimContext *ac, int start, int end) +{ + SpaceGraph *sipo = (SpaceGraph *)ac->sl; + ListBase anim_data = {NULL, NULL}; + bAnimListElem *ale; + int filter; + + /* Free existing ghost curves. */ + BKE_fcurves_free(&sipo->runtime.ghost_curves); + + /* Sanity check. */ + if (start >= end) { + printf("Error: Frame range for Ghost F-Curve creation is inappropriate\n"); + return; + } + + /* Filter data. */ + filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_CURVE_VISIBLE | ANIMFILTER_SEL | + ANIMFILTER_NODUPLIS); + ANIM_animdata_filter(ac, &anim_data, filter, ac->data, ac->datatype); + + /* Loop through filtered data and add keys between selected keyframes on every frame . */ + for (ale = anim_data.first; ale; ale = ale->next) { + FCurve *fcu = (FCurve *)ale->key_data; + FCurve *gcu = BKE_fcurve_create(); + AnimData *adt = ANIM_nla_mapping_get(ac, ale); + ChannelDriver *driver = fcu->driver; + FPoint *fpt; + float unitFac, offset; + int cfra; + short mapping_flag = ANIM_get_normalization_flags(ac); + + /* Disable driver so that it don't muck up the sampling process. */ + fcu->driver = NULL; + + /* Calculate unit-mapping factor. */ + unitFac = ANIM_unit_mapping_get_factor(ac->scene, ale->id, fcu, mapping_flag, &offset); + + /* Create samples, but store them in a new curve + * - we cannot use fcurve_store_samples() as that will only overwrite the original curve. + */ + gcu->fpt = fpt = MEM_callocN(sizeof(FPoint) * (end - start + 1), "Ghost FPoint Samples"); + gcu->totvert = end - start + 1; + + /* Use the sampling callback at 1-frame intervals from start to end frames. */ + for (cfra = start; cfra <= end; cfra++, fpt++) { + float cfrae = BKE_nla_tweakedit_remap(adt, cfra, NLATIME_CONVERT_UNMAP); + + fpt->vec[0] = cfrae; + fpt->vec[1] = (fcurve_samplingcb_evalcurve(fcu, NULL, cfrae) + offset) * unitFac; + } + + /* Set color of ghost curve + * - make the color slightly darker. + */ + gcu->color[0] = fcu->color[0] - 0.07f; + gcu->color[1] = fcu->color[1] - 0.07f; + gcu->color[2] = fcu->color[2] - 0.07f; + + /* Store new ghost curve. */ + BLI_addtail(&sipo->runtime.ghost_curves, gcu); + + /* Restore driver. */ + fcu->driver = driver; + } + + /* Admin and redraws. */ + ANIM_animdata_freelist(&anim_data); +} + +/* ------------------- */ + +static int graphkeys_create_ghostcurves_exec(bContext *C, wmOperator *UNUSED(op)) +{ + bAnimContext ac; + View2D *v2d; + int start, end; + + /* Get editor data. */ + if (ANIM_animdata_get_context(C, &ac) == 0) { + return OPERATOR_CANCELLED; + } + + /* Ghost curves are snapshots of the visible portions of the curves, + * so set range to be the visible range. */ + v2d = &ac.region->v2d; + start = (int)v2d->cur.xmin; + end = (int)v2d->cur.xmax; + + /* Bake selected curves into a ghost curve. */ + create_ghost_curves(&ac, start, end); + + /* Update this editor only. */ + ED_area_tag_redraw(CTX_wm_area(C)); + + return OPERATOR_FINISHED; +} + +void GRAPH_OT_ghost_curves_create(wmOperatorType *ot) +{ + /* Identifiers */ + ot->name = "Create Ghost Curves"; + ot->idname = "GRAPH_OT_ghost_curves_create"; + ot->description = + "Create snapshot (Ghosts) of selected F-Curves as background aid for active Graph Editor"; + + /* API callbacks */ + ot->exec = graphkeys_create_ghostcurves_exec; + ot->poll = graphop_visible_keyframes_poll; + + /* Flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + /* TODO: add props for start/end frames */ +} + +/* ******************** Clear Ghost-Curves Operator *********************** */ +/* This operator clears the 'ghost curves' for the active Graph Editor */ + +static int graphkeys_clear_ghostcurves_exec(bContext *C, wmOperator *UNUSED(op)) +{ + bAnimContext ac; + SpaceGraph *sipo; + + /* Get editor data. */ + if (ANIM_animdata_get_context(C, &ac) == 0) { + return OPERATOR_CANCELLED; + } + sipo = (SpaceGraph *)ac.sl; + + /* If no ghost curves, don't do anything. */ + if (BLI_listbase_is_empty(&sipo->runtime.ghost_curves)) { + return OPERATOR_CANCELLED; + } + /* Free ghost curves. */ + BKE_fcurves_free(&sipo->runtime.ghost_curves); + + /* Update this editor only. */ + ED_area_tag_redraw(CTX_wm_area(C)); + + return OPERATOR_FINISHED; +} + +void GRAPH_OT_ghost_curves_clear(wmOperatorType *ot) +{ + /* Identifiers */ + ot->name = "Clear Ghost Curves"; + ot->idname = "GRAPH_OT_ghost_curves_clear"; + ot->description = "Clear F-Curve snapshots (Ghosts) for active Graph Editor"; + + /* API callbacks */ + ot->exec = graphkeys_clear_ghostcurves_exec; + ot->poll = ED_operator_graphedit_active; + + /* Flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; +}
\ No newline at end of file |