Anim: View FCurve of Property in the Graph Editor #114407

Merged
Christoph Lendenfeld merged 40 commits from ChrisLend/blender:focus_in_ge into main 2023-11-21 14:07:03 +01:00
2 changed files with 284 additions and 3 deletions

View File

@ -26,6 +26,7 @@
#include "RNA_access.hh"
#include "RNA_define.hh"
#include "RNA_path.hh"
#include "BKE_action.h"
#include "BKE_anim_data.h"
@ -40,6 +41,7 @@
#include "BKE_nla.h"
#include "BKE_scene.h"
#include "BKE_screen.hh"
#include "BKE_workspace.h"
#include "DEG_depsgraph.hh"
#include "DEG_depsgraph_build.hh"
@ -67,7 +69,7 @@ static bool get_normalized_fcurve_bounds(FCurve *fcu,
AnimData *anim_data,
SpaceLink *space_link,
Scene *scene,
bAnimListElem *ale,
ID *id,
const bool include_handles,
const float range[2],
rctf *r_bounds)
@ -83,7 +85,7 @@ static bool get_normalized_fcurve_bounds(FCurve *fcu,
const short mapping_flag = ANIM_get_normalization_flags(space_link);
float offset;
const float unit_fac = ANIM_unit_mapping_get_factor(scene, ale->id, fcu, mapping_flag, &offset);
const float unit_fac = ANIM_unit_mapping_get_factor(scene, id, fcu, mapping_flag, &offset);
r_bounds->ymin = (r_bounds->ymin + offset) * unit_fac;
r_bounds->ymax = (r_bounds->ymax + offset) * unit_fac;
@ -181,7 +183,7 @@ static bool get_channel_bounds(bAnimContext *ac,
FCurve *fcu = (FCurve *)ale->key_data;
AnimData *anim_data = ANIM_nla_mapping_get(ac, ale);
found_bounds = get_normalized_fcurve_bounds(
fcu, anim_data, ac->sl, ac->scene, ale, include_handles, range, r_bounds);
fcu, anim_data, ac->sl, ac->scene, ale->id, include_handles, range, r_bounds);
break;
}
}
@ -897,6 +899,7 @@ void ANIM_frame_channel_y_extents(bContext *C, bAnimContext *ac)
ANIM_animdata_freelist(&anim_data);
}
/** \} */
/* -------------------------------------------------------------------- */
@ -4307,6 +4310,239 @@ static void ANIM_OT_channel_view_pick(wmOperatorType *ot)
"Ignore frames outside of the preview range");
}
/* Find a Graph Editor area and modify the given context to be the window region of it. */
static bool move_context_to_graph_editor(bContext *C)
{
bool found_graph_editor = false;
LISTBASE_FOREACH (wmWindow *, win, &CTX_wm_manager(C)->windows) {
bScreen *screen = BKE_workspace_active_screen_get(win->workspace_hook);
LISTBASE_FOREACH (ScrArea *, area, &screen->areabase) {
if (area->spacetype != SPACE_GRAPH) {
continue;
}
ARegion *window_region = BKE_area_find_region_type(area, RGN_TYPE_WINDOW);
if (!window_region) {
continue;
}
CTX_wm_window_set(C, win);
CTX_wm_screen_set(C, screen);
nathanvegdahl marked this conversation as resolved Outdated

Since rctf is a fairly small plain-old-data struct, I think it would be clearer to simply return it rather than using an output parameter. And then the callee can union it with another rctf if desired, rather than that being an implicit part of this function.

Since `rctf` is a fairly small plain-old-data struct, I think it would be clearer to simply return it rather than using an output parameter. And then the callee can union it with another `rctf` if desired, rather than that being an implicit part of this function.
CTX_wm_area_set(C, area);
CTX_wm_region_set(C, window_region);
found_graph_editor = true;
break;
}
if (found_graph_editor) {
break;
}
}
return found_graph_editor;
}
static void deselect_all_fcurves(bAnimContext *ac, const bool hide)
{
ListBase anim_data = {nullptr, nullptr};
const eAnimFilter_Flags filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_CURVE_VISIBLE |
ANIMFILTER_FCURVESONLY | ANIMFILTER_NODUPLIS);
ANIM_animdata_filter(ac, &anim_data, filter, ac->data, eAnimCont_Types(ac->datatype));
LISTBASE_FOREACH (bAnimListElem *, ale, &anim_data) {
FCurve *fcu = (FCurve *)ale->key_data;
fcu->flag &= ~FCURVE_SELECTED;
fcu->flag &= ~FCURVE_ACTIVE;
if (hide) {
fcu->flag &= ~FCURVE_VISIBLE;
}
}
ANIM_animdata_freelist(&anim_data);
}
static rctf calculate_selection_fcurve_bounds_and_unhide(
bContext *C,
ListBase /* CollectionPointerLink */ *selection,
PropertyRNA *prop,
char *id_to_prop_path,
const int index,
const bool whole_array)
{
rctf bounds;
bounds.xmin = INFINITY;
bounds.xmax = -INFINITY;
bounds.ymin = INFINITY;
bounds.ymax = -INFINITY;
SpaceLink *ge_space_link = CTX_wm_space_data(C);
if (ge_space_link->spacetype != SPACE_GRAPH) {
return bounds;
}
Scene *scene = CTX_data_scene(C);
float frame_range[2];
get_view_range(scene, true, frame_range);
const bool include_handles = false;
nathanvegdahl marked this conversation as resolved Outdated

The name of this function led me to believe that it was side-effect free, but in fact it's unhiding the fcurves as well. So I'm wondering if it makes sense to split this out into functions that serve as clearer vocabulary. For example:

  1. A function that finds the relevant fcurves and returns a Vector of them.
  2. A function that computes the bounds of a vector of fcurves.
  3. A function that unhides a vector of fcurves.

And then you can call them individually. In fact, 3 is probably short enough to just inline rather than making a function for it.

The name of this function led me to believe that it was side-effect free, but in fact it's unhiding the fcurves as well. So I'm wondering if it makes sense to split this out into functions that serve as clearer vocabulary. For example: 1. A function that finds the relevant fcurves and returns a `Vector` of them. 2. A function that computes the bounds of a vector of fcurves. 3. A function that unhides a vector of fcurves. And then you can call them individually. In fact, 3 is probably short enough to just inline rather than making a function for it.

I am not sure I can do that.
To do the NLA mapping, I need the AnimData struct
to get that, I need the ID

If I split the functions to handle a vector of FCurve * I no longer know which ID each FCurve belongs to.

I could make a struct that encapsulates the FCurve * with the AnimData and make a Vector of that.
Or rename the function to something more appropriate.
Let me know what you think

I am not sure I can do that. To do the NLA mapping, I need the `AnimData` struct to get that, I need the `ID` If I split the functions to handle a vector of `FCurve *` I no longer know which ID each FCurve belongs to. I could make a struct that encapsulates the `FCurve *` with the `AnimData` and make a Vector of that. Or rename the function to something more appropriate. Let me know what you think

Ah, that's a good point. Yeah, splitting this up could be tricky.

The functional programmer in me wants to split out the idea of filtering fcurves (i.e. finding the fcurves that satisfy a given criteria) from doing things with those fcurves (e.g. computing their total bounds, or unhiding them, etc.). But that would need to be part of a larger refactor of how a larger chunk of the animation code works, I think, and certainly doesn't belong in this PR even if we want to do it.

So yeah, for now let's just change the name. calculate_selection_fcurve_bounds_and_unhide is fine, I think. It's awkward, but I think that awkwardness is reflective of the multiple things the function is doing. And it's at least descriptive, so people won't accidentally reuse it thinking that all it does is compute bounds.

Ah, that's a good point. Yeah, splitting this up could be tricky. The functional programmer in me wants to split out the idea of filtering fcurves (i.e. finding the fcurves that satisfy a given criteria) from doing things with those fcurves (e.g. computing their total bounds, or unhiding them, etc.). But that would need to be part of a larger refactor of how a larger chunk of the animation code works, I think, and certainly doesn't belong in this PR even if we want to do it. So yeah, for now let's just change the name. `calculate_selection_fcurve_bounds_and_unhide` is fine, I think. It's awkward, but I think that awkwardness is reflective of the multiple things the function is doing. And it's at least descriptive, so people won't accidentally reuse it thinking that all it does is compute bounds.

good idea done that
at some point refactoring all the animation functions to check if they actually need AnimData would be good.
I am pretty sure we can simplify a lot of these functions

good idea done that at some point refactoring all the animation functions to check if they actually need `AnimData` would be good. I am pretty sure we can simplify a lot of these functions
LISTBASE_FOREACH (CollectionPointerLink *, selected, selection) {
ID *selected_id = selected->ptr.owner_id;
if (!BKE_animdata_id_is_animated(selected_id)) {
continue;
}
PointerRNA resolved_ptr;
PropertyRNA *resolved_prop;
if (id_to_prop_path != nullptr) {
const bool resolved = RNA_path_resolve_property(
&selected->ptr, id_to_prop_path, &resolved_ptr, &resolved_prop);
if (!resolved) {
continue;
}
}
else {
resolved_ptr = selected->ptr;
resolved_prop = prop;
}
char *path = RNA_path_from_ID_to_property(&resolved_ptr, resolved_prop);
AnimData *anim_data = BKE_animdata_from_id(selected_id);
blender::Vector<FCurve *> fcurves;
if (RNA_property_array_check(prop) && whole_array) {
const int length = RNA_property_array_length(&selected->ptr, prop);
for (int i = 0; i < length; i++) {
FCurve *fcurve = BKE_animadata_fcurve_find_by_rna_path(
anim_data, path, i, nullptr, nullptr);
if (fcurve != nullptr) {
fcurves.append(fcurve);
}
}
}
else {
FCurve *fcurve = BKE_animadata_fcurve_find_by_rna_path(
anim_data, path, index, nullptr, nullptr);
if (fcurve != nullptr) {
fcurves.append(fcurve);
}
}
MEM_freeN(path);
for (FCurve *fcurve : fcurves) {
fcurve->flag |= (FCURVE_SELECTED | FCURVE_VISIBLE);
rctf fcu_bounds;
float mapped_frame_range[2];
mapped_frame_range[0] = BKE_nla_tweakedit_remap(
anim_data, frame_range[0], NLATIME_CONVERT_UNMAP);
mapped_frame_range[1] = BKE_nla_tweakedit_remap(
anim_data, frame_range[1], NLATIME_CONVERT_UNMAP);
get_normalized_fcurve_bounds(fcurve,
anim_data,
ge_space_link,
scene,
selected_id,
include_handles,
mapped_frame_range,
&fcu_bounds);
BLI_rctf_union(&bounds, &fcu_bounds);
}
}
return bounds;
}
static int view_curve_in_graph_editor_exec(bContext *C, wmOperator *op)
{
PointerRNA ptr = {nullptr};
PropertyRNA *prop = nullptr;
uiBut *but;
int index;
if (!(but = UI_context_active_but_prop_get(C, &ptr, &prop, &index))) {
/* Pass event on if no active button found. */
return (OPERATOR_CANCELLED | OPERATOR_PASS_THROUGH);
}
ListBase selection = {nullptr, nullptr};
bool path_from_id;
char *id_to_prop_path;
const bool selected_list_success = UI_context_copy_to_selected_list(
C, &ptr, prop, &selection, &path_from_id, &id_to_prop_path);
if (BLI_listbase_is_empty(&selection) || !selected_list_success) {
WM_report(RPT_ERROR, "Nothing selected");
BLI_freelistN(&selection);
return OPERATOR_CANCELLED;
}
const bool found_graph_editor = move_context_to_graph_editor(C);
if (!found_graph_editor) {
WM_report(RPT_WARNING, "No open Graph Editor window found");
return OPERATOR_CANCELLED;
}
bAnimContext ac;
if (!ANIM_animdata_get_context(C, &ac)) {
/* This might never be called since we are manually setting the Graph Editor just before. */
WM_report(RPT_ERROR, "Cannot create the Animation Context");
return OPERATOR_CANCELLED;
}
const bool isolate = RNA_boolean_get(op->ptr, "isolate");
deselect_all_fcurves(&ac, isolate);
const bool whole_array = RNA_boolean_get(op->ptr, "all");
rctf bounds = calculate_selection_fcurve_bounds_and_unhide(
C, &selection, prop, id_to_prop_path, index, whole_array);
BLI_freelistN(&selection);
if (id_to_prop_path != nullptr) {
MEM_freeN(id_to_prop_path);
}
if (!BLI_rctf_is_valid(&bounds)) {
WM_report(RPT_ERROR, "F-Curves have no valid size");
return OPERATOR_CANCELLED;
}
ARegion *region = CTX_wm_region(C);
add_region_padding(C, region, &bounds);
const int smooth_viewtx = WM_operator_smooth_viewtx_get(op);
UI_view2d_smooth_view(C, region, &bounds, smooth_viewtx);
/* This ensures the channel list updates. */
ED_area_tag_redraw(CTX_wm_area(C));
return OPERATOR_FINISHED;
}
static void ANIM_OT_view_curve_in_graph_editor(wmOperatorType *ot)
{
/* Identifiers */
ot->name = "View In Graph Editor";
ot->idname = "ANIM_OT_view_curve_in_graph_editor";
ot->description = "Frame the property under the cursor in the Graph Editor";
/* API callbacks */
ot->exec = view_curve_in_graph_editor_exec;
RNA_def_boolean(ot->srna,
"all",
false,
"Show All",
"Frame the whole array property instead of only the index under the cursor");
RNA_def_boolean(ot->srna,
"isolate",
false,
"Isolate",
"Hides all other F-Curves other than the ones being framed");
}
/** \} */
/* -------------------------------------------------------------------- */
@ -4330,6 +4566,7 @@ void ED_operatortypes_animchannels()
WM_operatortype_append(ANIM_OT_channel_view_pick);
WM_operatortype_append(ANIM_OT_channels_view_selected);
WM_operatortype_append(ANIM_OT_view_curve_in_graph_editor);
WM_operatortype_append(ANIM_OT_channels_delete);

