Animation: blend to infinity slider #106517

Closed
AresDeveaux wants to merge 15 commits from AresDeveaux/blender:blend_to_infinity_slider into main

When changing the target branch, be careful to rebase the branch in your fork to match. See documentation.
6 changed files with 196 additions and 0 deletions

View File

@ -329,6 +329,7 @@ class GRAPH_MT_slider(Menu):
layout.operator("graph.breakdown", text="Breakdown")
layout.operator("graph.blend_to_neighbor", text="Blend to Neighbor")
layout.operator("graph.blend_to_default", text="Blend to Default Value")
layout.operator("graph.blend_to_infinity", text="Blend to Infinity")
layout.operator("graph.ease", text="Ease")
layout.operator("graph.gaussian_smooth", text="Smooth")

View File

@ -491,6 +491,58 @@ void ease_fcurve_segment(FCurve *fcu, FCurveSegment *segment, const float factor
/* ---------------- */
bool blend_to_infinity_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;

I think instead of splitting the vector into its two components it would be better to keep it as a vector and use the functions in math_vector_inline.c

This will potentially simplify your code quite a lot.

I think instead of splitting the vector into its two components it would be better to keep it as a vector and use the functions in `math_vector_inline.c` This will potentially simplify your code quite a lot.
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;

this can also be clearer.
segment->start_index + segment->length > fcu->totvert

this can also be clearer. `segment->start_index + segment->length > fcu->totvert`

I tried this change as well as the next one and it doesn't work as it should. Remember, we need 2 keys before and after the segment, the way you suggest just account for one. If I don't prevent not having 2 keys at each side of the segment the tool goes crazy.

I tried this change as well as the next one and it doesn't work as it should. Remember, we need 2 keys before and after the segment, the way you suggest just account for one. If I don't prevent not having 2 keys at each side of the segment the tool goes crazy.
}
reference_key = left_key;
beyond_key = fcu->bezt[segment->start_index - 2];
}

I don't think you need this. At least I can't see it being used other than the check for 0.0f

If it is required that there is more than 1 key selected, update the comment because there is no potential divide by 0

I don't think you need this. At least I can't see it being used other than the check for 0.0f If it is required that there is more than 1 key selected, update the comment because there is no potential divide by 0
/* 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];

This would be clearer if you checked explicitly to 0
segment->start_index == 0

the start index will never be smaller than 0

This would be clearer if you checked explicitly to 0 `segment->start_index == 0` the start index will never be smaller than 0

I am glad you didn't listen to me here, because I just realized what I suggested is wrong 🤦

I am glad you didn't listen to me here, because I just realized what I suggested is wrong 🤦
/* Avoids dividing by 0. */
if (x_delta == 0) {
return false;

shouldn't this return false?
also now that I think about it, I am not sure x_delta can ever be 0. It would only be 0 if the beyond key and the reference key are the same, which they cannot be since you are getting different indices.
Hm ok writing this now, you can have the curve in a state where two keys are at the same point in time. Shouldn't happen, but could...

Anyway, the function should return true if it ran and false if it didn't. Since you are exiting early here, I think it should return false.

shouldn't this return false? also now that I think about it, I am not sure x_delta can ever be 0. It would only be 0 if the beyond key and the reference key are the same, which they cannot be since you are getting different indices. Hm ok writing this now, you can have the curve in a state where two keys are at the same point in time. Shouldn't happen, but could... Anyway, the function should return true if it ran and false if it didn't. Since you are exiting early here, I think it should 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

since this is now outside of the if else, you can to
const float y_delta = ...
and then remove the lines 503 and 504

since this is now outside of the if else, you can to `const float y_delta = ...` and then remove the lines 503 and 504
* 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);
move_key(&fcu->bezt[i], key_y_value);

this can move into the for loop and you can then call it
const float new_x_delta = ...

this can move into the for loop and you can then call it `const float new_x_delta = ...`
}
return true;
}

this code can be moved to the first if else

