Animation: Add "Frame Channel" operators #104523

Merged
Christoph Lendenfeld merged 27 commits from ChrisLend/blender:frame-channel-operator into main 2023-02-17 18:11:10 +01:00
7 changed files with 272 additions and 229 deletions
Showing only changes of commit 3b95f60f2b - Show all commits

View File

@ -3509,8 +3509,8 @@ def km_animation_channels(params):
# Menus.
*_template_items_context_menu("DOPESHEET_MT_channel_context_menu", params.context_menu_event),
# View
("graph.view_channel_pick", {"type": 'MIDDLEMOUSE', "value": 'PRESS', "alt": True}, None),
("graph.view_selected_channels", {"type": 'NUMPAD_PERIOD', "value": 'PRESS'}, None),
("anim.channel_view_pick", {"type": 'MIDDLEMOUSE', "value": 'PRESS', "alt": True}, None),
("anim.channels_view_selected", {"type": 'NUMPAD_PERIOD', "value": 'PRESS'}, None),
])
return keymap

View File

@ -690,7 +690,7 @@ class DOPESHEET_MT_channel_context_menu(Menu):
is_graph_editor = context.area.type == 'GRAPH_EDITOR'
layout.separator()
layout.operator("graph.view_selected_channels")
layout.operator("anim.channels_view_selected")
layout.operator("anim.channels_setting_enable", text="Mute Channels").type = 'MUTE'
layout.operator("anim.channels_setting_disable", text="Unmute Channels").type = 'MUTE'

View File

@ -239,7 +239,7 @@ class GRAPH_MT_channel(Menu):
layout.operator("anim.channels_fcurves_enable")
layout.separator()
layout.operator("graph.view_selected_channels")
layout.operator("anim.channels_view_selected")
class GRAPH_MT_key(Menu):

View File

