Functions for convert between the color types and ostream support are now outside the classes. Many files were changed to fix cases where direct includes for headers were missing. Pull Request: https://projects.blender.org/blender/blender/pulls/145756
2586 lines
79 KiB
C++
2586 lines
79 KiB
C++
/* SPDX-FileCopyrightText: 2020 Blender Authors
|
|
*
|
|
* SPDX-License-Identifier: GPL-2.0-or-later */
|
|
|
|
/** \file
|
|
* \ingroup spgraph
|
|
*
|
|
* 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.
|
|
*/
|
|
|
|
#include <cfloat>
|
|
#include <cstring>
|
|
|
|
#include "MEM_guardedalloc.h"
|
|
|
|
#include "BLI_listbase.h"
|
|
#include "BLI_math_base.h"
|
|
|
|
#include "DEG_depsgraph.hh"
|
|
#include "DNA_anim_types.h"
|
|
#include "DNA_curve_types.h"
|
|
#include "DNA_scene_types.h"
|
|
|
|
#include "RNA_access.hh"
|
|
#include "RNA_define.hh"
|
|
|
|
#include "BLT_translation.hh"
|
|
|
|
#include "BKE_context.hh"
|
|
#include "BKE_report.hh"
|
|
|
|
#include "UI_interface.hh"
|
|
|
|
#include "ED_anim_api.hh"
|
|
#include "ED_keyframes_edit.hh"
|
|
#include "ED_numinput.hh"
|
|
#include "ED_screen.hh"
|
|
#include "ED_util.hh"
|
|
|
|
#include "WM_api.hh"
|
|
#include "WM_types.hh"
|
|
|
|
#include "ANIM_fcurve.hh"
|
|
|
|
#include <fmt/format.h>
|
|
|
|
#include "graph_intern.hh"
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Internal Struct & Defines
|
|
* \{ */
|
|
|
|
/* Used to obtain a list of animation channels for the operators to work on. */
|
|
#define OPERATOR_DATA_FILTER \
|
|
(ANIMFILTER_DATA_VISIBLE | ANIMFILTER_CURVE_VISIBLE | ANIMFILTER_FCURVESONLY | \
|
|
ANIMFILTER_FOREDIT | ANIMFILTER_SEL | ANIMFILTER_NODUPLIS)
|
|
|
|
/* This data type is only used for modal operation. */
|
|
struct tGraphSliderOp {
|
|
bAnimContext ac;
|
|
Scene *scene;
|
|
ScrArea *area;
|
|
ARegion *region;
|
|
|
|
/** A 0-1 value for determining how much we should decimate. */
|
|
PropertyRNA *factor_prop;
|
|
|
|
/** The original bezt curve data (used for restoring fcurves). */
|
|
ListBase bezt_arr_list;
|
|
|
|
tSlider *slider;
|
|
|
|
/* Each operator has a specific update function. */
|
|
void (*modal_update)(bContext *, wmOperator *);
|
|
|
|
/* If an operator stores custom data, it also needs to provide the function to clean it up. */
|
|
void *operator_data;
|
|
void (*free_operator_data)(void *operator_data);
|
|
|
|
NumInput num;
|
|
};
|
|
|
|
struct tBeztCopyData {
|
|
int tot_vert;
|
|
BezTriple *bezt;
|
|
};
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Utility Functions
|
|
* \{ */
|
|
|
|
/**
|
|
* Helper function that iterates over all FCurves and selected segments and applies the given
|
|
* function.
|
|
*/
|
|
static void apply_fcu_segment_function(bAnimContext *ac,
|
|
const float factor,
|
|
void (*segment_function)(FCurve *fcu,
|
|
FCurveSegment *segment,
|
|
const float factor))
|
|
{
|
|
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) {
|
|
segment_function(fcu, segment, factor);
|
|
}
|
|
|
|
ale->update |= ANIM_UPDATE_DEFAULT;
|
|
BLI_freelistN(&segments);
|
|
}
|
|
|
|
ANIM_animdata_update(ac, &anim_data);
|
|
ANIM_animdata_freelist(&anim_data);
|
|
}
|
|
|
|
static void common_draw_status_header(bContext *C, tGraphSliderOp *gso)
|
|
{
|
|
WorkspaceStatus status(C);
|
|
status.item(IFACE_("Confirm"), ICON_MOUSE_LMB);
|
|
status.item(IFACE_("Cancel"), ICON_EVENT_ESC);
|
|
status.item(IFACE_("Adjust"), ICON_MOUSE_MOVE);
|
|
if (hasNumInput(&gso->num)) {
|
|
char str_ofs[NUM_STR_REP_LEN];
|
|
outputNumInput(&gso->num, str_ofs, gso->scene->unit);
|
|
status.item(str_ofs, ICON_NONE);
|
|
}
|
|
else {
|
|
ED_slider_status_get(gso->slider, status);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Construct a list with the original bezt arrays so we can restore them during modal operation.
|
|
* The data is stored on the struct that is passed.
|
|
*/
|
|
static void store_original_bezt_arrays(tGraphSliderOp *gso)
|
|
{
|
|
ListBase anim_data = {nullptr, nullptr};
|
|
bAnimContext *ac = &gso->ac;
|
|
|
|
ANIM_animdata_filter(
|
|
ac, &anim_data, OPERATOR_DATA_FILTER, ac->data, eAnimCont_Types(ac->datatype));
|
|
|
|
/* Loop through filtered data and copy the curves. */
|
|
LISTBASE_FOREACH (bAnimListElem *, ale, &anim_data) {
|
|
const FCurve *fcu = (const FCurve *)ale->key_data;
|
|
|
|
if (fcu->bezt == nullptr) {
|
|
/* This curve is baked, skip it. */
|
|
continue;
|
|
}
|
|
|
|
tBeztCopyData *copy = MEM_mallocN<tBeztCopyData>("bezts_copy");
|
|
BezTriple *bezts_copy = MEM_malloc_arrayN<BezTriple>(fcu->totvert, "bezts_copy_array");
|
|
|
|
copy->tot_vert = fcu->totvert;
|
|
memcpy(bezts_copy, fcu->bezt, sizeof(BezTriple) * fcu->totvert);
|
|
|
|
copy->bezt = bezts_copy;
|
|
|
|
LinkData *link = nullptr;
|
|
|
|
link = MEM_callocN<LinkData>("Bezt Link");
|
|
link->data = copy;
|
|
|
|
BLI_addtail(&gso->bezt_arr_list, link);
|
|
}
|
|
|
|
ANIM_animdata_freelist(&anim_data);
|
|
}
|
|
|
|
/* Overwrite the current bezts arrays with the original data. */
|
|
static void reset_bezts(tGraphSliderOp *gso)
|
|
{
|
|
ListBase anim_data = {nullptr, nullptr};
|
|
LinkData *link_bezt;
|
|
bAnimListElem *ale;
|
|
|
|
bAnimContext *ac = &gso->ac;
|
|
|
|
/* Filter data. */
|
|
ANIM_animdata_filter(
|
|
ac, &anim_data, OPERATOR_DATA_FILTER, ac->data, eAnimCont_Types(ac->datatype));
|
|
|
|
/* Loop through filtered data and reset bezts. */
|
|
for (ale = static_cast<bAnimListElem *>(anim_data.first),
|
|
link_bezt = static_cast<LinkData *>(gso->bezt_arr_list.first);
|
|
ale;
|
|
ale = ale->next)
|
|
{
|
|
FCurve *fcu = (FCurve *)ale->key_data;
|
|
|
|
if (fcu->bezt == nullptr) {
|
|
/* This curve is baked, skip it. */
|
|
continue;
|
|
}
|
|
|
|
tBeztCopyData *data = static_cast<tBeztCopyData *>(link_bezt->data);
|
|
|
|
MEM_freeN(fcu->bezt);
|
|
|
|
fcu->bezt = MEM_malloc_arrayN<BezTriple>(data->tot_vert, __func__);
|
|
fcu->totvert = data->tot_vert;
|
|
|
|
memcpy(fcu->bezt, data->bezt, sizeof(BezTriple) * data->tot_vert);
|
|
|
|
link_bezt = link_bezt->next;
|
|
}
|
|
|
|
ANIM_animdata_freelist(&anim_data);
|
|
}
|
|
|
|
/**
|
|
* Get factor value and store it in RNA property.
|
|
* Custom data of #wmOperator needs to contain #tGraphSliderOp.
|
|
*/
|
|
static float slider_factor_get_and_remember(wmOperator *op)
|
|
{
|
|
tGraphSliderOp *gso = static_cast<tGraphSliderOp *>(op->customdata);
|
|
const float factor = ED_slider_factor_get(gso->slider);
|
|
RNA_property_float_set(op->ptr, gso->factor_prop, factor);
|
|
return factor;
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Common Modal Functions
|
|
* \{ */
|
|
|
|
static void graph_slider_exit(bContext *C, wmOperator *op)
|
|
{
|
|
tGraphSliderOp *gso = static_cast<tGraphSliderOp *>(op->customdata);
|
|
wmWindow *win = CTX_wm_window(C);
|
|
|
|
/* If data exists, clear its data and exit. */
|
|
if (gso == nullptr) {
|
|
return;
|
|
}
|
|
|
|
if (gso->free_operator_data != nullptr) {
|
|
gso->free_operator_data(gso->operator_data);
|
|
}
|
|
|
|
ScrArea *area = gso->area;
|
|
LinkData *link;
|
|
|
|
ED_slider_destroy(C, gso->slider);
|
|
|
|
for (link = static_cast<LinkData *>(gso->bezt_arr_list.first); link != nullptr;
|
|
link = link->next)
|
|
{
|
|
tBeztCopyData *copy = static_cast<tBeztCopyData *>(link->data);
|
|
MEM_freeN(copy->bezt);
|
|
MEM_freeN(link->data);
|
|
}
|
|
|
|
BLI_freelistN(&gso->bezt_arr_list);
|
|
MEM_freeN(gso);
|
|
|
|
/* Return to normal cursor and header status. */
|
|
WM_cursor_modal_restore(win);
|
|
ED_area_status_text(area, nullptr);
|
|
|
|
/* cleanup */
|
|
op->customdata = nullptr;
|
|
}
|
|
|
|
static void update_depsgraph(tGraphSliderOp *gso)
|
|
{
|
|
ListBase anim_data = {nullptr, nullptr};
|
|
|
|
bAnimContext *ac = &gso->ac;
|
|
ANIM_animdata_filter(
|
|
ac, &anim_data, OPERATOR_DATA_FILTER, ac->data, eAnimCont_Types(ac->datatype));
|
|
LISTBASE_FOREACH (bAnimListElem *, ale, &anim_data) {
|
|
DEG_id_tag_update(ale->fcurve_owner_id, ID_RECALC_ANIMATION);
|
|
}
|
|
|
|
ANIM_animdata_freelist(&anim_data);
|
|
}
|
|
|
|
static wmOperatorStatus graph_slider_modal(bContext *C, wmOperator *op, const wmEvent *event)
|
|
{
|
|
tGraphSliderOp *gso = static_cast<tGraphSliderOp *>(op->customdata);
|
|
|
|
const bool has_numinput = hasNumInput(&gso->num);
|
|
|
|
ED_slider_property_label_set(gso->slider,
|
|
fmt::format("{} ({})",
|
|
WM_operatortype_name(op->type, op->ptr),
|
|
RNA_property_ui_name(gso->factor_prop))
|
|
.c_str());
|
|
|
|
ED_slider_modal(gso->slider, event);
|
|
|
|
switch (event->type) {
|
|
/* Confirm */
|
|
case LEFTMOUSE:
|
|
case EVT_RETKEY:
|
|
case EVT_PADENTER: {
|
|
if (event->val == KM_PRESS) {
|
|
graph_slider_exit(C, op);
|
|
|
|
return OPERATOR_FINISHED;
|
|
}
|
|
break;
|
|
}
|
|
|
|
/* Cancel */
|
|
case EVT_ESCKEY:
|
|
case RIGHTMOUSE: {
|
|
if (event->val == KM_PRESS) {
|
|
reset_bezts(gso);
|
|
|
|
/* The owner id's of the FCurves need to be updated, else the animation will be stuck in
|
|
* the state prior to calling reset_bezt. */
|
|
update_depsgraph(gso);
|
|
|
|
WM_event_add_notifier(C, NC_ANIMATION | ND_KEYFRAME | NA_EDITED, nullptr);
|
|
|
|
graph_slider_exit(C, op);
|
|
|
|
return OPERATOR_CANCELLED;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case EVT_TABKEY:
|
|
/* Switch between acting on different properties. If this is not handled
|
|
* by the caller, it's explicitly gobbled up here to avoid it being passed
|
|
* through via the 'default' case. */
|
|
break;
|
|
|
|
/* When the mouse is moved, the percentage and the keyframes update. */
|
|
case MOUSEMOVE: {
|
|
if (has_numinput == false) {
|
|
/* Do the update as specified by the operator. */
|
|
gso->modal_update(C, op);
|
|
}
|
|
break;
|
|
}
|
|
default: {
|
|
if ((event->val == KM_PRESS) || (ISKEYMODIFIER(event->type) && event->val == KM_RELEASE)) {
|
|
|
|
if (handleNumInput(C, &gso->num, event)) {
|
|
float value;
|
|
applyNumInput(&gso->num, &value);
|
|
|
|
/* Grab percentage from numeric input, and store this new value for redo
|
|
* NOTE: users see ints, while internally we use a 0-1 float. */
|
|
if (ED_slider_mode_get(gso->slider) == SLIDER_MODE_PERCENT) {
|
|
value = value / 100.0f;
|
|
}
|
|
ED_slider_factor_set(gso->slider, value);
|
|
RNA_property_float_set(op->ptr, gso->factor_prop, value);
|
|
}
|
|
|
|
gso->modal_update(C, op);
|
|
break;
|
|
}
|
|
|
|
/* Unhandled event - maybe it was some view manipulation? */
|
|
/* Allow to pass through. */
|
|
return OPERATOR_RUNNING_MODAL | OPERATOR_PASS_THROUGH;
|
|
}
|
|
}
|
|
|
|
return OPERATOR_RUNNING_MODAL;
|
|
}
|
|
|
|
/* Allocate tGraphSliderOp and assign to op->customdata. */
|
|
static wmOperatorStatus graph_slider_invoke(bContext *C, wmOperator *op, const wmEvent *event)
|
|
{
|
|
tGraphSliderOp *gso;
|
|
|
|
WM_cursor_modal_set(CTX_wm_window(C), WM_CURSOR_EW_SCROLL);
|
|
|
|
/* Init slide-op data. */
|
|
gso = static_cast<tGraphSliderOp *>(
|
|
op->customdata = MEM_callocN(sizeof(tGraphSliderOp), "tGraphSliderOp"));
|
|
|
|
/* Get editor data. */
|
|
if (ANIM_animdata_get_context(C, &gso->ac) == 0) {
|
|
graph_slider_exit(C, op);
|
|
return OPERATOR_CANCELLED;
|
|
}
|
|
gso->ac.reports = op->reports;
|
|
|
|
gso->scene = CTX_data_scene(C);
|
|
gso->area = CTX_wm_area(C);
|
|
gso->region = CTX_wm_region(C);
|
|
|
|
store_original_bezt_arrays(gso);
|
|
|
|
gso->slider = ED_slider_create(C);
|
|
ED_slider_init(gso->slider, event);
|
|
|
|
if (gso->bezt_arr_list.first == nullptr) {
|
|
BKE_report(op->reports, RPT_ERROR, "Cannot find keys to operate on");
|
|
graph_slider_exit(C, op);
|
|
return OPERATOR_CANCELLED;
|
|
}
|
|
|
|
WM_event_add_modal_handler(C, op);
|
|
return OPERATOR_RUNNING_MODAL;
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Decimate Keyframes Operator
|
|
* \{ */
|
|
|
|
enum tDecimModes {
|
|
DECIM_RATIO = 1,
|
|
DECIM_ERROR,
|
|
};
|
|
|
|
static void decimate_graph_keys(bAnimContext *ac, float factor, float error_sq_max)
|
|
{
|
|
ListBase anim_data = {nullptr, nullptr};
|
|
|
|
/* Filter data. */
|
|
ANIM_animdata_filter(
|
|
ac, &anim_data, OPERATOR_DATA_FILTER, ac->data, eAnimCont_Types(ac->datatype));
|
|
|
|
/* Loop through filtered data and clean curves. */
|
|
LISTBASE_FOREACH (bAnimListElem *, ale, &anim_data) {
|
|
if (!decimate_fcurve(ale, factor, error_sq_max)) {
|
|
/* The selection contains unsupported keyframe types! */
|
|
BKE_report(ac->reports, RPT_WARNING, "Decimate: Skipping non linear/Bézier keyframes!");
|
|
}
|
|
|
|
ale->update |= ANIM_UPDATE_DEFAULT;
|
|
}
|
|
|
|
ANIM_animdata_update(ac, &anim_data);
|
|
ANIM_animdata_freelist(&anim_data);
|
|
}
|
|
|
|
/* Draw a percentage indicator in workspace footer. */
|
|
static void decimate_draw_status(bContext *C, tGraphSliderOp *gso)
|
|
{
|
|
WorkspaceStatus status(C);
|
|
status.item(IFACE_("Confirm"), ICON_MOUSE_LMB);
|
|
status.item(IFACE_("Cancel"), ICON_EVENT_ESC);
|
|
status.item(IFACE_("Adjust"), ICON_MOUSE_MOVE);
|
|
if (hasNumInput(&gso->num)) {
|
|
char str_ofs[NUM_STR_REP_LEN];
|
|
outputNumInput(&gso->num, str_ofs, gso->scene->unit);
|
|
status.item(str_ofs, ICON_NONE);
|
|
}
|
|
else {
|
|
ED_slider_status_get(gso->slider, status);
|
|
}
|
|
}
|
|
|
|
static void 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). */
|
|
tGraphSliderOp *gso = static_cast<tGraphSliderOp *>(op->customdata);
|
|
|
|
decimate_draw_status(C, gso);
|
|
|
|
/* Reset keyframe data (so we get back to the original state). */
|
|
reset_bezts(gso);
|
|
|
|
/* Apply... */
|
|
const float factor = slider_factor_get_and_remember(op);
|
|
/* We don't want to limit the decimation to a certain error margin. */
|
|
const float error_sq_max = FLT_MAX;
|
|
decimate_graph_keys(&gso->ac, factor, error_sq_max);
|
|
WM_event_add_notifier(C, NC_ANIMATION | ND_KEYFRAME | NA_EDITED, nullptr);
|
|
}
|
|
|
|
static wmOperatorStatus decimate_invoke(bContext *C, wmOperator *op, const wmEvent *event)
|
|
{
|
|
const wmOperatorStatus invoke_result = graph_slider_invoke(C, op, event);
|
|
|
|
if (invoke_result == OPERATOR_CANCELLED) {
|
|
return OPERATOR_CANCELLED;
|
|
}
|
|
|
|
tGraphSliderOp *gso = static_cast<tGraphSliderOp *>(op->customdata);
|
|
gso->factor_prop = RNA_struct_find_property(op->ptr, "factor");
|
|
gso->modal_update = decimate_modal_update;
|
|
ED_slider_allow_overshoot_set(gso->slider, false, false);
|
|
|
|
return invoke_result;
|
|
}
|
|
|
|
static wmOperatorStatus decimate_exec(bContext *C, wmOperator *op)
|
|
{
|
|
bAnimContext ac;
|
|
|
|
/* Get editor data. */
|
|
if (ANIM_animdata_get_context(C, &ac) == 0) {
|
|
return OPERATOR_CANCELLED;
|
|
}
|
|
|
|
tDecimModes mode = tDecimModes(RNA_enum_get(op->ptr, "mode"));
|
|
/* We want to be able to work on all available keyframes. */
|
|
float factor = 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:
|
|
factor = RNA_float_get(op->ptr, "factor");
|
|
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 (factor == 0.0f || error_sq_max == 0.0f) {
|
|
/* Nothing to remove. */
|
|
return OPERATOR_FINISHED;
|
|
}
|
|
|
|
decimate_graph_keys(&ac, factor, error_sq_max);
|
|
|
|
/* Set notifier that keyframes have changed. */
|
|
WM_event_add_notifier(C, NC_ANIMATION | ND_KEYFRAME | NA_EDITED, nullptr);
|
|
|
|
return OPERATOR_FINISHED;
|
|
}
|
|
|
|
static bool decimate_poll_property(const bContext * /*C*/, wmOperator *op, const PropertyRNA *prop)
|
|
{
|
|
const char *prop_id = RNA_property_identifier(prop);
|
|
const int mode = RNA_enum_get(op->ptr, "mode");
|
|
|
|
if (STREQ(prop_id, "factor") && mode != DECIM_RATIO) {
|
|
return false;
|
|
}
|
|
if (STREQ(prop_id, "remove_error_margin") && mode != DECIM_ERROR) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static std::string decimate_get_description(bContext * /*C*/,
|
|
wmOperatorType * /*ot*/,
|
|
PointerRNA *ptr)
|
|
{
|
|
|
|
if (RNA_enum_get(ptr, "mode") == DECIM_ERROR) {
|
|
return TIP_(
|
|
"Decimate F-Curves by specifying how much they can deviate from the original curve");
|
|
}
|
|
|
|
/* Use default description. */
|
|
return "";
|
|
}
|
|
|
|
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, nullptr, 0, nullptr, nullptr},
|
|
};
|
|
|
|
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 = decimate_poll_property;
|
|
ot->get_description = decimate_get_description;
|
|
ot->invoke = decimate_invoke;
|
|
ot->modal = graph_slider_modal;
|
|
ot->exec = 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_factor(ot->srna,
|
|
"factor",
|
|
1.0f / 3.0f,
|
|
0.0f,
|
|
1.0f,
|
|
"Factor",
|
|
"The ratio 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);
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Blend to Neighbor Operator
|
|
* \{ */
|
|
|
|
static void blend_to_neighbor_graph_keys(bAnimContext *ac, const float factor)
|
|
{
|
|
apply_fcu_segment_function(ac, factor, blend_to_neighbor_fcurve_segment);
|
|
}
|
|
|
|
static void blend_to_neighbor_modal_update(bContext *C, wmOperator *op)
|
|
{
|
|
tGraphSliderOp *gso = static_cast<tGraphSliderOp *>(op->customdata);
|
|
|
|
common_draw_status_header(C, gso);
|
|
|
|
/* Reset keyframe data to the state at invoke. */
|
|
reset_bezts(gso);
|
|
|
|
const float factor = slider_factor_get_and_remember(op);
|
|
blend_to_neighbor_graph_keys(&gso->ac, factor);
|
|
|
|
WM_event_add_notifier(C, NC_ANIMATION | ND_KEYFRAME | NA_EDITED, nullptr);
|
|
}
|
|
|
|
static wmOperatorStatus blend_to_neighbor_invoke(bContext *C, wmOperator *op, const wmEvent *event)
|
|
{
|
|
const wmOperatorStatus invoke_result = graph_slider_invoke(C, op, event);
|
|
|
|
if (invoke_result == OPERATOR_CANCELLED) {
|
|
return invoke_result;
|
|
}
|
|
|
|
tGraphSliderOp *gso = static_cast<tGraphSliderOp *>(op->customdata);
|
|
gso->modal_update = blend_to_neighbor_modal_update;
|
|
gso->factor_prop = RNA_struct_find_property(op->ptr, "factor");
|
|
common_draw_status_header(C, gso);
|
|
ED_slider_factor_bounds_set(gso->slider, -1, 1);
|
|
ED_slider_factor_set(gso->slider, 0.0f);
|
|
|
|
return invoke_result;
|
|
}
|
|
|
|
static wmOperatorStatus blend_to_neighbor_exec(bContext *C, wmOperator *op)
|
|
{
|
|
bAnimContext ac;
|
|
|
|
if (ANIM_animdata_get_context(C, &ac) == 0) {
|
|
return OPERATOR_CANCELLED;
|
|
}
|
|
|
|
const float factor = RNA_float_get(op->ptr, "factor");
|
|
|
|
blend_to_neighbor_graph_keys(&ac, factor);
|
|
|
|
/* Set notifier that keyframes have changed. */
|
|
WM_event_add_notifier(C, NC_ANIMATION | ND_KEYFRAME | NA_EDITED, nullptr);
|
|
|
|
return OPERATOR_FINISHED;
|
|
}
|
|
|
|
void GRAPH_OT_blend_to_neighbor(wmOperatorType *ot)
|
|
{
|
|
/* Identifiers. */
|
|
ot->name = "Blend to Neighbor";
|
|
ot->idname = "GRAPH_OT_blend_to_neighbor";
|
|
ot->description = "Blend selected keyframes to their left or right neighbor";
|
|
|
|
/* API callbacks. */
|
|
ot->invoke = blend_to_neighbor_invoke;
|
|
ot->modal = graph_slider_modal;
|
|
ot->exec = blend_to_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,
|
|
"Blend",
|
|
"The blend factor with 0 being the current frame",
|
|
-1.0f,
|
|
1.0f);
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Breakdown Operator
|
|
* \{ */
|
|
|
|
static void breakdown_graph_keys(bAnimContext *ac, float factor)
|
|
{
|
|
apply_fcu_segment_function(ac, factor, breakdown_fcurve_segment);
|
|
}
|
|
|
|
static void breakdown_modal_update(bContext *C, wmOperator *op)
|
|
{
|
|
tGraphSliderOp *gso = static_cast<tGraphSliderOp *>(op->customdata);
|
|
|
|
common_draw_status_header(C, gso);
|
|
|
|
/* Reset keyframe data to the state at invoke. */
|
|
reset_bezts(gso);
|
|
const float factor = slider_factor_get_and_remember(op);
|
|
breakdown_graph_keys(&gso->ac, factor);
|
|
WM_event_add_notifier(C, NC_ANIMATION | ND_KEYFRAME | NA_EDITED, nullptr);
|
|
}
|
|
|
|
static wmOperatorStatus breakdown_invoke(bContext *C, wmOperator *op, const wmEvent *event)
|
|
{
|
|
const wmOperatorStatus invoke_result = graph_slider_invoke(C, op, event);
|
|
|
|
if (invoke_result == OPERATOR_CANCELLED) {
|
|
return invoke_result;
|
|
}
|
|
|
|
tGraphSliderOp *gso = static_cast<tGraphSliderOp *>(op->customdata);
|
|
gso->modal_update = breakdown_modal_update;
|
|
gso->factor_prop = RNA_struct_find_property(op->ptr, "factor");
|
|
common_draw_status_header(C, gso);
|
|
ED_slider_factor_bounds_set(gso->slider, -1, 1);
|
|
ED_slider_factor_set(gso->slider, 0.0f);
|
|
|
|
return invoke_result;
|
|
}
|
|
|
|
static wmOperatorStatus breakdown_exec(bContext *C, wmOperator *op)
|
|
{
|
|
bAnimContext ac;
|
|
|
|
if (ANIM_animdata_get_context(C, &ac) == 0) {
|
|
return OPERATOR_CANCELLED;
|
|
}
|
|
|
|
const float factor = RNA_float_get(op->ptr, "factor");
|
|
|
|
breakdown_graph_keys(&ac, factor);
|
|
|
|
/* Set notifier that keyframes have changed. */
|
|
WM_event_add_notifier(C, NC_ANIMATION | ND_KEYFRAME | NA_EDITED, nullptr);
|
|
|
|
return OPERATOR_FINISHED;
|
|
}
|
|
|
|
void GRAPH_OT_breakdown(wmOperatorType *ot)
|
|
{
|
|
/* Identifiers. */
|
|
ot->name = "Breakdown";
|
|
ot->idname = "GRAPH_OT_breakdown";
|
|
ot->description = "Move selected keyframes to an inbetween position relative to adjacent keys";
|
|
|
|
/* API callbacks. */
|
|
ot->invoke = breakdown_invoke;
|
|
ot->modal = graph_slider_modal;
|
|
ot->exec = breakdown_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",
|
|
"Favor either the left or the right key",
|
|
-1.0f,
|
|
1.0f);
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Blend to Default Value Operator
|
|
* \{ */
|
|
|
|
static void blend_to_default_graph_keys(bAnimContext *ac, const float factor)
|
|
{
|
|
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;
|
|
|
|
/* Check if the curves actually have any points. */
|
|
if (fcu == nullptr || fcu->bezt == nullptr || fcu->totvert == 0) {
|
|
continue;
|
|
}
|
|
|
|
PointerRNA id_ptr = RNA_id_pointer_create(ale->id);
|
|
|
|
blend_to_default_fcurve(&id_ptr, fcu, factor);
|
|
ale->update |= ANIM_UPDATE_DEFAULT;
|
|
}
|
|
|
|
ANIM_animdata_update(ac, &anim_data);
|
|
ANIM_animdata_freelist(&anim_data);
|
|
}
|
|
|
|
static void blend_to_default_modal_update(bContext *C, wmOperator *op)
|
|
{
|
|
tGraphSliderOp *gso = static_cast<tGraphSliderOp *>(op->customdata);
|
|
|
|
common_draw_status_header(C, gso);
|
|
|
|
/* Set notifier that keyframes have changed. */
|
|
reset_bezts(gso);
|
|
const float factor = ED_slider_factor_get(gso->slider);
|
|
RNA_property_float_set(op->ptr, gso->factor_prop, factor);
|
|
blend_to_default_graph_keys(&gso->ac, factor);
|
|
WM_event_add_notifier(C, NC_ANIMATION | ND_KEYFRAME | NA_EDITED, nullptr);
|
|
}
|
|
|
|
static wmOperatorStatus blend_to_default_invoke(bContext *C, wmOperator *op, const wmEvent *event)
|
|
{
|
|
const wmOperatorStatus invoke_result = graph_slider_invoke(C, op, event);
|
|
|
|
if (invoke_result == OPERATOR_CANCELLED) {
|
|
return invoke_result;
|
|
}
|
|
|
|
tGraphSliderOp *gso = static_cast<tGraphSliderOp *>(op->customdata);
|
|
gso->modal_update = blend_to_default_modal_update;
|
|
gso->factor_prop = RNA_struct_find_property(op->ptr, "factor");
|
|
common_draw_status_header(C, gso);
|
|
ED_slider_factor_set(gso->slider, 0.0f);
|
|
|
|
return invoke_result;
|
|
}
|
|
|
|
static wmOperatorStatus blend_to_default_exec(bContext *C, wmOperator *op)
|
|
{
|
|
bAnimContext ac;
|
|
|
|
if (ANIM_animdata_get_context(C, &ac) == 0) {
|
|
return OPERATOR_CANCELLED;
|
|
}
|
|
|
|
const float factor = RNA_float_get(op->ptr, "factor");
|
|
|
|
blend_to_default_graph_keys(&ac, factor);
|
|
|
|
/* Set notifier that keyframes have changed. */
|
|
WM_event_add_notifier(C, NC_ANIMATION | ND_KEYFRAME | NA_EDITED, nullptr);
|
|
|
|
return OPERATOR_FINISHED;
|
|
}
|
|
|
|
void GRAPH_OT_blend_to_default(wmOperatorType *ot)
|
|
{
|
|
/* Identifiers. */
|
|
ot->name = "Blend to Default Value";
|
|
ot->idname = "GRAPH_OT_blend_to_default";
|
|
ot->description = "Blend selected keys to their default value from their current position";
|
|
|
|
/* API callbacks. */
|
|
ot->invoke = blend_to_default_invoke;
|
|
ot->modal = graph_slider_modal;
|
|
ot->exec = blend_to_default_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",
|
|
"How much to blend to the default value",
|
|
0.0f,
|
|
1.0f);
|
|
}
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Ease Operator
|
|
* \{ */
|
|
|
|
static void ease_graph_keys(bAnimContext *ac, const float factor, const float width)
|
|
{
|
|
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) {
|
|
ease_fcurve_segment(fcu, segment, factor, width);
|
|
}
|
|
|
|
ale->update |= ANIM_UPDATE_DEFAULT;
|
|
BLI_freelistN(&segments);
|
|
}
|
|
|
|
ANIM_animdata_update(ac, &anim_data);
|
|
ANIM_animdata_freelist(&anim_data);
|
|
}
|
|
|
|
static void ease_draw_status_header(bContext *C, wmOperator *op)
|
|
{
|
|
tGraphSliderOp *gso = static_cast<tGraphSliderOp *>(op->customdata);
|
|
WorkspaceStatus status(C);
|
|
status.item(IFACE_("Confirm"), ICON_MOUSE_LMB);
|
|
status.item(IFACE_("Cancel"), ICON_EVENT_ESC);
|
|
status.item(IFACE_("Adjust"), ICON_MOUSE_MOVE);
|
|
if (hasNumInput(&gso->num)) {
|
|
char str_ofs[NUM_STR_REP_LEN];
|
|
outputNumInput(&gso->num, str_ofs, gso->scene->unit);
|
|
status.item(str_ofs, ICON_NONE);
|
|
}
|
|
else {
|
|
ED_slider_status_get(gso->slider, status);
|
|
/* Operator specific functionality that extends beyond the slider. */
|
|
if (STREQ(RNA_property_identifier(gso->factor_prop), "factor")) {
|
|
status.item(IFACE_("Modify Sharpness"), ICON_EVENT_TAB);
|
|
}
|
|
else {
|
|
status.item(IFACE_("Modify Curve Bend"), ICON_EVENT_TAB);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void ease_modal_update(bContext *C, wmOperator *op)
|
|
{
|
|
tGraphSliderOp *gso = static_cast<tGraphSliderOp *>(op->customdata);
|
|
|
|
ease_draw_status_header(C, op);
|
|
|
|
/* Reset keyframes to the state at invoke. */
|
|
reset_bezts(gso);
|
|
float factor;
|
|
float width;
|
|
if (STREQ(RNA_property_identifier(gso->factor_prop), "factor")) {
|
|
factor = slider_factor_get_and_remember(op);
|
|
width = RNA_float_get(op->ptr, "sharpness");
|
|
}
|
|
else {
|
|
factor = RNA_float_get(op->ptr, "factor");
|
|
width = slider_factor_get_and_remember(op);
|
|
}
|
|
|
|
ease_graph_keys(&gso->ac, factor, width);
|
|
WM_event_add_notifier(C, NC_ANIMATION | ND_KEYFRAME | NA_EDITED, nullptr);
|
|
}
|
|
|
|
static wmOperatorStatus ease_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_TABKEY: {
|
|
tGraphSliderOp *gso = static_cast<tGraphSliderOp *>(op->customdata);
|
|
if (STREQ(RNA_property_identifier(gso->factor_prop), "factor")) {
|
|
/* Switch to sharpness. */
|
|
ED_slider_allow_overshoot_set(gso->slider, false, true);
|
|
ED_slider_factor_bounds_set(gso->slider, 0.001f, 10);
|
|
ED_slider_factor_set(gso->slider, RNA_float_get(op->ptr, "sharpness"));
|
|
ED_slider_mode_set(gso->slider, SLIDER_MODE_FLOAT);
|
|
ED_slider_unit_set(gso->slider, "");
|
|
gso->factor_prop = RNA_struct_find_property(op->ptr, "sharpness");
|
|
}
|
|
else {
|
|
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);
|
|
ED_slider_factor_set(gso->slider, RNA_float_get(op->ptr, "factor"));
|
|
ED_slider_mode_set(gso->slider, SLIDER_MODE_PERCENT);
|
|
ED_slider_unit_set(gso->slider, "%");
|
|
gso->factor_prop = RNA_struct_find_property(op->ptr, "factor");
|
|
}
|
|
ease_modal_update(C, op);
|
|
break;
|
|
}
|
|
|
|
default:
|
|
return graph_slider_modal(C, op, event);
|
|
}
|
|
return OPERATOR_RUNNING_MODAL;
|
|
}
|
|
|
|
static wmOperatorStatus ease_invoke(bContext *C, wmOperator *op, const wmEvent *event)
|
|
{
|
|
const wmOperatorStatus invoke_result = graph_slider_invoke(C, op, event);
|
|
|
|
if (invoke_result == OPERATOR_CANCELLED) {
|
|
return invoke_result;
|
|
}
|
|
|
|
tGraphSliderOp *gso = static_cast<tGraphSliderOp *>(op->customdata);
|
|
gso->modal_update = ease_modal_update;
|
|
gso->factor_prop = RNA_struct_find_property(op->ptr, "factor");
|
|
ease_draw_status_header(C, op);
|
|
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);
|
|
ED_slider_property_label_set(gso->slider, RNA_property_ui_name(gso->factor_prop));
|
|
|
|
return invoke_result;
|
|
}
|
|
|
|
static wmOperatorStatus ease_exec(bContext *C, wmOperator *op)
|
|
{
|
|
bAnimContext ac;
|
|
|
|
if (ANIM_animdata_get_context(C, &ac) == 0) {
|
|
return OPERATOR_CANCELLED;
|
|
}
|
|
|
|
const float factor = RNA_float_get(op->ptr, "factor");
|
|
const float width = RNA_float_get(op->ptr, "sharpness");
|
|
|
|
ease_graph_keys(&ac, factor, width);
|
|
|
|
WM_event_add_notifier(C, NC_ANIMATION | ND_KEYFRAME | NA_EDITED, nullptr);
|
|
|
|
return OPERATOR_FINISHED;
|
|
}
|
|
|
|
void GRAPH_OT_ease(wmOperatorType *ot)
|
|
{
|
|
/* Identifiers. */
|
|
ot->name = "Ease Keyframes";
|
|
ot->idname = "GRAPH_OT_ease";
|
|
ot->description = "Align keyframes on a ease-in or ease-out curve";
|
|
|
|
/* API callbacks. */
|
|
ot->invoke = ease_invoke;
|
|
ot->modal = ease_modal;
|
|
ot->exec = ease_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,
|
|
"Curve Bend",
|
|
"Defines if the keys should be aligned on an ease-in or ease-out curve",
|
|
-1.0f,
|
|
1.0f);
|
|
|
|
RNA_def_float(ot->srna,
|
|
"sharpness",
|
|
2.0f,
|
|
0.001f,
|
|
FLT_MAX,
|
|
"Sharpness",
|
|
"Higher values make the change more abrupt",
|
|
0.01f,
|
|
16.0f);
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Blend Offset Operator
|
|
* \{ */
|
|
|
|
static void blend_offset_graph_keys(bAnimContext *ac, const float factor)
|
|
{
|
|
apply_fcu_segment_function(ac, factor, blend_offset_fcurve_segment);
|
|
}
|
|
|
|
static void blend_offset_draw_status_header(bContext *C, tGraphSliderOp *gso)
|
|
{
|
|
common_draw_status_header(C, gso);
|
|
}
|
|
|
|
static void blend_offset_modal_update(bContext *C, wmOperator *op)
|
|
{
|
|
tGraphSliderOp *gso = static_cast<tGraphSliderOp *>(op->customdata);
|
|
|
|
blend_offset_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_offset_graph_keys(&gso->ac, factor);
|
|
WM_event_add_notifier(C, NC_ANIMATION | ND_KEYFRAME | NA_EDITED, nullptr);
|
|
}
|
|
|
|
static wmOperatorStatus blend_offset_invoke(bContext *C, wmOperator *op, const wmEvent *event)
|
|
{
|
|
const wmOperatorStatus invoke_result = graph_slider_invoke(C, op, event);
|
|
|
|
if (invoke_result == OPERATOR_CANCELLED) {
|
|
return invoke_result;
|
|
}
|
|
|
|
tGraphSliderOp *gso = static_cast<tGraphSliderOp *>(op->customdata);
|
|
gso->modal_update = blend_offset_modal_update;
|
|
gso->factor_prop = RNA_struct_find_property(op->ptr, "factor");
|
|
blend_offset_draw_status_header(C, gso);
|
|
ED_slider_factor_bounds_set(gso->slider, -1, 1);
|
|
ED_slider_factor_set(gso->slider, 0.0f);
|
|
|
|
return invoke_result;
|
|
}
|
|
|
|
static wmOperatorStatus blend_offset_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_offset_graph_keys(&ac, factor);
|
|
|
|
/* Set notifier that keyframes have changed. */
|
|
WM_event_add_notifier(C, NC_ANIMATION | ND_KEYFRAME | NA_EDITED, nullptr);
|
|
|
|
return OPERATOR_FINISHED;
|
|
}
|
|
|
|
void GRAPH_OT_blend_offset(wmOperatorType *ot)
|
|
{
|
|
/* Identifiers. */
|
|
ot->name = "Blend Offset Keyframes";
|
|
ot->idname = "GRAPH_OT_blend_offset";
|
|
ot->description = "Shift selected keys to the value of the neighboring keys as a block";
|
|
|
|
/* API callbacks. */
|
|
ot->invoke = blend_offset_invoke;
|
|
ot->modal = graph_slider_modal;
|
|
ot->exec = blend_offset_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,
|
|
"Offset Factor",
|
|
"Control which key to offset towards and how far",
|
|
-1.0f,
|
|
1.0f);
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Blend to Ease Operator
|
|
* \{ */
|
|
|
|
static void blend_to_ease_graph_keys(bAnimContext *ac, const float factor)
|
|
{
|
|
apply_fcu_segment_function(ac, factor, blend_to_ease_fcurve_segment);
|
|
}
|
|
|
|
static void blend_to_ease_draw_status_header(bContext *C, tGraphSliderOp *gso)
|
|
{
|
|
common_draw_status_header(C, gso);
|
|
}
|
|
|
|
static void blend_to_ease_modal_update(bContext *C, wmOperator *op)
|
|
{
|
|
tGraphSliderOp *gso = static_cast<tGraphSliderOp *>(op->customdata);
|
|
|
|
blend_to_ease_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_ease_graph_keys(&gso->ac, factor);
|
|
WM_event_add_notifier(C, NC_ANIMATION | ND_KEYFRAME | NA_EDITED, nullptr);
|
|
}
|
|
|
|
static wmOperatorStatus blend_to_ease_invoke(bContext *C, wmOperator *op, const wmEvent *event)
|
|
{
|
|
const wmOperatorStatus invoke_result = graph_slider_invoke(C, op, event);
|
|
|
|
if (invoke_result == OPERATOR_CANCELLED) {
|
|
return invoke_result;
|
|
}
|
|
|
|
tGraphSliderOp *gso = static_cast<tGraphSliderOp *>(op->customdata);
|
|
gso->modal_update = blend_to_ease_modal_update;
|
|
gso->factor_prop = RNA_struct_find_property(op->ptr, "factor");
|
|
blend_to_ease_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 wmOperatorStatus blend_to_ease_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_ease_graph_keys(&ac, factor);
|
|
|
|
/* Set notifier that keyframes have changed. */
|
|
WM_event_add_notifier(C, NC_ANIMATION | ND_KEYFRAME | NA_EDITED, nullptr);
|
|
|
|
return OPERATOR_FINISHED;
|
|
}
|
|
|
|
void GRAPH_OT_blend_to_ease(wmOperatorType *ot)
|
|
{
|
|
/* Identifiers. */
|
|
ot->name = "Blend to Ease Keyframes";
|
|
ot->idname = "GRAPH_OT_blend_to_ease";
|
|
ot->description = "Blends keyframes from current state to an ease-in or ease-out curve";
|
|
|
|
/* API callbacks. */
|
|
ot->invoke = blend_to_ease_invoke;
|
|
ot->modal = graph_slider_modal;
|
|
ot->exec = blend_to_ease_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,
|
|
"Blend",
|
|
"Favor either original data or ease curve",
|
|
-1.0f,
|
|
1.0f);
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Match Slope
|
|
* \{ */
|
|
|
|
static void match_slope_graph_keys(bAnimContext *ac, const float factor)
|
|
{
|
|
ListBase anim_data = {nullptr, nullptr};
|
|
|
|
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) {
|
|
BKE_report(
|
|
ac->reports, RPT_WARNING, "You need at least 2 keys to the right side of the selection");
|
|
}
|
|
else {
|
|
BKE_report(
|
|
ac->reports, 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);
|
|
}
|
|
|
|
static void match_slope_modal_update(bContext *C, wmOperator *op)
|
|
{
|
|
tGraphSliderOp *gso = static_cast<tGraphSliderOp *>(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, nullptr);
|
|
}
|
|
|
|
static wmOperatorStatus match_slope_invoke(bContext *C, wmOperator *op, const wmEvent *event)
|
|
{
|
|
const wmOperatorStatus invoke_result = graph_slider_invoke(C, op, event);
|
|
|
|
if (invoke_result == OPERATOR_CANCELLED) {
|
|
return invoke_result;
|
|
}
|
|
|
|
tGraphSliderOp *gso = static_cast<tGraphSliderOp *>(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 wmOperatorStatus match_slope_exec(bContext *C, wmOperator *op)
|
|
{
|
|
bAnimContext ac;
|
|
|
|
/* Get editor data. */
|
|
if (ANIM_animdata_get_context(C, &ac) == 0) {
|
|
return OPERATOR_CANCELLED;
|
|
}
|
|
ac.reports = op->reports;
|
|
|
|
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, nullptr);
|
|
|
|
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 Time Offset
|
|
* \{ */
|
|
|
|
static void time_offset_graph_keys(bAnimContext *ac, const float factor)
|
|
{
|
|
apply_fcu_segment_function(ac, factor, time_offset_fcurve_segment);
|
|
}
|
|
|
|
static void time_offset_draw_status_header(bContext *C, tGraphSliderOp *gso)
|
|
{
|
|
common_draw_status_header(C, gso);
|
|
}
|
|
|
|
static void time_offset_modal_update(bContext *C, wmOperator *op)
|
|
{
|
|
tGraphSliderOp *gso = static_cast<tGraphSliderOp *>(op->customdata);
|
|
|
|
time_offset_draw_status_header(C, gso);
|
|
|
|
/* Reset keyframes to the state at invoke. */
|
|
reset_bezts(gso);
|
|
const float factor = slider_factor_get_and_remember(op);
|
|
time_offset_graph_keys(&gso->ac, factor);
|
|
WM_event_add_notifier(C, NC_ANIMATION | ND_KEYFRAME | NA_EDITED, nullptr);
|
|
}
|
|
|
|
static wmOperatorStatus time_offset_invoke(bContext *C, wmOperator *op, const wmEvent *event)
|
|
{
|
|
const wmOperatorStatus invoke_result = graph_slider_invoke(C, op, event);
|
|
|
|
if (invoke_result == OPERATOR_CANCELLED) {
|
|
return invoke_result;
|
|
}
|
|
|
|
tGraphSliderOp *gso = static_cast<tGraphSliderOp *>(op->customdata);
|
|
gso->modal_update = time_offset_modal_update;
|
|
gso->factor_prop = RNA_struct_find_property(op->ptr, "frame_offset");
|
|
time_offset_draw_status_header(C, gso);
|
|
ED_slider_factor_bounds_set(gso->slider, -10, 10);
|
|
ED_slider_increment_step_set(gso->slider, 1);
|
|
ED_slider_factor_set(gso->slider, 0.0f);
|
|
ED_slider_mode_set(gso->slider, SLIDER_MODE_FLOAT);
|
|
ED_slider_unit_set(gso->slider, "Frames");
|
|
|
|
return invoke_result;
|
|
}
|
|
|
|
static wmOperatorStatus time_offset_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, "frame_offset");
|
|
|
|
time_offset_graph_keys(&ac, factor);
|
|
|
|
/* Set notifier that keyframes have changed. */
|
|
WM_event_add_notifier(C, NC_ANIMATION | ND_KEYFRAME | NA_EDITED, nullptr);
|
|
|
|
return OPERATOR_FINISHED;
|
|
}
|
|
|
|
void GRAPH_OT_time_offset(wmOperatorType *ot)
|
|
{
|
|
/* Identifiers. */
|
|
ot->name = "Time Offset Keyframes";
|
|
ot->idname = "GRAPH_OT_time_offset";
|
|
ot->description = "Shifts the value of selected keys in time";
|
|
|
|
/* API callbacks. */
|
|
ot->invoke = time_offset_invoke;
|
|
ot->modal = graph_slider_modal;
|
|
ot->exec = time_offset_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,
|
|
"frame_offset",
|
|
0.0f,
|
|
-FLT_MAX,
|
|
FLT_MAX,
|
|
"Frame Offset",
|
|
"How far in frames to offset the animation",
|
|
-10.0f,
|
|
10.0f);
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Shear Operator
|
|
* \{ */
|
|
|
|
static const EnumPropertyItem shear_direction_items[] = {
|
|
{SHEAR_FROM_LEFT,
|
|
"FROM_LEFT",
|
|
0,
|
|
"From Left",
|
|
"Shear the keys using the left key as reference"},
|
|
{SHEAR_FROM_RIGHT,
|
|
"FROM_RIGHT",
|
|
0,
|
|
"From Right",
|
|
"Shear the keys using the right key as reference"},
|
|
{0, nullptr, 0, nullptr, nullptr},
|
|
};
|
|
|
|
static void shear_graph_keys(bAnimContext *ac, const float factor, tShearDirection direction)
|
|
{
|
|
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) {
|
|
shear_fcurve_segment(fcu, segment, factor, direction);
|
|
}
|
|
|
|
ale->update |= ANIM_UPDATE_DEFAULT;
|
|
BLI_freelistN(&segments);
|
|
}
|
|
|
|
ANIM_animdata_update(ac, &anim_data);
|
|
ANIM_animdata_freelist(&anim_data);
|
|
}
|
|
|
|
static void shear_draw_status_header(bContext *C, tGraphSliderOp *gso, tShearDirection direction)
|
|
{
|
|
WorkspaceStatus status(C);
|
|
status.item(IFACE_("Confirm"), ICON_MOUSE_LMB);
|
|
status.item(IFACE_("Cancel"), ICON_EVENT_ESC);
|
|
status.item(IFACE_("Adjust"), ICON_MOUSE_MOVE);
|
|
if (hasNumInput(&gso->num)) {
|
|
char str_ofs[NUM_STR_REP_LEN];
|
|
outputNumInput(&gso->num, str_ofs, gso->scene->unit);
|
|
status.item(str_ofs, ICON_NONE);
|
|
}
|
|
else {
|
|
ED_slider_status_get(gso->slider, status);
|
|
status.item(
|
|
fmt::format("{} ({})",
|
|
IFACE_("Direction"),
|
|
direction == SHEAR_FROM_LEFT ? IFACE_("From Left") : IFACE_("From Right")),
|
|
ICON_EVENT_D);
|
|
}
|
|
}
|
|
|
|
static void shear_modal_update(bContext *C, wmOperator *op)
|
|
{
|
|
tGraphSliderOp *gso = static_cast<tGraphSliderOp *>(op->customdata);
|
|
|
|
/* Reset keyframes to the state at invoke. */
|
|
reset_bezts(gso);
|
|
const float factor = slider_factor_get_and_remember(op);
|
|
const tShearDirection direction = tShearDirection(RNA_enum_get(op->ptr, "direction"));
|
|
|
|
shear_draw_status_header(C, gso, direction);
|
|
|
|
shear_graph_keys(&gso->ac, factor, direction);
|
|
WM_event_add_notifier(C, NC_ANIMATION | ND_KEYFRAME | NA_EDITED, nullptr);
|
|
}
|
|
|
|
static wmOperatorStatus shear_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: {
|
|
tShearDirection direction = tShearDirection(RNA_enum_get(op->ptr, "direction"));
|
|
RNA_enum_set(op->ptr,
|
|
"direction",
|
|
(direction == SHEAR_FROM_LEFT) ? SHEAR_FROM_RIGHT : SHEAR_FROM_LEFT);
|
|
shear_modal_update(C, op);
|
|
break;
|
|
}
|
|
|
|
default:
|
|
return graph_slider_modal(C, op, event);
|
|
break;
|
|
}
|
|
return OPERATOR_RUNNING_MODAL;
|
|
}
|
|
|
|
static wmOperatorStatus shear_invoke(bContext *C, wmOperator *op, const wmEvent *event)
|
|
{
|
|
const wmOperatorStatus invoke_result = graph_slider_invoke(C, op, event);
|
|
|
|
if (invoke_result == OPERATOR_CANCELLED) {
|
|
return invoke_result;
|
|
}
|
|
|
|
tGraphSliderOp *gso = static_cast<tGraphSliderOp *>(op->customdata);
|
|
gso->modal_update = shear_modal_update;
|
|
gso->factor_prop = RNA_struct_find_property(op->ptr, "factor");
|
|
const tShearDirection direction = tShearDirection(RNA_enum_get(op->ptr, "direction"));
|
|
|
|
shear_draw_status_header(C, gso, direction);
|
|
ED_slider_factor_bounds_set(gso->slider, -1, 1);
|
|
ED_slider_factor_set(gso->slider, 0.0f);
|
|
|
|
return invoke_result;
|
|
}
|
|
|
|
static wmOperatorStatus shear_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 tShearDirection direction = tShearDirection(RNA_enum_get(op->ptr, "direction"));
|
|
|
|
shear_graph_keys(&ac, factor, direction);
|
|
|
|
/* Set notifier that keyframes have changed. */
|
|
WM_event_add_notifier(C, NC_ANIMATION | ND_KEYFRAME | NA_EDITED, nullptr);
|
|
|
|
return OPERATOR_FINISHED;
|
|
}
|
|
|
|
void GRAPH_OT_shear(wmOperatorType *ot)
|
|
{
|
|
/* Identifiers. */
|
|
ot->name = "Shear Keyframes";
|
|
ot->idname = "GRAPH_OT_shear";
|
|
ot->description =
|
|
"Affect the value of the keys linearly, keeping the same relationship between them using "
|
|
"either the left or the right key as reference";
|
|
|
|
/* API callbacks. */
|
|
ot->invoke = shear_invoke;
|
|
ot->modal = shear_modal;
|
|
ot->exec = shear_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,
|
|
"Shear Factor",
|
|
"The amount of shear to apply",
|
|
-1.0f,
|
|
1.0f);
|
|
|
|
RNA_def_enum(ot->srna,
|
|
"direction",
|
|
shear_direction_items,
|
|
SHEAR_FROM_LEFT,
|
|
"Direction",
|
|
"Which end of the segment to use as a reference to shear from");
|
|
}
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Scale Average Operator
|
|
* \{ */
|
|
|
|
static void scale_average_graph_keys(bAnimContext *ac, const float factor)
|
|
{
|
|
apply_fcu_segment_function(ac, factor, scale_average_fcurve_segment);
|
|
}
|
|
|
|
static void scale_average_modal_update(bContext *C, wmOperator *op)
|
|
{
|
|
tGraphSliderOp *gso = static_cast<tGraphSliderOp *>(op->customdata);
|
|
|
|
common_draw_status_header(C, gso);
|
|
|
|
/* Reset keyframes to the state at invoke. */
|
|
reset_bezts(gso);
|
|
const float factor = slider_factor_get_and_remember(op);
|
|
scale_average_graph_keys(&gso->ac, factor);
|
|
WM_event_add_notifier(C, NC_ANIMATION | ND_KEYFRAME | NA_EDITED, nullptr);
|
|
}
|
|
|
|
static wmOperatorStatus scale_average_invoke(bContext *C, wmOperator *op, const wmEvent *event)
|
|
{
|
|
const wmOperatorStatus invoke_result = graph_slider_invoke(C, op, event);
|
|
|
|
if (invoke_result == OPERATOR_CANCELLED) {
|
|
return invoke_result;
|
|
}
|
|
|
|
tGraphSliderOp *gso = static_cast<tGraphSliderOp *>(op->customdata);
|
|
gso->modal_update = scale_average_modal_update;
|
|
gso->factor_prop = RNA_struct_find_property(op->ptr, "factor");
|
|
common_draw_status_header(C, gso);
|
|
ED_slider_factor_bounds_set(gso->slider, 0, 2);
|
|
ED_slider_factor_set(gso->slider, 1.0f);
|
|
|
|
return invoke_result;
|
|
}
|
|
|
|
static wmOperatorStatus scale_average_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");
|
|
|
|
scale_average_graph_keys(&ac, factor);
|
|
|
|
/* Set notifier that keyframes have changed. */
|
|
WM_event_add_notifier(C, NC_ANIMATION | ND_KEYFRAME | NA_EDITED, nullptr);
|
|
|
|
return OPERATOR_FINISHED;
|
|
}
|
|
|
|
void GRAPH_OT_scale_average(wmOperatorType *ot)
|
|
{
|
|
/* Identifiers. */
|
|
ot->name = "Scale Average Keyframes";
|
|
ot->idname = "GRAPH_OT_scale_average";
|
|
ot->description = "Scale selected key values by their combined average";
|
|
|
|
/* API callbacks. */
|
|
ot->invoke = scale_average_invoke;
|
|
ot->modal = graph_slider_modal;
|
|
ot->exec = scale_average_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",
|
|
1.0f,
|
|
-FLT_MAX,
|
|
FLT_MAX,
|
|
"Scale Factor",
|
|
"The scale factor applied to the curve segments",
|
|
0.0f,
|
|
2.0f);
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Gauss Smooth Operator
|
|
* \{ */
|
|
|
|
/* It is necessary to store data for smoothing when running in modal, because the sampling of
|
|
* FCurves shouldn't be done on every update. */
|
|
struct tGaussOperatorData {
|
|
double *kernel;
|
|
ListBase segment_links; /* tFCurveSegmentLink */
|
|
ListBase anim_data; /* bAnimListElem */
|
|
};
|
|
|
|
/* Store data to smooth an FCurve segment. */
|
|
struct tFCurveSegmentLink {
|
|
tFCurveSegmentLink *next, *prev;
|
|
FCurve *fcu;
|
|
FCurveSegment *segment;
|
|
/* Array of y-values. The length of the array equals the length of the
|
|
* segment. */
|
|
float *original_y_values;
|
|
/* Array of y-values of the FCurve segment at regular intervals. */
|
|
float *samples;
|
|
int sample_count;
|
|
};
|
|
|
|
/* Allocates data that has to be freed after. */
|
|
static float *back_up_key_y_values(const FCurveSegment *segment, const FCurve *fcu)
|
|
{
|
|
float *original_y_values = MEM_calloc_arrayN<float>(segment->length,
|
|
"Smooth FCurve original values");
|
|
for (int i = 0; i < segment->length; i++) {
|
|
original_y_values[i] = fcu->bezt[i + segment->start_index].vec[1][1];
|
|
}
|
|
return original_y_values;
|
|
}
|
|
|
|
static void gaussian_smooth_allocate_operator_data(tGraphSliderOp *gso,
|
|
const int filter_width,
|
|
const float sigma)
|
|
{
|
|
tGaussOperatorData *operator_data = MEM_callocN<tGaussOperatorData>("tGaussOperatorData");
|
|
const int kernel_size = filter_width + 1;
|
|
double *kernel = MEM_calloc_arrayN<double>(kernel_size, "Gauss Kernel");
|
|
ED_ANIM_get_1d_gauss_kernel(sigma, kernel_size, kernel);
|
|
operator_data->kernel = kernel;
|
|
|
|
ListBase anim_data = {nullptr, nullptr};
|
|
ANIM_animdata_filter(
|
|
&gso->ac, &anim_data, OPERATOR_DATA_FILTER, gso->ac.data, eAnimCont_Types(gso->ac.datatype));
|
|
|
|
ListBase segment_links = {nullptr, nullptr};
|
|
LISTBASE_FOREACH (bAnimListElem *, ale, &anim_data) {
|
|
FCurve *fcu = (FCurve *)ale->key_data;
|
|
ListBase fcu_segments = find_fcurve_segments(fcu);
|
|
LISTBASE_FOREACH (FCurveSegment *, segment, &fcu_segments) {
|
|
tFCurveSegmentLink *segment_link = MEM_callocN<tFCurveSegmentLink>("FCurve Segment Link");
|
|
segment_link->fcu = fcu;
|
|
segment_link->segment = segment;
|
|
segment_link->original_y_values = back_up_key_y_values(segment, fcu);
|
|
BezTriple left_bezt = fcu->bezt[segment->start_index];
|
|
BezTriple right_bezt = fcu->bezt[segment->start_index + segment->length - 1];
|
|
const int sample_count = int(right_bezt.vec[1][0] - left_bezt.vec[1][0]) +
|
|
(filter_width * 2 + 1);
|
|
float *samples = MEM_calloc_arrayN<float>(sample_count, "Smooth FCurve Op Samples");
|
|
blender::animrig::sample_fcurve_segment(
|
|
fcu, left_bezt.vec[1][0] - filter_width, 1, samples, sample_count);
|
|
segment_link->samples = samples;
|
|
segment_link->sample_count = sample_count;
|
|
BLI_addtail(&segment_links, segment_link);
|
|
}
|
|
}
|
|
|
|
operator_data->anim_data = anim_data;
|
|
operator_data->segment_links = segment_links;
|
|
gso->operator_data = operator_data;
|
|
}
|
|
|
|
static void gaussian_smooth_free_operator_data(void *operator_data)
|
|
{
|
|
tGaussOperatorData *gauss_data = (tGaussOperatorData *)operator_data;
|
|
LISTBASE_FOREACH (tFCurveSegmentLink *, segment_link, &gauss_data->segment_links) {
|
|
MEM_freeN(segment_link->samples);
|
|
MEM_freeN(segment_link->segment);
|
|
MEM_freeN(segment_link->original_y_values);
|
|
}
|
|
MEM_freeN(gauss_data->kernel);
|
|
BLI_freelistN(&gauss_data->segment_links);
|
|
ANIM_animdata_freelist(&gauss_data->anim_data);
|
|
MEM_freeN(gauss_data);
|
|
}
|
|
|
|
static void gaussian_smooth_modal_update(bContext *C, wmOperator *op)
|
|
{
|
|
tGraphSliderOp *gso = static_cast<tGraphSliderOp *>(op->customdata);
|
|
|
|
bAnimContext ac;
|
|
|
|
if (ANIM_animdata_get_context(C, &ac) == 0) {
|
|
return;
|
|
}
|
|
|
|
common_draw_status_header(C, gso);
|
|
|
|
const float factor = slider_factor_get_and_remember(op);
|
|
tGaussOperatorData *operator_data = (tGaussOperatorData *)gso->operator_data;
|
|
const int filter_width = RNA_int_get(op->ptr, "filter_width");
|
|
|
|
LISTBASE_FOREACH (tFCurveSegmentLink *, segment, &operator_data->segment_links) {
|
|
smooth_fcurve_segment(segment->fcu,
|
|
segment->segment,
|
|
segment->original_y_values,
|
|
segment->samples,
|
|
segment->sample_count,
|
|
factor,
|
|
filter_width,
|
|
operator_data->kernel);
|
|
}
|
|
|
|
LISTBASE_FOREACH (bAnimListElem *, ale, &operator_data->anim_data) {
|
|
ale->update |= ANIM_UPDATE_DEFAULT;
|
|
}
|
|
|
|
ANIM_animdata_update(&ac, &operator_data->anim_data);
|
|
WM_event_add_notifier(C, NC_ANIMATION | ND_KEYFRAME | NA_EDITED, nullptr);
|
|
}
|
|
|
|
static wmOperatorStatus gaussian_smooth_invoke(bContext *C, wmOperator *op, const wmEvent *event)
|
|
{
|
|
const wmOperatorStatus invoke_result = graph_slider_invoke(C, op, event);
|
|
|
|
if (invoke_result == OPERATOR_CANCELLED) {
|
|
return invoke_result;
|
|
}
|
|
|
|
tGraphSliderOp *gso = static_cast<tGraphSliderOp *>(op->customdata);
|
|
gso->modal_update = gaussian_smooth_modal_update;
|
|
gso->factor_prop = RNA_struct_find_property(op->ptr, "factor");
|
|
|
|
const float sigma = RNA_float_get(op->ptr, "sigma");
|
|
const int filter_width = RNA_int_get(op->ptr, "filter_width");
|
|
|
|
gaussian_smooth_allocate_operator_data(gso, filter_width, sigma);
|
|
gso->free_operator_data = gaussian_smooth_free_operator_data;
|
|
|
|
ED_slider_allow_overshoot_set(gso->slider, false, false);
|
|
ED_slider_factor_set(gso->slider, 0.0f);
|
|
common_draw_status_header(C, gso);
|
|
|
|
return invoke_result;
|
|
}
|
|
|
|
static void gaussian_smooth_graph_keys(bAnimContext *ac,
|
|
const float factor,
|
|
double *kernel,
|
|
const int filter_width)
|
|
{
|
|
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) {
|
|
BezTriple left_bezt = fcu->bezt[segment->start_index];
|
|
BezTriple right_bezt = fcu->bezt[segment->start_index + segment->length - 1];
|
|
const int sample_count = int(right_bezt.vec[1][0] - left_bezt.vec[1][0]) +
|
|
(filter_width * 2 + 1);
|
|
float *samples = MEM_calloc_arrayN<float>(sample_count, "Smooth FCurve Op Samples");
|
|
float *original_y_values = back_up_key_y_values(segment, fcu);
|
|
blender::animrig::sample_fcurve_segment(
|
|
fcu, left_bezt.vec[1][0] - filter_width, 1, samples, sample_count);
|
|
smooth_fcurve_segment(
|
|
fcu, segment, original_y_values, samples, sample_count, factor, filter_width, kernel);
|
|
MEM_freeN(samples);
|
|
MEM_freeN(original_y_values);
|
|
}
|
|
|
|
BLI_freelistN(&segments);
|
|
ale->update |= ANIM_UPDATE_DEFAULT;
|
|
}
|
|
|
|
ANIM_animdata_update(ac, &anim_data);
|
|
ANIM_animdata_freelist(&anim_data);
|
|
}
|
|
|
|
static wmOperatorStatus gaussian_smooth_exec(bContext *C, wmOperator *op)
|
|
{
|
|
bAnimContext ac;
|
|
|
|
if (ANIM_animdata_get_context(C, &ac) == 0) {
|
|
return OPERATOR_CANCELLED;
|
|
}
|
|
const float factor = RNA_float_get(op->ptr, "factor");
|
|
const int filter_width = RNA_int_get(op->ptr, "filter_width");
|
|
const int kernel_size = filter_width + 1;
|
|
double *kernel = MEM_calloc_arrayN<double>(kernel_size, "Gauss Kernel");
|
|
ED_ANIM_get_1d_gauss_kernel(RNA_float_get(op->ptr, "sigma"), kernel_size, kernel);
|
|
|
|
gaussian_smooth_graph_keys(&ac, factor, kernel, filter_width);
|
|
|
|
MEM_freeN(kernel);
|
|
|
|
/* Set notifier that keyframes have changed. */
|
|
WM_event_add_notifier(C, NC_ANIMATION | ND_KEYFRAME | NA_EDITED, nullptr);
|
|
|
|
return OPERATOR_FINISHED;
|
|
}
|
|
|
|
void GRAPH_OT_gaussian_smooth(wmOperatorType *ot)
|
|
{
|
|
/* Identifiers. */
|
|
ot->name = "Gaussian Smooth";
|
|
ot->idname = "GRAPH_OT_gaussian_smooth";
|
|
ot->description = "Smooth the curve using a Gaussian filter";
|
|
|
|
/* API callbacks. */
|
|
ot->invoke = gaussian_smooth_invoke;
|
|
ot->modal = graph_slider_modal;
|
|
ot->exec = gaussian_smooth_exec;
|
|
ot->poll = graphop_editable_keyframes_poll;
|
|
|
|
/* Flags. */
|
|
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
|
|
|
|
RNA_def_float_factor(ot->srna,
|
|
"factor",
|
|
1.0f,
|
|
0.0f,
|
|
FLT_MAX,
|
|
"Factor",
|
|
"How much to blend to the default value",
|
|
0.0f,
|
|
1.0f);
|
|
|
|
RNA_def_float(ot->srna,
|
|
"sigma",
|
|
0.33f,
|
|
0.001f,
|
|
FLT_MAX,
|
|
"Sigma",
|
|
"The shape of the gaussian distribution, lower values make it sharper",
|
|
0.001f,
|
|
100.0f);
|
|
|
|
RNA_def_int(ot->srna,
|
|
"filter_width",
|
|
6,
|
|
1,
|
|
64,
|
|
"Filter Width",
|
|
"How far to each side the operator will average the key values",
|
|
1,
|
|
32);
|
|
}
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Butterworth Smooth Operator
|
|
* \{ */
|
|
|
|
struct tBtwOperatorData {
|
|
ButterworthCoefficients *coefficients;
|
|
ListBase segment_links; /* tFCurveSegmentLink */
|
|
ListBase anim_data; /* bAnimListElem */
|
|
};
|
|
|
|
static int btw_calculate_sample_count(const BezTriple *right_bezt,
|
|
const BezTriple *left_bezt,
|
|
const int filter_order,
|
|
const int samples_per_frame)
|
|
{
|
|
/* Adding a constant 60 frames to combat the issue that the phase delay is shifting data out of
|
|
* the sample count range. This becomes an issue when running the filter backwards. */
|
|
const int sample_count = (int(right_bezt->vec[1][0] - left_bezt->vec[1][0]) + 1 +
|
|
(filter_order * 2)) *
|
|
samples_per_frame +
|
|
60;
|
|
return sample_count;
|
|
}
|
|
|
|
static void btw_smooth_allocate_operator_data(tGraphSliderOp *gso,
|
|
const int filter_order,
|
|
const int samples_per_frame)
|
|
{
|
|
tBtwOperatorData *operator_data = MEM_callocN<tBtwOperatorData>("tBtwOperatorData");
|
|
|
|
operator_data->coefficients = ED_anim_allocate_butterworth_coefficients(filter_order);
|
|
|
|
ListBase anim_data = {nullptr, nullptr};
|
|
ANIM_animdata_filter(
|
|
&gso->ac, &anim_data, OPERATOR_DATA_FILTER, gso->ac.data, eAnimCont_Types(gso->ac.datatype));
|
|
|
|
ListBase segment_links = {nullptr, nullptr};
|
|
LISTBASE_FOREACH (bAnimListElem *, ale, &anim_data) {
|
|
FCurve *fcu = (FCurve *)ale->key_data;
|
|
ListBase fcu_segments = find_fcurve_segments(fcu);
|
|
|
|
LISTBASE_FOREACH (FCurveSegment *, segment, &fcu_segments) {
|
|
|
|
tFCurveSegmentLink *segment_link = MEM_callocN<tFCurveSegmentLink>("FCurve Segment Link");
|
|
segment_link->fcu = fcu;
|
|
segment_link->segment = segment;
|
|
BezTriple left_bezt = fcu->bezt[segment->start_index];
|
|
BezTriple right_bezt = fcu->bezt[segment->start_index + segment->length - 1];
|
|
const int sample_count = btw_calculate_sample_count(
|
|
&right_bezt, &left_bezt, filter_order, samples_per_frame);
|
|
float *samples = MEM_calloc_arrayN<float>(sample_count, "Btw Smooth FCurve Op Samples");
|
|
blender::animrig::sample_fcurve_segment(
|
|
fcu, left_bezt.vec[1][0] - filter_order, samples_per_frame, samples, sample_count);
|
|
segment_link->samples = samples;
|
|
segment_link->sample_count = sample_count;
|
|
BLI_addtail(&segment_links, segment_link);
|
|
}
|
|
}
|
|
|
|
operator_data->anim_data = anim_data;
|
|
operator_data->segment_links = segment_links;
|
|
gso->operator_data = operator_data;
|
|
}
|
|
|
|
static void btw_smooth_free_operator_data(void *operator_data)
|
|
{
|
|
tBtwOperatorData *btw_data = (tBtwOperatorData *)operator_data;
|
|
LISTBASE_FOREACH (tFCurveSegmentLink *, segment_link, &btw_data->segment_links) {
|
|
MEM_freeN(segment_link->samples);
|
|
MEM_freeN(segment_link->segment);
|
|
}
|
|
ED_anim_free_butterworth_coefficients(btw_data->coefficients);
|
|
BLI_freelistN(&btw_data->segment_links);
|
|
ANIM_animdata_freelist(&btw_data->anim_data);
|
|
MEM_freeN(btw_data);
|
|
}
|
|
|
|
static void btw_smooth_modal_update(bContext *C, wmOperator *op)
|
|
{
|
|
tGraphSliderOp *gso = static_cast<tGraphSliderOp *>(op->customdata);
|
|
|
|
bAnimContext ac;
|
|
|
|
if (ANIM_animdata_get_context(C, &ac) == 0) {
|
|
return;
|
|
}
|
|
|
|
common_draw_status_header(C, gso);
|
|
|
|
tBtwOperatorData *operator_data = (tBtwOperatorData *)gso->operator_data;
|
|
|
|
const float frame_rate = float(ac.scene->r.frs_sec) / ac.scene->r.frs_sec_base;
|
|
const int samples_per_frame = RNA_int_get(op->ptr, "samples_per_frame");
|
|
const float sampling_frequency = frame_rate * samples_per_frame;
|
|
|
|
const float cutoff_frequency = slider_factor_get_and_remember(op);
|
|
const int blend_in_out = RNA_int_get(op->ptr, "blend_in_out");
|
|
|
|
ED_anim_calculate_butterworth_coefficients(
|
|
cutoff_frequency, sampling_frequency, operator_data->coefficients);
|
|
|
|
LISTBASE_FOREACH (tFCurveSegmentLink *, segment, &operator_data->segment_links) {
|
|
butterworth_smooth_fcurve_segment(segment->fcu,
|
|
segment->segment,
|
|
segment->samples,
|
|
segment->sample_count,
|
|
1,
|
|
blend_in_out,
|
|
samples_per_frame,
|
|
operator_data->coefficients);
|
|
}
|
|
|
|
LISTBASE_FOREACH (bAnimListElem *, ale, &operator_data->anim_data) {
|
|
ale->update |= ANIM_UPDATE_DEFAULT;
|
|
}
|
|
|
|
ANIM_animdata_update(&ac, &operator_data->anim_data);
|
|
WM_event_add_notifier(C, NC_ANIMATION | ND_KEYFRAME | NA_EDITED, nullptr);
|
|
}
|
|
|
|
static wmOperatorStatus btw_smooth_invoke(bContext *C, wmOperator *op, const wmEvent *event)
|
|
{
|
|
const wmOperatorStatus invoke_result = graph_slider_invoke(C, op, event);
|
|
|
|
if (invoke_result == OPERATOR_CANCELLED) {
|
|
return invoke_result;
|
|
}
|
|
|
|
tGraphSliderOp *gso = static_cast<tGraphSliderOp *>(op->customdata);
|
|
gso->modal_update = btw_smooth_modal_update;
|
|
gso->factor_prop = RNA_struct_find_property(op->ptr, "cutoff_frequency");
|
|
|
|
const int filter_order = RNA_int_get(op->ptr, "filter_order");
|
|
const int samples_per_frame = RNA_int_get(op->ptr, "samples_per_frame");
|
|
|
|
btw_smooth_allocate_operator_data(gso, filter_order, samples_per_frame);
|
|
gso->free_operator_data = btw_smooth_free_operator_data;
|
|
|
|
const float frame_rate = float(gso->scene->r.frs_sec) / gso->scene->r.frs_sec_base;
|
|
const float sampling_frequency = frame_rate * samples_per_frame;
|
|
ED_slider_factor_bounds_set(gso->slider, 0, sampling_frequency / 2);
|
|
ED_slider_increment_step_set(gso->slider, sampling_frequency / 20);
|
|
ED_slider_factor_set(gso->slider, RNA_float_get(op->ptr, "cutoff_frequency"));
|
|
ED_slider_allow_overshoot_set(gso->slider, false, false);
|
|
ED_slider_mode_set(gso->slider, SLIDER_MODE_FLOAT);
|
|
ED_slider_unit_set(gso->slider, "Hz");
|
|
common_draw_status_header(C, gso);
|
|
|
|
return invoke_result;
|
|
}
|
|
|
|
static void btw_smooth_graph_keys(bAnimContext *ac,
|
|
const float factor,
|
|
const int blend_in_out,
|
|
float cutoff_frequency,
|
|
const int filter_order,
|
|
const int samples_per_frame)
|
|
{
|
|
ListBase anim_data = {nullptr, nullptr};
|
|
ANIM_animdata_filter(
|
|
ac, &anim_data, OPERATOR_DATA_FILTER, ac->data, eAnimCont_Types(ac->datatype));
|
|
|
|
ButterworthCoefficients *bw_coeff = ED_anim_allocate_butterworth_coefficients(filter_order);
|
|
|
|
const float frame_rate = float(ac->scene->r.frs_sec) / ac->scene->r.frs_sec_base;
|
|
const float sampling_frequency = frame_rate * samples_per_frame;
|
|
/* Clamp cutoff frequency to Nyquist Frequency. */
|
|
cutoff_frequency = min_ff(cutoff_frequency, sampling_frequency / 2);
|
|
ED_anim_calculate_butterworth_coefficients(cutoff_frequency, sampling_frequency, bw_coeff);
|
|
|
|
LISTBASE_FOREACH (bAnimListElem *, ale, &anim_data) {
|
|
FCurve *fcu = (FCurve *)ale->key_data;
|
|
ListBase segments = find_fcurve_segments(fcu);
|
|
|
|
LISTBASE_FOREACH (FCurveSegment *, segment, &segments) {
|
|
BezTriple left_bezt = fcu->bezt[segment->start_index];
|
|
BezTriple right_bezt = fcu->bezt[segment->start_index + segment->length - 1];
|
|
const int sample_count = btw_calculate_sample_count(
|
|
&right_bezt, &left_bezt, filter_order, samples_per_frame);
|
|
float *samples = MEM_calloc_arrayN<float>(sample_count, "Smooth FCurve Op Samples");
|
|
blender::animrig::sample_fcurve_segment(
|
|
fcu, left_bezt.vec[1][0] - filter_order, samples_per_frame, samples, sample_count);
|
|
butterworth_smooth_fcurve_segment(
|
|
fcu, segment, samples, sample_count, factor, blend_in_out, samples_per_frame, bw_coeff);
|
|
MEM_freeN(samples);
|
|
}
|
|
|
|
BLI_freelistN(&segments);
|
|
ale->update |= ANIM_UPDATE_DEFAULT;
|
|
}
|
|
|
|
ED_anim_free_butterworth_coefficients(bw_coeff);
|
|
ANIM_animdata_update(ac, &anim_data);
|
|
ANIM_animdata_freelist(&anim_data);
|
|
}
|
|
|
|
static wmOperatorStatus btw_smooth_exec(bContext *C, wmOperator *op)
|
|
{
|
|
bAnimContext ac;
|
|
|
|
if (ANIM_animdata_get_context(C, &ac) == 0) {
|
|
return OPERATOR_CANCELLED;
|
|
}
|
|
const float blend = RNA_float_get(op->ptr, "blend");
|
|
const float cutoff_frequency = RNA_float_get(op->ptr, "cutoff_frequency");
|
|
const int filter_order = RNA_int_get(op->ptr, "filter_order");
|
|
const int samples_per_frame = RNA_int_get(op->ptr, "samples_per_frame");
|
|
const int blend_in_out = RNA_int_get(op->ptr, "blend_in_out");
|
|
btw_smooth_graph_keys(
|
|
&ac, blend, blend_in_out, cutoff_frequency, filter_order, samples_per_frame);
|
|
|
|
/* Set notifier that keyframes have changed. */
|
|
WM_event_add_notifier(C, NC_ANIMATION | ND_KEYFRAME | NA_EDITED, nullptr);
|
|
|
|
return OPERATOR_FINISHED;
|
|
}
|
|
|
|
void GRAPH_OT_butterworth_smooth(wmOperatorType *ot)
|
|
{
|
|
/* Identifiers. */
|
|
ot->name = "Butterworth Smooth";
|
|
ot->idname = "GRAPH_OT_butterworth_smooth";
|
|
ot->description = "Smooth an F-Curve while maintaining the general shape of the curve";
|
|
|
|
/* API callbacks. */
|
|
ot->invoke = btw_smooth_invoke;
|
|
ot->modal = graph_slider_modal;
|
|
ot->exec = btw_smooth_exec;
|
|
ot->poll = graphop_editable_keyframes_poll;
|
|
|
|
/* Flags. */
|
|
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO | OPTYPE_BLOCKING | OPTYPE_GRAB_CURSOR_X;
|
|
|
|
RNA_def_float(ot->srna,
|
|
"cutoff_frequency",
|
|
3.0f,
|
|
0.0f,
|
|
FLT_MAX,
|
|
"Frequency Cutoff (Hz)",
|
|
"Lower values give a smoother curve",
|
|
0.0f,
|
|
FLT_MAX);
|
|
|
|
RNA_def_int(ot->srna,
|
|
"filter_order",
|
|
4,
|
|
1,
|
|
32,
|
|
"Filter Order",
|
|
"Higher values produce a harder frequency cutoff",
|
|
1,
|
|
16);
|
|
|
|
RNA_def_int(ot->srna,
|
|
"samples_per_frame",
|
|
1,
|
|
1,
|
|
64,
|
|
"Samples per Frame",
|
|
"How many samples to calculate per frame, helps with subframe data",
|
|
1,
|
|
16);
|
|
|
|
RNA_def_float_factor(ot->srna,
|
|
"blend",
|
|
1.0f,
|
|
0,
|
|
FLT_MAX,
|
|
"Blend",
|
|
"How much to blend to the smoothed curve",
|
|
0.0f,
|
|
1.0f);
|
|
|
|
RNA_def_int(ot->srna,
|
|
"blend_in_out",
|
|
1,
|
|
0,
|
|
INT_MAX,
|
|
"Blend In/Out",
|
|
"Linearly blend the smooth data to the border frames of the selection",
|
|
0,
|
|
128);
|
|
}
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Push-Pull Operator
|
|
* \{ */
|
|
|
|
static void push_pull_graph_keys(bAnimContext *ac, const float factor)
|
|
{
|
|
apply_fcu_segment_function(ac, factor, push_pull_fcurve_segment);
|
|
}
|
|
|
|
static void push_pull_modal_update(bContext *C, wmOperator *op)
|
|
{
|
|
tGraphSliderOp *gso = static_cast<tGraphSliderOp *>(op->customdata);
|
|
|
|
common_draw_status_header(C, gso);
|
|
|
|
/* Reset keyframes to the state at invoke. */
|
|
reset_bezts(gso);
|
|
const float factor = slider_factor_get_and_remember(op);
|
|
push_pull_graph_keys(&gso->ac, factor);
|
|
WM_event_add_notifier(C, NC_ANIMATION | ND_KEYFRAME | NA_EDITED, nullptr);
|
|
}
|
|
|
|
static wmOperatorStatus push_pull_invoke(bContext *C, wmOperator *op, const wmEvent *event)
|
|
{
|
|
const wmOperatorStatus invoke_result = graph_slider_invoke(C, op, event);
|
|
|
|
if (invoke_result == OPERATOR_CANCELLED) {
|
|
return invoke_result;
|
|
}
|
|
|
|
tGraphSliderOp *gso = static_cast<tGraphSliderOp *>(op->customdata);
|
|
gso->modal_update = push_pull_modal_update;
|
|
gso->factor_prop = RNA_struct_find_property(op->ptr, "factor");
|
|
ED_slider_factor_bounds_set(gso->slider, 0, 2);
|
|
ED_slider_factor_set(gso->slider, 1);
|
|
common_draw_status_header(C, gso);
|
|
|
|
return invoke_result;
|
|
}
|
|
|
|
static wmOperatorStatus push_pull_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");
|
|
|
|
push_pull_graph_keys(&ac, factor);
|
|
|
|
/* Set notifier that keyframes have changed. */
|
|
WM_event_add_notifier(C, NC_ANIMATION | ND_KEYFRAME | NA_EDITED, nullptr);
|
|
|
|
return OPERATOR_FINISHED;
|
|
}
|
|
|
|
void GRAPH_OT_push_pull(wmOperatorType *ot)
|
|
{
|
|
/* Identifiers. */
|
|
ot->name = "Push Pull Keyframes";
|
|
ot->idname = "GRAPH_OT_push_pull";
|
|
ot->description = "Exaggerate or minimize the value of the selected keys";
|
|
|
|
/* API callbacks. */
|
|
ot->invoke = push_pull_invoke;
|
|
ot->modal = graph_slider_modal;
|
|
ot->exec = push_pull_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",
|
|
1.0f,
|
|
-FLT_MAX,
|
|
FLT_MAX,
|
|
"Factor",
|
|
"Control how far to push or pull the keys",
|
|
0.0f,
|
|
2.0f);
|
|
}
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Scale from Left Operator
|
|
* \{ */
|
|
|
|
static const EnumPropertyItem scale_anchor_items[] = {
|
|
{int(FCurveSegmentAnchor::LEFT), "LEFT", 0, "From Left", ""},
|
|
{int(FCurveSegmentAnchor::RIGHT), "RIGHT", 0, "From Right", ""},
|
|
{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)
|
|
{
|
|
tGraphSliderOp *gso = static_cast<tGraphSliderOp *>(op->customdata);
|
|
WorkspaceStatus status(C);
|
|
status.item(IFACE_("Confirm"), ICON_MOUSE_LMB);
|
|
status.item(IFACE_("Cancel"), ICON_EVENT_ESC);
|
|
status.item(IFACE_("Adjust"), ICON_MOUSE_MOVE);
|
|
|
|
if (hasNumInput(&gso->num)) {
|
|
char str_ofs[NUM_STR_REP_LEN];
|
|
outputNumInput(&gso->num, str_ofs, gso->scene->unit);
|
|
status.item(str_ofs, ICON_NONE);
|
|
}
|
|
else {
|
|
ED_slider_status_get(gso->slider, status);
|
|
/* Operator specific functionality that extends beyond the slider. */
|
|
const FCurveSegmentAnchor anchor = FCurveSegmentAnchor(RNA_enum_get(op->ptr, "anchor"));
|
|
ED_slider_status_get(gso->slider, status);
|
|
status.item(fmt::format("{} ({})",
|
|
IFACE_("Direction"),
|
|
anchor == FCurveSegmentAnchor::LEFT ? IFACE_("From Left") :
|
|
IFACE_("From Right")),
|
|
ICON_EVENT_D);
|
|
}
|
|
}
|
|
|
|
static void scale_from_neighbor_modal_update(bContext *C, wmOperator *op)
|
|
{
|
|
tGraphSliderOp *gso = static_cast<tGraphSliderOp *>(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, nullptr);
|
|
}
|
|
|
|
static wmOperatorStatus 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 wmOperatorStatus scale_from_neighbor_invoke(bContext *C,
|
|
wmOperator *op,
|
|
const wmEvent *event)
|
|
{
|
|
const wmOperatorStatus invoke_result = graph_slider_invoke(C, op, event);
|
|
|
|
if (invoke_result == OPERATOR_CANCELLED) {
|
|
return OPERATOR_CANCELLED;
|
|
}
|
|
|
|
tGraphSliderOp *gso = static_cast<tGraphSliderOp *>(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 wmOperatorStatus 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, nullptr);
|
|
|
|
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 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");
|
|
}
|
|
|
|
/** \} */
|