Files
test2/source/blender/editors/space_graph/graph_view.cc
Nathan Vegdahl 1dcd435a87 Fix #130216: GPv3: Crash in render due to slotted Actions
The root cause of this bug can be traced to:

- `ANIM_nla_mapping_get(ac, ale)` conditionally returns an `AnimData *adt`.
- This can be `nullptr` in various cases, depending on the editor (in `ac`) and
  the type & source of data (in `ale`).
- This `nullptr` has different meanings:
  1. There is not enough information to return an `adt` (like `ac` or `ale`
     being `nullptr` themselves).
  2. NLA time remapping should not be done. For example for NLA control F-Curves
     (like animated strip influence), or Grease Pencil (because that doesn't use
     the NLA).
- The above-returned `adt` is passed to other functions. Some of them are aware
  of the "`nullptr` means no NLA time remapping" scenario, and gracefully handle
  it. Other code, however, just gets "an adt" from the caller and handles it as
  normal (and likely crashes on `nullptr`). Other cases start out as the first,
  but somewhere in the call stack shift over to the second.

The approach taken in this PR to fix the bug is to (generally) stop signaling
"do not use NLA time remapping" via `adt = nullptr`, and instead explicitly
indicate/check whether remapping should be done.

In most cases this means passing a `bAnimListElem *` instead of an `AnimData *`,
because the former has the information needed to determine if time remapping
should be done or not. However, in some cases there is no `bAnimListElem *` to
pass, and instead other information determines whether remapping is needed. In
those cases we add a `bool` parameter or field in the appropriate place so that
calling code can explicitly indicate whether remapping should be done or not.

To accomplish this a variety of functions have been added to help handle things
correctly.  Of particular note:

- `AnimData *ANIM_nla_mapping_get(ac, ale)` (that conditionally returned an
  `adt`) has been removed entirely in favor of the new
  `bool ANIM_nla_mapping_allowed(ale)` function that simply returns whether
  nla remapping should be done or not.
- `ANIM_nla_tweakedit_remap(ale, …)` has been added, which wraps
  `BKE_nla_tweakedit_remap(adt, …)` and only performs the remapping when
  `ANIM_nla_mapping_allowed()` indicates that it's allowed.
- `ANIM_nla_mapping_apply_if_needed_fcurve(ale, …)` has been added, which is an
  alternative to `ANIM_nla_mapping_apply_fcurve(adt, …)` that also only performs
  the remapping when `ANIM_nla_mapping_allowed()` indicates that it's allowed.

Note that even with this PR there are still a couple of places remaining that
use `adt = nullptr` to indicate "don't remap", because they appear to be correct
and would require larger changes to make explicit. In those cases comments have
been added to explain the situation, with a reference to this PR.  In the future
we way want to take the time to change those as well.

Also of minor note: this PR moves the definition of the type `slot_handle_t`
from ANIM_action.hh to BKE_action.hh. This is due to `BKE_nla.hh` (which needs
that definition) now being included directly and indirectly in a lot more
places. Moving the definition to BKE_action.hh prevents all of those new places
from gaining dependencies on the animrig module.

Co-authored-by: Sybren A. Stüvel <sybren@blender.org>
Pull Request: https://projects.blender.org/blender/blender/pulls/130440
2024-11-21 16:37:13 +01:00

544 lines
15 KiB
C++