@ -47,6 +47,7 @@
#include "ED_anim_api.h"
#include "ED_armature.h"
#include "ED_keyframes_edit.h" /* XXX move the select modes out of there! */
#include "ED_markers.h"
#include "ED_object.h"
#include "ED_screen.h"
#include "ED_select_utils.h"
@ -3637,6 +3638,270 @@ static void ANIM_OT_channel_select_keys(wmOperatorType *ot)
/** \} */
/* -------------------------------------------------------------------- */
/** \name View Channel Operator
* \{ */
static void get_normalized_fcurve_bounds(FCurve *fcu,
dr.sybren marked this conversation as resolved

I think these three pointers can all be const, as this only gets the bounds and shouldn't set anything.

I think these three pointers can all be `const`, as this only gets the bounds and shouldn't set anything.

only ale can be const in this case
because ANIM_get_normalization_flags and ANIM_unit_mapping_get_factor would throw warnings otherwise since they aren't set to const

only `ale` can be const in this case because `ANIM_get_normalization_flags` and `ANIM_unit_mapping_get_factor` would throw warnings otherwise since they aren't set to const
bAnimContext *ac,
bAnimListElem *ale,
bool include_handles,
ChrisLend marked this conversation as resolved

const

`const`
const float range[2],
rctf *r_bounds)
{
const bool fcu_selection_only = false;
BKE_fcurve_calc_bounds(fcu,
&r_bounds->xmin,
&r_bounds->xmax,
&r_bounds->ymin,
&r_bounds->ymax,
fcu_selection_only,
include_handles,
range);
float unitFac, offset;
short mapping_flag = ANIM_get_normalization_flags(ac);
ChrisLend marked this conversation as resolved

const

`const`
unitFac = ANIM_unit_mapping_get_factor(ac->scene, ale->id, fcu, mapping_flag, &offset);
ChrisLend marked this conversation as resolved

Declaring unitFac here makes it possible to const float it.

Declaring `unitFac` here makes it possible to `const float` it.
r_bounds->ymin += offset;
r_bounds->ymax += offset;

@dr.sybren
fixed the issue with the flat curves with that

@dr.sybren fixed the issue with the flat curves with that
r_bounds->ymin *= unitFac;
r_bounds->ymax *= unitFac;
ChrisLend marked this conversation as resolved

I think it's nicer to see the entire formula in one go:

r_bounds->ymin = unitFac * (r_bounds->ymin + offset);
I think it's nicer to see the entire formula in one go: ```c r_bounds->ymin = unitFac * (r_bounds->ymin + offset); ```
}
static void get_view_range(Scene *scene, const bool use_preview_range, float r_range[2])
{
if (use_preview_range && scene->r.flag & SCER_PRV_RANGE) {
r_range[0] = scene->r.psfra;
r_range[1] = scene->r.pefra;
}
else {
r_range[0] = -FLT_MAX;
r_range[1] = FLT_MAX;
}
}
/* Take regions into account, that could block the view.
* Marker region is supposed to be larger than the scroll-bar, so prioritize it. */
static void add_region_padding(bContext *C, bAnimContext *ac, rctf *bounds)
{
BLI_rctf_scale(bounds, 1.1f);
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(bounds, ac->region->winy, pad_bottom, pad_top);
}
/* Iterates through regions because the operator might not have been called from the correct
* region. */
static void move_graph_view(bContext *C, bAnimContext *ac, rctf *bounds, const int smooth_viewtx)
{
LISTBASE_FOREACH (ARegion *, region, &ac->area->regionbase) {
if (region->regiontype == RGN_TYPE_WINDOW) {
UI_view2d_smooth_view(C, region, bounds, smooth_viewtx);
}
}
}
static int graphkeys_view_selected_channels_exec(bContext *C, wmOperator *op)
{
bAnimContext ac;
/* Get editor data. */
if (ANIM_animdata_get_context(C, &ac) == 0) {
return OPERATOR_CANCELLED;
}
ListBase anim_data = {NULL, NULL};
const int filter = (ANIMFILTER_SEL | ANIMFILTER_NODUPLIS | ANIMFILTER_FCURVESONLY);
size_t anim_data_length = ANIM_animdata_filter(&ac, &anim_data, filter, ac.data, ac.datatype);
if (anim_data_length == 0) {
return OPERATOR_CANCELLED;
}
float range[2];
const bool use_preview_range = RNA_boolean_get(op->ptr, "use_preview_range");
get_view_range(ac.scene, use_preview_range, range);
rctf bounds = {.xmin = FLT_MAX, .xmax = -FLT_MAX, .ymin = FLT_MAX, .ymax = -FLT_MAX};
bAnimListElem *ale;
dr.sybren marked this conversation as resolved

How can the view be blocked, by taking regions into account?
I think for the right meaning the comma has to be removed.

Having said that, the comment is a bit vague, "take into account" doesn't mean that much until you know where & how the function is used.

It's better to be more descriptive of what it does, rather than about where it is supposed to be used.

How can the view be blocked, by taking regions into account? I think for the right meaning the comma has to be removed. Having said that, the comment is a bit vague, "take into account" doesn't mean that much until you know where & how the function is used. It's better to be more descriptive of what it does, rather than about where it is supposed to be used.

let me know if the comment makes more sense now

let me know if the comment makes more sense now
const bool include_handles = RNA_boolean_get(op->ptr, "include_handles");
for (ale = anim_data.first; ale; ale = ale->next) {
if (ale->datatype != ALE_FCURVE) {
continue;
}
FCurve *fcu = (FCurve *)ale->key_data;
ChrisLend marked this conversation as resolved

const

`const`
rctf fcu_bounds;
get_normalized_fcurve_bounds(fcu, &ac, ale, include_handles, range, &fcu_bounds);
BLI_rctf_union(&bounds, &fcu_bounds);
}
dr.sybren marked this conversation as resolved

Within the context of this patch "the operator" can be understood, but once this is in Blender's sources, it's a bit vague. The doc also explains what this code does in terms of what other code didn't do, and that creates a tight coupling that's not wanted.

Within the context of this patch "the operator" can be understood, but once this is in Blender's sources, it's a bit vague. The doc also explains what this code does in terms of what other code didn't do, and that creates a tight coupling that's not wanted.

I hope the comment is better now

I hope the comment is better now
add_region_padding(C, &ac, &bounds);
const int smooth_viewtx = WM_operator_smooth_viewtx_get(op);
move_graph_view(C, &ac, &bounds, smooth_viewtx);
ANIM_animdata_freelist(&anim_data);
return OPERATOR_FINISHED;
}
static bool channel_view_poll(bContext *C)
{
return ED_operator_action_active(C) || ED_operator_graphedit_active(C);
}
static void ANIM_OT_channels_view_selected(wmOperatorType *ot)
{
/* Identifiers */
ot->name = "Frame Selected Channels";
ot->idname = "ANIM_OT_channels_view_selected";
ot->description = "Reset viewable area to show the selected channels";
/* API callbacks */
ot->exec = graphkeys_view_selected_channels_exec;
ot->poll = channel_view_poll;
ChrisLend marked this conversation as resolved

Use WM_report to show a warning that explains why the operator couldn't run.

Use `WM_report` to show a warning that explains why the operator couldn't run.
ot->flag = 0;
ot->prop = RNA_def_boolean(ot->srna,
"include_handles",
true,
"Include Handles",
"Include handles of keyframes when calculating extents");
ot->prop = RNA_def_boolean(ot->srna,
"use_preview_range",
true,
"Use Preview Range",
"Ignore frames outside of the preview range");
}
static void get_gpencil_bounds(bGPDlayer *gpl, const float range[2], rctf *r_bounds)
{
bool found_start = false;
int start_frame = 0;
int end_frame = 1;
LISTBASE_FOREACH (bGPDframe *, gpf, &gpl->frames) {
if (gpf->framenum < range[0]) {
continue;
}
if (gpf->framenum > range[1]) {
break;
}
if (!found_start) {
start_frame = gpf->framenum;
found_start = true;
}
end_frame = gpf->framenum;
}
r_bounds->xmin = start_frame;
r_bounds->xmax = end_frame;
r_bounds->ymin = 0;
r_bounds->ymax = 1;
}
static void get_channel_bounds(bAnimContext *ac,
bAnimListElem *ale,
const float range[2],
const bool include_handles,
rctf *r_bounds)
{
switch (ale->datatype) {
case ALE_GPFRAME:
bGPDlayer *gpl = (bGPDlayer *)ale->data;
get_gpencil_bounds(gpl, range, r_bounds);
break;
case ALE_FCURVE:
FCurve *fcu = (FCurve *)ale->key_data;
get_normalized_fcurve_bounds(fcu, ac, ale, include_handles, range, r_bounds);
break;
}
}
static int graphkeys_channel_view_pick_invoke(bContext *C, wmOperator *op, const wmEvent *event)
{
bAnimContext ac;
if (ANIM_animdata_get_context(C, &ac) == 0) {
return OPERATOR_CANCELLED;
}
float x, y;
int channel_index;
ARegion *region = ac.region;
View2D *v2d = &region->v2d;
UI_view2d_region_to_view(v2d, event->mval[0], event->mval[1], &x, &y);
UI_view2d_listview_view_to_cell(ACHANNEL_NAMEWIDTH,
ACHANNEL_STEP(&ac),
0,
ACHANNEL_FIRST_TOP(&ac),
x,
y,
NULL,
&channel_index);
ListBase anim_data = {NULL, NULL};
const int filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE | ANIMFILTER_NODUPLIS);
ANIM_animdata_filter(&ac, &anim_data, filter, ac.data, ac.datatype);
bAnimListElem *ale;
ale = BLI_findlink(&anim_data, channel_index);
if (ale == NULL) {
ANIM_animdata_freelist(&anim_data);
return OPERATOR_CANCELLED;
}
float range[2];
const bool use_preview_range = RNA_boolean_get(op->ptr, "use_preview_range");
get_view_range(ac.scene, use_preview_range, range);
rctf bounds;
const bool include_handles = RNA_boolean_get(op->ptr, "include_handles");
get_channel_bounds(&ac, ale, range, include_handles, &bounds);
add_region_padding(C, &ac, &bounds);
const int smooth_viewtx = WM_operator_smooth_viewtx_get(op);
move_graph_view(C, &ac, &bounds, smooth_viewtx);
ANIM_animdata_freelist(&anim_data);
return OPERATOR_FINISHED;
}
static void ANIM_OT_channel_view_pick(wmOperatorType *ot)
{
/* Identifiers */
ot->name = "Frame Channel Under Cursor";
ot->idname = "ANIM_OT_channel_view_pick";
ot->description = "Reset viewable area to show the channel under the cursor";
/* API callbacks */
ot->invoke = graphkeys_channel_view_pick_invoke;
ot->poll = channel_view_poll;
ot->flag = 0;
ot->prop = RNA_def_boolean(ot->srna,
"include_handles",
true,
"Include Handles",
"Include handles of keyframes when calculating extents");
ot->prop = RNA_def_boolean(ot->srna,
"use_preview_range",
true,
"Use Preview Range",
"Ignore frames outside of the preview range");
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Operator Registration
* \{ */
@ -3656,6 +3921,9 @@ void ED_operatortypes_animchannels(void)
WM_operatortype_append(ANIM_OT_channels_setting_disable);
WM_operatortype_append(ANIM_OT_channels_setting_toggle);
WM_operatortype_append(ANIM_OT_channel_view_pick);
WM_operatortype_append(ANIM_OT_channels_view_selected);
WM_operatortype_append(ANIM_OT_channels_delete);
/* XXX does this need to be a separate operator? */

View File

@ -101,8 +101,6 @@ void GRAPH_OT_previewrange_set(struct wmOperatorType *ot);
void GRAPH_OT_view_all(struct wmOperatorType *ot);
void GRAPH_OT_view_selected(struct wmOperatorType *ot);
void GRAPH_OT_view_frame(struct wmOperatorType *ot);
void GRAPH_OT_view_selected_channels(struct wmOperatorType *ot);
void GRAPH_OT_view_channel_pick(struct wmOperatorType *ot);
void GRAPH_OT_click_insert(struct wmOperatorType *ot);
void GRAPH_OT_keyframe_insert(struct wmOperatorType *ot);

View File

@ -423,8 +423,6 @@ void graphedit_operatortypes(void)
WM_operatortype_append(GRAPH_OT_view_all);
WM_operatortype_append(GRAPH_OT_view_selected);
WM_operatortype_append(GRAPH_OT_view_frame);
WM_operatortype_append(GRAPH_OT_view_selected_channels);
WM_operatortype_append(GRAPH_OT_view_channel_pick);
WM_operatortype_append(GRAPH_OT_ghost_curves_create);
WM_operatortype_append(GRAPH_OT_ghost_curves_clear);

View File

@ -374,227 +374,6 @@ void GRAPH_OT_view_frame(wmOperatorType *ot)
/** \} */
/* -------------------------------------------------------------------- */
/** \name View Channel Operator
* \{ */
static void get_normalized_fcurve_bounds(FCurve *fcu,
bAnimContext *ac,
bAnimListElem *ale,
bool include_handles,
const float range[2],
rctf *r_bounds)
{
const bool fcu_selection_only = false;
BKE_fcurve_calc_bounds(fcu,
&r_bounds->xmin,
&r_bounds->xmax,
&r_bounds->ymin,
&r_bounds->ymax,
fcu_selection_only,
include_handles,
range);
float unitFac, offset;
short mapping_flag = ANIM_get_normalization_flags(ac);
unitFac = ANIM_unit_mapping_get_factor(ac->scene, ale->id, fcu, mapping_flag, &offset);
r_bounds->ymin += offset;
r_bounds->ymax += offset;
r_bounds->ymin *= unitFac;
r_bounds->ymax *= unitFac;
}
static void get_view_range(Scene *scene, const bool use_preview_range, float r_range[2])
{
if (use_preview_range && scene->r.flag & SCER_PRV_RANGE) {
r_range[0] = scene->r.psfra;
r_range[1] = scene->r.pefra;
}
else {
r_range[0] = -FLT_MAX;
r_range[1] = FLT_MAX;
}
}
static void pad_fcurve_bounds(bContext *C, bAnimContext *ac, rctf *bounds)
{
BLI_rctf_scale(bounds, 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(bounds, ac->region->winy, pad_bottom, pad_top);
}
static void move_graph_view(bContext *C, bAnimContext *ac, rctf *bounds, const int smooth_viewtx)
{
/* Iterate through regions because the operator might not have been called from the correct
* region. */
LISTBASE_FOREACH (ARegion *, region, &ac->area->regionbase) {
if (region->regiontype == RGN_TYPE_WINDOW) {
UI_view2d_smooth_view(C, region, bounds, smooth_viewtx);
}
}
}
static int graphkeys_view_selected_channels_exec(bContext *C, wmOperator *op)
{
bAnimContext ac;
/* Get editor data. */
if (ANIM_animdata_get_context(C, &ac) == 0) {
return OPERATOR_CANCELLED;
}
ListBase anim_data = {NULL, NULL};
const int filter = (ANIMFILTER_SEL | ANIMFILTER_NODUPLIS | ANIMFILTER_FCURVESONLY);
size_t anim_data_length = ANIM_animdata_filter(&ac, &anim_data, filter, ac.data, ac.datatype);
if (anim_data_length == 0) {
return OPERATOR_CANCELLED;
}
float range[2];
const bool use_preview_range = RNA_boolean_get(op->ptr, "use_preview_range");
get_view_range(ac.scene, use_preview_range, range);
rctf bounds = {.xmin = FLT_MAX, .xmax = -FLT_MAX, .ymin = FLT_MAX, .ymax = -FLT_MAX};
bAnimListElem *ale;
const bool include_handles = RNA_boolean_get(op->ptr, "include_handles");
for (ale = anim_data.first; ale; ale = ale->next) {
if (ale->datatype != ALE_FCURVE) {
continue;
}
FCurve *fcu = (FCurve *)ale->key_data;
rctf fcu_bounds;
get_normalized_fcurve_bounds(fcu, &ac, ale, include_handles, range, &fcu_bounds);
BLI_rctf_union(&bounds, &fcu_bounds);
}
pad_fcurve_bounds(C, &ac, &bounds);
const int smooth_viewtx = WM_operator_smooth_viewtx_get(op);
move_graph_view(C, &ac, &bounds, smooth_viewtx);
ANIM_animdata_freelist(&anim_data);
return OPERATOR_FINISHED;
}
static bool view_channel_poll(bContext *C)
{
return ED_operator_action_active(C) || ED_operator_graphedit_active(C);
}
void GRAPH_OT_view_selected_channels(wmOperatorType *ot)
{
/* Identifiers */
ot->name = "Frame Selected Channels";
ot->idname = "GRAPH_OT_view_selected_channels";
ot->description = "Reset viewable area to show the selected channels";
/* API callbacks */
ot->exec = graphkeys_view_selected_channels_exec;
ot->poll = view_channel_poll;
ot->flag = 0;
ot->prop = RNA_def_boolean(ot->srna,
"include_handles",
true,
"Include Handles",
"Include handles of keyframes when calculating extents");
ot->prop = RNA_def_boolean(ot->srna,
"use_preview_range",
true,
"Use Preview Range",
"Ignore frames outside of the preview range");
}
static int graphkeys_view_channel_pick_invoke(bContext *C, wmOperator *op, const wmEvent *event)
{
bAnimContext ac;
if (ANIM_animdata_get_context(C, &ac) == 0) {
return OPERATOR_CANCELLED;
}
float x, y;
int channel_index;
ARegion *region = ac.region;
View2D *v2d = &region->v2d;
UI_view2d_region_to_view(v2d, event->mval[0], event->mval[1], &x, &y);
UI_view2d_listview_view_to_cell(ACHANNEL_NAMEWIDTH,
ACHANNEL_STEP(&ac),
0,
ACHANNEL_FIRST_TOP(&ac),
x,
y,
NULL,
&channel_index);
ListBase anim_data = {NULL, NULL};
const int filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE |
ANIMFILTER_LIST_CHANNELS | ANIMFILTER_FCURVESONLY);
ANIM_animdata_filter(&ac, &anim_data, filter, ac.data, ac.datatype);
bAnimListElem *ale;
ale = BLI_findlink(&anim_data, channel_index);
if (ale == NULL || ale->datatype != ALE_FCURVE) {
return OPERATOR_CANCELLED;
}
float range[2];
const bool use_preview_range = RNA_boolean_get(op->ptr, "use_preview_range");
get_view_range(ac.scene, use_preview_range, range);
rctf fcu_bounds;
const bool include_handles = RNA_boolean_get(op->ptr, "include_handles");
FCurve *fcu = (FCurve *)ale->key_data;
get_normalized_fcurve_bounds(fcu, &ac, ale, include_handles, range, &fcu_bounds);
pad_fcurve_bounds(C, &ac, &fcu_bounds);
const int smooth_viewtx = WM_operator_smooth_viewtx_get(op);
move_graph_view(C, &ac, &fcu_bounds, smooth_viewtx);
return OPERATOR_FINISHED;
}
void GRAPH_OT_view_channel_pick(wmOperatorType *ot)
{
/* Identifiers */
ot->name = "Frame Channel Under Cursor";
ot->idname = "GRAPH_OT_view_channel_pick";
ot->description = "Reset viewable area to show the channel under the cursor";
/* API callbacks */
ot->invoke = graphkeys_view_channel_pick_invoke;
ot->poll = view_channel_poll;
ot->flag = 0;
ot->prop = RNA_def_boolean(ot->srna,
"include_handles",
true,
"Include Handles",
"Include handles of keyframes when calculating extents");
ot->prop = RNA_def_boolean(ot->srna,
"use_preview_range",
true,
"Use Preview Range",
"Ignore frames outside of the preview range");
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Create Ghost-Curves Operator
*