From bb8766ef01ce0cc32e05da3505a9d8e3f4f0aec3 Mon Sep 17 00:00:00 2001 From: Christoph Lendenfeld Date: Thu, 17 Aug 2023 10:28:46 +0200 Subject: [PATCH] Animation: Match Slope slider It blends selected keys to the slope of neighboring ones. It is used to push the segment closer to the values of the next or previous pose. Co-authored-by: Ares Deveaux Pull Request: https://projects.blender.org/blender/blender/pulls/110567 --- scripts/startup/bl_ui/space_graph.py | 1 + .../editors/animation/keyframes_general.cc | 52 ++++++++ .../editors/include/ED_keyframes_edit.hh | 1 + .../editors/space_graph/graph_intern.h | 1 + .../blender/editors/space_graph/graph_ops.cc | 1 + .../editors/space_graph/graph_slider_ops.cc | 120 ++++++++++++++++++ 6 files changed, 176 insertions(+) diff --git a/scripts/startup/bl_ui/space_graph.py b/scripts/startup/bl_ui/space_graph.py index 3f16fb7e4ee..b47ef2473a2 100644 --- a/scripts/startup/bl_ui/space_graph.py +++ b/scripts/startup/bl_ui/space_graph.py @@ -301,6 +301,7 @@ class GRAPH_MT_key_blending(Menu): layout.operator("graph.ease", text="Ease") layout.operator("graph.blend_offset", text="Blend Offset") layout.operator("graph.blend_to_ease", text="Blend to Ease") + layout.operator("graph.match_slope", text="Match Slope") class GRAPH_MT_key_smoothing(Menu): diff --git a/source/blender/editors/animation/keyframes_general.cc b/source/blender/editors/animation/keyframes_general.cc index 4abad28763a..704c907545a 100644 --- a/source/blender/editors/animation/keyframes_general.cc +++ b/source/blender/editors/animation/keyframes_general.cc @@ -774,6 +774,58 @@ void blend_to_ease_fcurve_segment(FCurve *fcu, FCurveSegment *segment, const flo /* ---------------- */ +bool match_slope_fcurve_segment(FCurve *fcu, FCurveSegment *segment, const float factor) +{ + const BezTriple *left_key = fcurve_segment_start_get(fcu, segment->start_index); + const BezTriple *right_key = fcurve_segment_end_get(fcu, segment->start_index + segment->length); + + BezTriple beyond_key; + const BezTriple *reference_key; + + if (factor >= 0) { + /* Stop the function if there is no key beyond the the right neighboring one. */ + if (segment->start_index + segment->length >= fcu->totvert - 1) { + return false; + } + reference_key = right_key; + beyond_key = fcu->bezt[segment->start_index + segment->length + 1]; + } + else { + /* Stop the function if there is no key beyond the left neighboring one. */ + if (segment->start_index <= 1) { + return false; + } + reference_key = left_key; + beyond_key = fcu->bezt[segment->start_index - 2]; + } + + /* This delta values are used to get the relationship between the bookend keys and the + * reference keys beyong those. */ + const float y_delta = beyond_key.vec[1][1] - reference_key->vec[1][1]; + const float x_delta = beyond_key.vec[1][0] - reference_key->vec[1][0]; + + /* Avoids dividing by 0. */ + if (x_delta == 0) { + return false; + } + + for (int i = segment->start_index; i < segment->start_index + segment->length; i++) { + + /* These new deltas are used to determine the relationship between the current key and the + * bookend ones. */ + const float new_x_delta = fcu->bezt[i].vec[1][0] - reference_key->vec[1][0]; + const float new_y_delta = new_x_delta * y_delta / x_delta; + + const float delta = reference_key->vec[1][1] + new_y_delta - fcu->bezt[i].vec[1][1]; + + const float key_y_value = fcu->bezt[i].vec[1][1] + delta * fabs(factor); + BKE_fcurve_keyframe_move_value_with_handles(&fcu->bezt[i], key_y_value); + } + return true; +} + +/* ---------------- */ + void breakdown_fcurve_segment(FCurve *fcu, FCurveSegment *segment, const float factor) { const BezTriple *left_bezt = fcurve_segment_start_get(fcu, segment->start_index); diff --git a/source/blender/editors/include/ED_keyframes_edit.hh b/source/blender/editors/include/ED_keyframes_edit.hh index 19732cd6ceb..69bdcf3fb19 100644 --- a/source/blender/editors/include/ED_keyframes_edit.hh +++ b/source/blender/editors/include/ED_keyframes_edit.hh @@ -468,6 +468,7 @@ void ease_fcurve_segment(FCurve *fcu, FCurveSegment *segment, float factor); void blend_offset_fcurve_segment(FCurve *fcu, FCurveSegment *segment, float factor); void blend_to_ease_fcurve_segment(FCurve *fcu, FCurveSegment *segment, float factor); bool decimate_fcurve(bAnimListElem *ale, float remove_ratio, float error_sq_max); +bool match_slope_fcurve_segment(FCurve *fcu, FCurveSegment *segment, float factor); /** * Blends the selected keyframes to the default value of the property the F-curve drives. diff --git a/source/blender/editors/space_graph/graph_intern.h b/source/blender/editors/space_graph/graph_intern.h index 133fd4482e8..c1e93a184cb 100644 --- a/source/blender/editors/space_graph/graph_intern.h +++ b/source/blender/editors/space_graph/graph_intern.h @@ -121,6 +121,7 @@ void GRAPH_OT_breakdown(struct wmOperatorType *ot); void GRAPH_OT_ease(struct wmOperatorType *ot); void GRAPH_OT_blend_offset(struct wmOperatorType *ot); void GRAPH_OT_blend_to_ease(struct wmOperatorType *ot); +void GRAPH_OT_match_slope(struct wmOperatorType *ot); void GRAPH_OT_decimate(struct wmOperatorType *ot); void GRAPH_OT_blend_to_default(struct wmOperatorType *ot); void GRAPH_OT_butterworth_smooth(struct wmOperatorType *ot); diff --git a/source/blender/editors/space_graph/graph_ops.cc b/source/blender/editors/space_graph/graph_ops.cc index d83281d78aa..89b4b55f4cc 100644 --- a/source/blender/editors/space_graph/graph_ops.cc +++ b/source/blender/editors/space_graph/graph_ops.cc @@ -471,6 +471,7 @@ void graphedit_operatortypes() WM_operatortype_append(GRAPH_OT_ease); WM_operatortype_append(GRAPH_OT_blend_offset); WM_operatortype_append(GRAPH_OT_blend_to_ease); + WM_operatortype_append(GRAPH_OT_match_slope); WM_operatortype_append(GRAPH_OT_blend_to_default); WM_operatortype_append(GRAPH_OT_gaussian_smooth); WM_operatortype_append(GRAPH_OT_butterworth_smooth); diff --git a/source/blender/editors/space_graph/graph_slider_ops.cc b/source/blender/editors/space_graph/graph_slider_ops.cc index af30fdf14fa..536bb4d9ff6 100644 --- a/source/blender/editors/space_graph/graph_slider_ops.cc +++ b/source/blender/editors/space_graph/graph_slider_ops.cc @@ -1166,6 +1166,126 @@ void GRAPH_OT_blend_to_ease(wmOperatorType *ot) 1.0f); } +/* -------------------------------------------------------------------- */ +/** \name Match Slope + * \{ */ + +static void match_slope_graph_keys(bAnimContext *ac, const float factor) +{ + ListBase anim_data = {NULL, NULL}; + + bool all_segments_valid = true; + + ANIM_animdata_filter( + ac, &anim_data, OPERATOR_DATA_FILTER, ac->data, eAnimCont_Types(ac->datatype)); + LISTBASE_FOREACH (bAnimListElem *, ale, &anim_data) { + FCurve *fcu = (FCurve *)ale->key_data; + ListBase segments = find_fcurve_segments(fcu); + + LISTBASE_FOREACH (FCurveSegment *, segment, &segments) { + all_segments_valid = match_slope_fcurve_segment(fcu, segment, factor); + } + + ale->update |= ANIM_UPDATE_DEFAULT; + BLI_freelistN(&segments); + } + + if(!all_segments_valid) { + if (factor >= 0){ + WM_report(RPT_WARNING, "You need at least 2 keys to the right side of the selection."); + } + else { + WM_report(RPT_WARNING, "You need at least 2 keys to the left side of the selection."); + } + } + + ANIM_animdata_update(ac, &anim_data); + ANIM_animdata_freelist(&anim_data); +} + +static void match_slope_draw_status_header(bContext *C, tGraphSliderOp *gso) +{ + common_draw_status_header(C, gso, "Match Slope"); +} + +static void match_slope_modal_update(bContext *C, wmOperator *op) +{ + tGraphSliderOp *gso = static_cast(op->customdata); + + match_slope_draw_status_header(C, gso); + + /* Reset keyframes to the state at invoke. */ + reset_bezts(gso); + const float factor = slider_factor_get_and_remember(op); + match_slope_graph_keys(&gso->ac, factor); + WM_event_add_notifier(C, NC_ANIMATION | ND_KEYFRAME | NA_EDITED, NULL); +} + +static int match_slope_invoke(bContext *C, wmOperator *op, const wmEvent *event) +{ + const int invoke_result = graph_slider_invoke(C, op, event); + + if (invoke_result == OPERATOR_CANCELLED) { + return invoke_result; + } + + tGraphSliderOp *gso = static_cast(op->customdata); + gso->modal_update = match_slope_modal_update; + gso->factor_prop = RNA_struct_find_property(op->ptr, "factor"); + match_slope_draw_status_header(C, gso); + ED_slider_allow_overshoot_set(gso->slider, false, false); + ED_slider_factor_bounds_set(gso->slider, -1, 1); + ED_slider_factor_set(gso->slider, 0.0f); + + return invoke_result; +} + +static int match_slope_exec(bContext *C, wmOperator *op) +{ + bAnimContext ac; + + /* Get editor data. */ + if (ANIM_animdata_get_context(C, &ac) == 0) { + return OPERATOR_CANCELLED; + } + + const float factor = RNA_float_get(op->ptr, "factor"); + + match_slope_graph_keys(&ac, factor); + + /* Set notifier that keyframes have changed. */ + WM_event_add_notifier(C, NC_ANIMATION | ND_KEYFRAME | NA_EDITED, NULL); + + return OPERATOR_FINISHED; +} + +void GRAPH_OT_match_slope(wmOperatorType *ot) +{ + /* Identifiers. */ + ot->name = "Match Slope"; + ot->idname = "GRAPH_OT_match_slope"; + ot->description = "Blend selected keys to the slope of neighboring ones"; + + /* API callbacks. */ + ot->invoke = match_slope_invoke; + ot->modal = graph_slider_modal; + ot->exec = match_slope_exec; + ot->poll = graphop_editable_keyframes_poll; + + /* Flags. */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + RNA_def_float_factor(ot->srna, + "factor", + 0.0f, + -FLT_MAX, + FLT_MAX, + "Factor", + "Defines which keys to use as slope and how much to blend towards them", + -1.0f, + 1.0f); +} + /** \} */ /* -------------------------------------------------------------------- */ /** \name Gauss Smooth Operator