/* SPDX-FileCopyrightText: 2020 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup spgraph
*/
#include <cmath>
#include "MEM_guardedalloc.h"
#include "BLI_listbase.h"
#include "BLI_rect.h"
#include "DNA_anim_types.h"
#include "DNA_scene_types.h"
#include "DNA_space_types.h"
#include "RNA_access.hh"
#include "RNA_define.hh"
#include "BKE_context.hh"
#include "BKE_fcurve.hh"
#include "BKE_nla.hh"
#include "UI_view2d.hh"
#include "ED_anim_api.hh"
#include "ED_markers.hh"
#include "ED_screen.hh"
#include "WM_api.hh"
#include "WM_types.hh"
#include "graph_intern.hh"
/* -------------------------------------------------------------------- */
/** \name Calculate Range
* \{ */
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;
ListBase anim_data = {nullptr, nullptr};
int filter;
/* Get data to filter, from Dopesheet. */
filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_CURVE_VISIBLE | ANIMFILTER_FCURVESONLY |
ANIMFILTER_NODUPLIS);
if (U.animation_flag & USER_ANIM_ONLY_SHOW_SELECTED_CURVE_KEYS) {
filter |= ANIMFILTER_SEL;
}
ANIM_animdata_filter(
ac, &anim_data, eAnimFilter_Flags(filter), ac->data, eAnimCont_Types(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. */
LISTBASE_FOREACH (bAnimListElem *, ale, &anim_data) {
FCurve *fcu = (FCurve *)ale->key_data;
rctf bounds;
float unitFac, offset;
/* Get range. */
if (BKE_fcurve_calc_bounds(fcu, do_sel_only, include_handles, nullptr, &bounds)) {
short mapping_flag = ANIM_get_normalization_flags(ac->sl);
/* Apply NLA scaling. */
bounds.xmin = ANIM_nla_tweakedit_remap(ale, bounds.xmin, NLATIME_CONVERT_MAP);
bounds.xmax = ANIM_nla_tweakedit_remap(ale, bounds.xmax, NLATIME_CONVERT_MAP);
/* Apply unit corrections. */
unitFac = ANIM_unit_mapping_get_factor(ac->scene, ale->id, fcu, mapping_flag, &offset);
bounds.ymin += offset;
bounds.ymax += offset;
bounds.ymin *= unitFac;
bounds.ymax *= unitFac;
/* Try to set cur using these values, if they're more extreme than previously set values.
*/
if ((xmin) && (bounds.xmin < *xmin)) {
*xmin = bounds.xmin;
}
if ((xmax) && (bounds.xmax > *xmax)) {
*xmax = bounds.xmax;
}
if ((ymin) && (bounds.ymin < *ymin)) {
*ymin = bounds.ymin;
}
if ((ymax) && (bounds.ymax > *ymax)) {
*ymax = bounds.ymax;
}
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)) {
*ymin -= 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;
}
}
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Automatic Preview-Range Operator
* \{ */
static int graphkeys_previewrange_exec(bContext *C, wmOperator * /*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 == nullptr) {
return OPERATOR_CANCELLED;
}
scene = ac.scene;
/* Set the range directly. */
get_graph_keyframe_extents(&ac, &min, &max, nullptr, nullptr, true, 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 = "Set Preview Range to Selected";
ot->idname = "GRAPH_OT_previewrange_set";
ot->description = "Set Preview Range based on range of selected keyframes";
/* API callbacks */
ot->exec = graphkeys_previewrange_exec;
/* XXX: unchecked poll to get F-samples working too, but makes modifier damage trickier. */
ot->poll = ED_operator_graphedit_active;
/* Flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name 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. */
cur_new = ANIM_frame_range_view2d_add_xmargin(ac.region->v2d, cur_new);
BLI_rctf_resize_y(&cur_new, 1.1f * BLI_rctf_size_y(&cur_new));
/* 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 F-samples 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 F-samples 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");
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name 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;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name 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 = {nullptr, nullptr};
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_FCURVESONLY |
ANIMFILTER_SEL | ANIMFILTER_NODUPLIS);
ANIM_animdata_filter(
ac, &anim_data, eAnimFilter_Flags(filter), ac->data, eAnimCont_Types(ac->datatype));
/* Loop through filtered data and add keys between selected keyframes on every frame. */
LISTBASE_FOREACH (bAnimListElem *, ale, &anim_data) {
FCurve *fcu = (FCurve *)ale->key_data;
FCurve *gcu = BKE_fcurve_create();
ChannelDriver *driver = fcu->driver;
FPoint *fpt;
float unitFac, offset;
int cfra;
short mapping_flag = ANIM_get_normalization_flags(ac->sl);
/* Disable driver so that it don't muck up the sampling process. */
fcu->driver = nullptr;
/* 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 = static_cast<FPoint *>(
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++) {
const float cfrae = ANIM_nla_tweakedit_remap(ale, cfra, NLATIME_CONVERT_UNMAP);
fpt->vec[0] = cfrae;
fpt->vec[1] = (fcurve_samplingcb_evalcurve(fcu, nullptr, 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 * /*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 */
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Clear Ghost-Curves Operator
*
* This operator clears the 'ghost curves' for the active Graph Editor.
* \{ */
static int graphkeys_clear_ghostcurves_exec(bContext *C, wmOperator * /*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;
}
/** \} */