this code can be moved to the first `if else`
/* ---------------- */
void breakdown_fcurve_segment(FCurve *fcu, FCurveSegment *segment, const float factor)
{
const BezTriple *left_bezt = fcurve_segment_start_get(fcu, segment->start_index);

View File

@ -437,6 +437,9 @@ void smooth_fcurve_segment(struct FCurve *fcu,
int kernel_size,
double *kernel);
void ease_fcurve_segment(struct FCurve *fcu, struct FCurveSegment *segment, float factor);
bool blend_to_infinity_fcurve_segment(struct FCurve *fcu,
struct FCurveSegment *segment,
float factor);
bool decimate_fcurve(struct bAnimListElem *ale, float remove_ratio, float error_sq_max);
void blend_to_default_fcurve(struct PointerRNA *id_ptr, struct FCurve *fcu, float factor);
/**

View File

@ -114,6 +114,7 @@ void GRAPH_OT_clean(struct wmOperatorType *ot);
void GRAPH_OT_blend_to_neighbor(struct wmOperatorType *ot);
void GRAPH_OT_breakdown(struct wmOperatorType *ot);
void GRAPH_OT_ease(struct wmOperatorType *ot);
void GRAPH_OT_blend_to_infinity(struct wmOperatorType *ot);
void GRAPH_OT_decimate(struct wmOperatorType *ot);
void GRAPH_OT_blend_to_default(struct wmOperatorType *ot);
void GRAPH_OT_gaussian_smooth(struct wmOperatorType *ot);

View File

@ -463,6 +463,7 @@ void graphedit_operatortypes(void)
WM_operatortype_append(GRAPH_OT_blend_to_neighbor);
WM_operatortype_append(GRAPH_OT_breakdown);
WM_operatortype_append(GRAPH_OT_ease);
WM_operatortype_append(GRAPH_OT_blend_to_infinity);
WM_operatortype_append(GRAPH_OT_blend_to_default);
WM_operatortype_append(GRAPH_OT_gaussian_smooth);
WM_operatortype_append(GRAPH_OT_euler_filter);

View File

@ -1060,6 +1060,144 @@ void GRAPH_OT_ease(wmOperatorType *ot)
1.0f);
}
/* -------------------------------------------------------------------- */
/** \name Blend to Infinity
* \{ */
static void blend_to_infinity_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, 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 = blend_to_infinity_fcurve_segment(fcu, segment, factor);

the conditions can also be simplified like above
you can also continue after the warning because the code cannot run anyway

the conditions can also be simplified like above you can also `continue` after the warning because the code cannot run anyway

your suggestion of adding continue to the condition for the warning works, but made me realize 2 things:

1.- I need to split the warning into two possibilities. One when we don't have two reference keys on the left, and another if we don't have two reference keys on the right. As it is now it locks the function even if one of the sides has all it needs (I added this)

2.- If we put continue on the condition in graph_slider_ops.c we don't need the same if statements in `keyframes_general.c' since it will never be executed. (I didn't delete them until you take a look at it)

By the way, doesn't it bother you the warning is blinking? I'm thinking it's doing that because of where I put it. I think it is refreshing as the function is called back when the slider is modified, but don't know in what function I should put it to avoid it.

your suggestion of adding continue to the condition for the warning works, but made me realize 2 things: 1.- I need to split the warning into two possibilities. One when we don't have two reference keys on the left, and another if we don't have two reference keys on the right. As it is now it locks the function even if one of the sides has all it needs (I added this) 2.- If we put continue on the condition in graph_slider_ops.c we don't need the same if statements in `keyframes_general.c' since it will never be executed. (I didn't delete them until you take a look at it) By the way, doesn't it bother you the warning is blinking? I'm thinking it's doing that because of where I put it. I think it is refreshing as the function is called back when the slider is modified, but don't know in what function I should put it to avoid it.

Let's have the warning outside of the for-loop so it's only displayed once per modal cycle
so make a bool all_segments_valid = true
and where the warning is now, set it to false

then after the 2 loops do a if(!all_segments_valid)

Let's have the warning outside of the for-loop so it's only displayed once per modal cycle so make a `bool all_segments_valid = true` and where the warning is now, set it to false then after the 2 loops do a `if(!all_segments_valid)`
}
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 blend_to_infinity_draw_status_header(bContext *C, tGraphSliderOp *gso)
{
char status_str[UI_MAX_DRAW_STR];
char mode_str[32];
char slider_string[UI_MAX_DRAW_STR];
ED_slider_status_string_get(gso->slider, slider_string, UI_MAX_DRAW_STR);
strcpy(mode_str, TIP_("Blend to Infinity Keys"));
if (hasNumInput(&gso->num)) {
char str_ofs[NUM_STR_REP_LEN];
outputNumInput(&gso->num, str_ofs, &gso->scene->unit);
BLI_snprintf(status_str, sizeof(status_str), "%s: %s", mode_str, str_ofs);
}
else {
BLI_snprintf(status_str, sizeof(status_str), "%s: %s", mode_str, slider_string);
}
ED_workspace_status_text(C, status_str);
}
static void blend_to_infinity_modal_update(bContext *C, wmOperator *op)
{
tGraphSliderOp *gso = op->customdata;
blend_to_infinity_draw_status_header(C, gso);
/* Reset keyframes to the state at invoke. */
reset_bezts(gso);
const float factor = slider_factor_get_and_remember(op);
blend_to_infinity_graph_keys(&gso->ac, factor);
WM_event_add_notifier(C, NC_ANIMATION | ND_KEYFRAME | NA_EDITED, NULL);
}
static int blend_to_infinity_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 = op->customdata;
gso->modal_update = blend_to_infinity_modal_update;
gso->factor_prop = RNA_struct_find_property(op->ptr, "factor");
blend_to_infinity_draw_status_header(C, gso);
ED_slider_allow_overshoot_set(gso->slider, false);
ED_slider_is_bidirectional_set(gso->slider, true);
ED_slider_factor_set(gso->slider, 0.0f);
return invoke_result;
}
static int blend_to_infinity_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");
blend_to_infinity_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_blend_to_infinity(wmOperatorType *ot)
{
/* Identifiers. */
ot->name = "Blend to Infinity Keyframes";
ot->idname = "GRAPH_OT_blend_to_infinity";
ot->description = "Blend selected keys to the slant of neighboring ones";
/* API callbacks. */
ot->invoke = blend_to_infinity_invoke;
ot->modal = graph_slider_modal;
ot->exec = blend_to_infinity_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,
"Curve Bend",
"Control the bend of the curve",
-1.0f,
1.0f);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Gauss Smooth Operator