View File

@ -655,6 +655,50 @@ bool ui_popup_context_menu_for_button(bContext *C, uiBut *but, const wmEvent *ev
}
}
if (but->flag & UI_BUT_ANIMATED) {
uiItemS(layout);
if (is_array_component) {
PointerRNA op_ptr;
wmOperatorType *ot;
ot = WM_operatortype_find("ANIM_OT_view_curve_in_graph_editor", false);
uiItemFullO_ptr(
layout,
ot,
CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "View Single in Graph Editor"),
ICON_NONE,
nullptr,
WM_OP_INVOKE_DEFAULT,
UI_ITEM_NONE,
&op_ptr);
RNA_boolean_set(&op_ptr, "all", false);
uiItemFullO_ptr(layout,
ot,
CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "View All in Graph Editor"),
ICON_NONE,
nullptr,
WM_OP_INVOKE_DEFAULT,
UI_ITEM_NONE,
&op_ptr);
RNA_boolean_set(&op_ptr, "all", true);
}
else {
PointerRNA op_ptr;
wmOperatorType *ot;
ot = WM_operatortype_find("ANIM_OT_view_curve_in_graph_editor", false);
uiItemFullO_ptr(layout,
ot,
CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "View in Graph Editor"),
ICON_NONE,
nullptr,
WM_OP_INVOKE_DEFAULT,
UI_ITEM_NONE,
&op_ptr);
RNA_boolean_set(&op_ptr, "all", false);
}
}
/* Drivers */
if (but->flag & UI_BUT_DRIVEN) {
uiItemS(layout);