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