From b27718a9e73a8e5ae29fb68a439ec76fbb5d8080 Mon Sep 17 00:00:00 2001 From: Christoph Lendenfeld Date: Thu, 21 Dec 2023 10:46:43 +0100 Subject: [PATCH] Anim: Graph Editor Scale From Neighbor Combination of two operators by Ares Deveaux #106524 and #106523 Introduces a new operator "Scale from Neighbor" that scales selected keyframe segments from either the left or right keyframe neighbor. Pressing "D" during modal operation will switch from which end of the segment the scaling happens. This is useful to make a section of animation closer to a pose on either side. Co-authored-by: Ares Deveaux Pull Request: https://projects.blender.org/blender/blender/pulls/112387 --- scripts/startup/bl_ui/space_graph.py | 1 + .../editors/animation/keyframes_general.cc | 23 +++ .../editors/include/ED_keyframes_edit.hh | 6 + .../editors/space_graph/graph_intern.h | 1 + .../blender/editors/space_graph/graph_ops.cc | 1 + .../editors/space_graph/graph_slider_ops.cc | 188 ++++++++++++++++++ 6 files changed, 220 insertions(+) diff --git a/scripts/startup/bl_ui/space_graph.py b/scripts/startup/bl_ui/space_graph.py index 1334550b752..7df04028b55 100644 --- a/scripts/startup/bl_ui/space_graph.py +++ b/scripts/startup/bl_ui/space_graph.py @@ -337,6 +337,7 @@ class GRAPH_MT_key_blending(Menu): layout.operator("graph.push_pull", text="Push Pull") layout.operator("graph.shear", text="Shear Keys") layout.operator("graph.scale_average", text="Scale Average") + layout.operator("graph.scale_from_neighbor", text="Scale from Neighbor") layout.operator("graph.time_offset", text="Time Offset") diff --git a/source/blender/editors/animation/keyframes_general.cc b/source/blender/editors/animation/keyframes_general.cc index df4a191c73f..a8cca182a55 100644 --- a/source/blender/editors/animation/keyframes_general.cc +++ b/source/blender/editors/animation/keyframes_general.cc @@ -951,6 +951,29 @@ void time_offset_fcurve_segment(FCurve *fcu, FCurveSegment *segment, const float /* ---------------- */ +void scale_from_fcurve_segment_neighbor(FCurve *fcu, + FCurveSegment *segment, + const float factor, + const FCurveSegmentAnchor anchor) +{ + const BezTriple *reference_key; + switch (anchor) { + case FCurveSegmentAnchor::LEFT: + reference_key = fcurve_segment_start_get(fcu, segment->start_index); + break; + case FCurveSegmentAnchor::RIGHT: + reference_key = fcurve_segment_end_get(fcu, segment->start_index + segment->length); + break; + } + + for (int i = segment->start_index; i < segment->start_index + segment->length; i++) { + const float key_y_value = interpf(fcu->bezt[i].vec[1][1], reference_key->vec[1][1], factor); + BKE_fcurve_keyframe_move_value_with_handles(&fcu->bezt[i], key_y_value); + } +} + +/* ---------------- */ + 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 a02f6edb88e..8fa1f1b0135 100644 --- a/source/blender/editors/include/ED_keyframes_edit.hh +++ b/source/blender/editors/include/ED_keyframes_edit.hh @@ -437,6 +437,12 @@ void blend_to_neighbor_fcurve_segment(FCurve *fcu, FCurveSegment *segment, float void breakdown_fcurve_segment(FCurve *fcu, FCurveSegment *segment, float factor); void scale_average_fcurve_segment(FCurve *fcu, FCurveSegment *segment, float factor); void push_pull_fcurve_segment(FCurve *fcu, FCurveSegment *segment, float factor); +/* Used for operators that need a reference key of the segment to work. */ +enum class FCurveSegmentAnchor { LEFT, RIGHT }; +void scale_from_fcurve_segment_neighbor(FCurve *fcu, + FCurveSegment *segment, + float factor, + FCurveSegmentAnchor anchor); /** * Get a 1D gauss kernel. Since the kernel is symmetrical, only calculates the positive side. * \param sigma: The shape of the gauss distribution. diff --git a/source/blender/editors/space_graph/graph_intern.h b/source/blender/editors/space_graph/graph_intern.h index 5d0d4ba88de..fe868e6aaef 100644 --- a/source/blender/editors/space_graph/graph_intern.h +++ b/source/blender/editors/space_graph/graph_intern.h @@ -135,6 +135,7 @@ void GRAPH_OT_shear(struct wmOperatorType *ot); void GRAPH_OT_scale_average(struct wmOperatorType *ot); void GRAPH_OT_push_pull(struct wmOperatorType *ot); void GRAPH_OT_time_offset(struct wmOperatorType *ot); +void GRAPH_OT_scale_from_neighbor(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 ef2796d442d..0b01c62e01e 100644 --- a/source/blender/editors/space_graph/graph_ops.cc +++ b/source/blender/editors/space_graph/graph_ops.cc @@ -472,6 +472,7 @@ void graphedit_operatortypes() WM_operatortype_append(GRAPH_OT_ease); WM_operatortype_append(GRAPH_OT_shear); WM_operatortype_append(GRAPH_OT_scale_average); + WM_operatortype_append(GRAPH_OT_scale_from_neighbor); WM_operatortype_append(GRAPH_OT_blend_offset); WM_operatortype_append(GRAPH_OT_blend_to_ease); WM_operatortype_append(GRAPH_OT_match_slope); diff --git a/source/blender/editors/space_graph/graph_slider_ops.cc b/source/blender/editors/space_graph/graph_slider_ops.cc index 2b98741a769..b9324bd4522 100644 --- a/source/blender/editors/space_graph/graph_slider_ops.cc +++ b/source/blender/editors/space_graph/graph_slider_ops.cc @@ -2280,3 +2280,191 @@ void GRAPH_OT_push_pull(wmOperatorType *ot) 2.0f); } /** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Scale from Left Operator + * \{ */ + +static const EnumPropertyItem scale_anchor_items[] = { + {int(FCurveSegmentAnchor::LEFT), "LEFT", 0, "From Left", "foo"}, + {int(FCurveSegmentAnchor::RIGHT), "RIGHT", 0, "From Right", "foo"}, + {0, nullptr, 0, nullptr, nullptr}, +}; + +static void scale_from_neighbor_graph_keys(bAnimContext *ac, + const float factor, + const FCurveSegmentAnchor anchor) +{ + ListBase anim_data = {nullptr, nullptr}; + + 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) { + scale_from_fcurve_segment_neighbor(fcu, segment, factor, anchor); + } + + ale->update |= ANIM_UPDATE_DEFAULT; + BLI_freelistN(&segments); + } + + ANIM_animdata_update(ac, &anim_data); + ANIM_animdata_freelist(&anim_data); +} + +static void scale_from_neighbor_draw_status_header(bContext *C, wmOperator *op) +{ + char status_str[UI_MAX_DRAW_STR]; + char mode_str[32]; + char slider_string[UI_MAX_DRAW_STR]; + + tGraphSliderOp *gso = static_cast(op->customdata); + ED_slider_status_string_get(gso->slider, slider_string, UI_MAX_DRAW_STR); + + /* Operator specific functionality that extends beyond the slider. */ + char op_slider_string[UI_MAX_DRAW_STR]; + const FCurveSegmentAnchor anchor = FCurveSegmentAnchor(RNA_enum_get(op->ptr, "anchor")); + switch (anchor) { + case FCurveSegmentAnchor::LEFT: + SNPRINTF(op_slider_string, "%s | %s", slider_string, "[D] - Scale From Right End"); + break; + + case FCurveSegmentAnchor::RIGHT: + SNPRINTF(op_slider_string, "%s | %s", slider_string, "[D] - Scale From Left End"); + break; + } + + STRNCPY(mode_str, TIP_("Scale from Neighbor Keys")); + + if (hasNumInput(&gso->num)) { + char str_ofs[NUM_STR_REP_LEN]; + + outputNumInput(&gso->num, str_ofs, &gso->scene->unit); + + SNPRINTF(status_str, "%s: %s", mode_str, str_ofs); + } + else { + SNPRINTF(status_str, "%s: %s", mode_str, op_slider_string); + } + + ED_workspace_status_text(C, status_str); +} + +static void scale_from_neighbor_modal_update(bContext *C, wmOperator *op) +{ + tGraphSliderOp *gso = static_cast(op->customdata); + + scale_from_neighbor_draw_status_header(C, op); + + /* Reset keyframes to the state at invoke. */ + reset_bezts(gso); + const float factor = slider_factor_get_and_remember(op); + const FCurveSegmentAnchor anchor = FCurveSegmentAnchor(RNA_enum_get(op->ptr, "anchor")); + scale_from_neighbor_graph_keys(&gso->ac, factor, anchor); + WM_event_add_notifier(C, NC_ANIMATION | ND_KEYFRAME | NA_EDITED, NULL); +} + +static int scale_from_neighbor_modal(bContext *C, wmOperator *op, const wmEvent *event) +{ + if (event->val != KM_PRESS) { + return graph_slider_modal(C, op, event); + } + + switch (event->type) { + case EVT_DKEY: { + FCurveSegmentAnchor anchor = FCurveSegmentAnchor(RNA_enum_get(op->ptr, "anchor")); + switch (anchor) { + case FCurveSegmentAnchor::LEFT: + RNA_enum_set(op->ptr, "anchor", int(FCurveSegmentAnchor::RIGHT)); + break; + + case FCurveSegmentAnchor::RIGHT: + RNA_enum_set(op->ptr, "anchor", int(FCurveSegmentAnchor::LEFT)); + break; + } + scale_from_neighbor_modal_update(C, op); + break; + } + + default: + return graph_slider_modal(C, op, event); + } + return OPERATOR_RUNNING_MODAL; +} + +static int scale_from_neighbor_invoke(bContext *C, wmOperator *op, const wmEvent *event) +{ + const int invoke_result = graph_slider_invoke(C, op, event); + + if (invoke_result == OPERATOR_CANCELLED) { + return OPERATOR_CANCELLED; + } + + tGraphSliderOp *gso = static_cast(op->customdata); + gso->modal_update = scale_from_neighbor_modal_update; + gso->factor_prop = RNA_struct_find_property(op->ptr, "factor"); + scale_from_neighbor_draw_status_header(C, op); + ED_slider_factor_bounds_set(gso->slider, 0, 2); + ED_slider_factor_set(gso->slider, 1.0f); + + return invoke_result; +} + +static int scale_from_neighbor_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"); + + const FCurveSegmentAnchor anchor = FCurveSegmentAnchor(RNA_enum_get(op->ptr, "anchor")); + scale_from_neighbor_graph_keys(&ac, factor, anchor); + + /* Set notifier that keyframes have changed. */ + WM_event_add_notifier(C, NC_ANIMATION | ND_KEYFRAME | NA_EDITED, NULL); + + return OPERATOR_FINISHED; +} + +void GRAPH_OT_scale_from_neighbor(wmOperatorType *ot) +{ + /* Identifiers. */ + ot->name = "Scale from Neighbor"; + ot->idname = "GRAPH_OT_scale_from_neighbor"; + ot->description = + "Increase or decrease the value of selected keys \n\ + in relationship to the neighboring one"; + + /* API callbacks. */ + ot->invoke = scale_from_neighbor_invoke; + ot->modal = scale_from_neighbor_modal; + ot->exec = scale_from_neighbor_exec; + ot->poll = graphop_editable_keyframes_poll; + + /* Flags. */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO | OPTYPE_BLOCKING | OPTYPE_GRAB_CURSOR_X; + + RNA_def_float_factor(ot->srna, + "factor", + 0.0f, + -FLT_MAX, + FLT_MAX, + "Factor", + "The factor to scale keys with", + -1.0f, + 1.0f); + + RNA_def_enum(ot->srna, + "anchor", + scale_anchor_items, + int(FCurveSegmentAnchor::LEFT), + "Reference Key", + "Which end of the segment to use as a reference to scale from"); +}