This repository has been archived on 2023-10-09. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
blender-archive/source/blender/editors/animation/anim_draw.c
Julian Eisel 585dd63c6e Cleanup: Move RNA path functions into own C++ file
NOTE: This is committed to the 3.3 branch as part of D15606, which we
decided should go to this release still (by Bastien, Dalai and me). That
is because these are important usability fixes/improvements to have for
the LTS release.

Adds `rna_path.cc` and `RNA_path.h`.

`rna_access.c` is a quite big file, which makes it rather hard and
inconvenient to navigate. RNA path functions form a nicely coherent unit
that can stand well on it's own, so it makes sense to split them off to
mitigate the problem. Moreover, I was looking into refactoring the quite
convoluted/overloaded `rna_path_parse()`, and found that some C++
features may help greatly with that. So having that code compile in C++
would be helpful to attempt that.

Differential Revision: https://developer.blender.org/D15540

Reviewed by: Brecht Van Lommel, Campbell Barton, Bastien Montagne
2022-08-04 16:13:00 +02:00

661 lines
19 KiB
C

/* SPDX-License-Identifier: GPL-2.0-or-later
* Copyright 2008 Blender Foundation. All rights reserved. */
/** \file
* \ingroup edanimation
*/
#include "BLI_sys_types.h"
#include "DNA_anim_types.h"
#include "DNA_gpencil_types.h"
#include "DNA_mask_types.h"
#include "DNA_object_types.h"
#include "DNA_scene_types.h"
#include "DNA_screen_types.h"
#include "DNA_space_types.h"
#include "DNA_userdef_types.h"
#include "BLI_dlrbTree.h"
#include "BLI_math.h"
#include "BLI_rect.h"
#include "BLI_timecode.h"
#include "BLI_utildefines.h"
#include "BKE_context.h"
#include "BKE_curve.h"
#include "BKE_fcurve.h"
#include "BKE_global.h"
#include "BKE_mask.h"
#include "BKE_nla.h"
#include "ED_anim_api.h"
#include "ED_keyframes_draw.h"
#include "ED_keyframes_edit.h"
#include "ED_keyframes_keylist.h"
#include "RNA_access.h"
#include "RNA_path.h"
#include "UI_interface.h"
#include "UI_resources.h"
#include "UI_view2d.h"
#include "GPU_immediate.h"
#include "GPU_matrix.h"
#include "GPU_state.h"
/* *************************************************** */
/* CURRENT FRAME DRAWING */
void ANIM_draw_cfra(const bContext *C, View2D *v2d, short flag)
{
Scene *scene = CTX_data_scene(C);
const float time = scene->r.cfra + scene->r.subframe;
const float x = (float)(time * scene->r.framelen);
GPU_line_width((flag & DRAWCFRA_WIDE) ? 3.0 : 2.0);
GPUVertFormat *format = immVertexFormat();
uint pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT);
immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR);
/* Draw a light green line to indicate current frame */
immUniformThemeColor(TH_CFRAME);
immBegin(GPU_PRIM_LINES, 2);
immVertex2f(pos, x, v2d->cur.ymin - 500.0f); /* XXX arbitrary... want it go to bottom */
immVertex2f(pos, x, v2d->cur.ymax);
immEnd();
immUnbindProgram();
}
/* *************************************************** */
/* PREVIEW RANGE 'CURTAINS' */
/* NOTE: 'Preview Range' tools are defined in `anim_ops.c`. */
void ANIM_draw_previewrange(const bContext *C, View2D *v2d, int end_frame_width)
{
Scene *scene = CTX_data_scene(C);
/* only draw this if preview range is set */
if (PRVRANGEON) {
GPU_blend(GPU_BLEND_ALPHA);
GPUVertFormat *format = immVertexFormat();
uint pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT);
immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR);
immUniformThemeColorShadeAlpha(TH_ANIM_PREVIEW_RANGE, -25, -30);
/* XXX: Fix this hardcoded color (anim_active) */
// immUniformColor4f(0.8f, 0.44f, 0.1f, 0.2f);
/* only draw two separate 'curtains' if there's no overlap between them */
if (PSFRA < PEFRA + end_frame_width) {
immRectf(pos, v2d->cur.xmin, v2d->cur.ymin, (float)PSFRA, v2d->cur.ymax);
immRectf(pos, (float)(PEFRA + end_frame_width), v2d->cur.ymin, v2d->cur.xmax, v2d->cur.ymax);
}
else {
immRectf(pos, v2d->cur.xmin, v2d->cur.ymin, v2d->cur.xmax, v2d->cur.ymax);
}
immUnbindProgram();
GPU_blend(GPU_BLEND_NONE);
}
}
/* *************************************************** */
/* SCENE FRAME RANGE */
void ANIM_draw_framerange(Scene *scene, View2D *v2d)
{
/* draw darkened area outside of active timeline frame range */
GPU_blend(GPU_BLEND_ALPHA);
GPUVertFormat *format = immVertexFormat();
uint pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT);
immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR);
immUniformThemeColorShadeAlpha(TH_BACK, -25, -100);
if (scene->r.sfra < scene->r.efra) {
immRectf(pos, v2d->cur.xmin, v2d->cur.ymin, (float)scene->r.sfra, v2d->cur.ymax);
immRectf(pos, (float)scene->r.efra, v2d->cur.ymin, v2d->cur.xmax, v2d->cur.ymax);
}
else {
immRectf(pos, v2d->cur.xmin, v2d->cur.ymin, v2d->cur.xmax, v2d->cur.ymax);
}
GPU_blend(GPU_BLEND_NONE);
/* thin lines where the actual frames are */
immUniformThemeColorShade(TH_BACK, -60);
immBegin(GPU_PRIM_LINES, 4);
immVertex2f(pos, (float)scene->r.sfra, v2d->cur.ymin);
immVertex2f(pos, (float)scene->r.sfra, v2d->cur.ymax);
immVertex2f(pos, (float)scene->r.efra, v2d->cur.ymin);
immVertex2f(pos, (float)scene->r.efra, v2d->cur.ymax);
immEnd();
immUnbindProgram();
}
void ANIM_draw_action_framerange(
AnimData *adt, bAction *action, View2D *v2d, float ymin, float ymax)
{
if ((action->flag & ACT_FRAME_RANGE) == 0) {
return;
}
/* Compute the dimensions. */
CLAMP_MIN(ymin, v2d->cur.ymin);
CLAMP_MAX(ymax, v2d->cur.ymax);
if (ymin > ymax) {
return;
}
const float sfra = BKE_nla_tweakedit_remap(adt, action->frame_start, NLATIME_CONVERT_MAP);
const float efra = BKE_nla_tweakedit_remap(adt, action->frame_end, NLATIME_CONVERT_MAP);
/* Diagonal stripe filled area outside of the frame range. */
GPU_blend(GPU_BLEND_ALPHA);
GPUVertFormat *format = immVertexFormat();
uint pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT);
immBindBuiltinProgram(GPU_SHADER_2D_DIAG_STRIPES);
float color[4];
UI_GetThemeColorShadeAlpha4fv(TH_BACK, -40, -50, color);
immUniform4f("color1", color[0], color[1], color[2], color[3]);
immUniform4f("color2", 0.0f, 0.0f, 0.0f, 0.0f);
immUniform1i("size1", 2 * U.dpi_fac);
immUniform1i("size2", 4 * U.dpi_fac);
if (sfra < efra) {
immRectf(pos, v2d->cur.xmin, ymin, sfra, ymax);
immRectf(pos, efra, ymin, v2d->cur.xmax, ymax);
}
else {
immRectf(pos, v2d->cur.xmin, ymin, v2d->cur.xmax, ymax);
}
immUnbindProgram();
GPU_blend(GPU_BLEND_NONE);
/* Thin lines where the actual frames are. */
immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR);
immUniformThemeColorShade(TH_BACK, -60);
GPU_line_width(1.0f);
immBegin(GPU_PRIM_LINES, 4);
immVertex2f(pos, sfra, ymin);
immVertex2f(pos, sfra, ymax);
immVertex2f(pos, efra, ymin);
immVertex2f(pos, efra, ymax);
immEnd();
immUnbindProgram();
}
/* *************************************************** */
/* NLA-MAPPING UTILITIES (required for drawing and also editing keyframes). */
AnimData *ANIM_nla_mapping_get(bAnimContext *ac, bAnimListElem *ale)
{
/* sanity checks */
if (ac == NULL) {
return NULL;
}
/* abort if rendering - we may get some race condition issues... */
if (G.is_rendering) {
return NULL;
}
/* apart from strictly keyframe-related contexts, this shouldn't even happen */
/* XXX: nla and channel here may not be necessary... */
if (ELEM(ac->datatype,
ANIMCONT_ACTION,
ANIMCONT_SHAPEKEY,
ANIMCONT_DOPESHEET,
ANIMCONT_FCURVES,
ANIMCONT_NLA,
ANIMCONT_CHANNEL)) {
/* handling depends on the type of animation-context we've got */
if (ale) {
/* NLA Control Curves occur on NLA strips,
* and shouldn't be subjected to this kind of mapping. */
if (ale->type != ANIMTYPE_NLACURVE) {
return ale->adt;
}
}
}
/* cannot handle... */
return NULL;
}
/* ------------------- */
/* Helper function for ANIM_nla_mapping_apply_fcurve() -> "restore",
* i.e. mapping points back to action-time. */
static short bezt_nlamapping_restore(KeyframeEditData *ked, BezTriple *bezt)
{
/* AnimData block providing scaling is stored in 'data', only_keys option is stored in i1 */
AnimData *adt = (AnimData *)ked->data;
short only_keys = (short)ked->i1;
/* adjust BezTriple handles only if allowed to */
if (only_keys == 0) {
bezt->vec[0][0] = BKE_nla_tweakedit_remap(adt, bezt->vec[0][0], NLATIME_CONVERT_UNMAP);
bezt->vec[2][0] = BKE_nla_tweakedit_remap(adt, bezt->vec[2][0], NLATIME_CONVERT_UNMAP);
}
bezt->vec[1][0] = BKE_nla_tweakedit_remap(adt, bezt->vec[1][0], NLATIME_CONVERT_UNMAP);
return 0;
}
/* helper function for ANIM_nla_mapping_apply_fcurve() -> "apply",
* i.e. mapping points to NLA-mapped global time */
static short bezt_nlamapping_apply(KeyframeEditData *ked, BezTriple *bezt)
{
/* AnimData block providing scaling is stored in 'data', only_keys option is stored in i1 */
AnimData *adt = (AnimData *)ked->data;
short only_keys = (short)ked->i1;
/* adjust BezTriple handles only if allowed to */
if (only_keys == 0) {
bezt->vec[0][0] = BKE_nla_tweakedit_remap(adt, bezt->vec[0][0], NLATIME_CONVERT_MAP);
bezt->vec[2][0] = BKE_nla_tweakedit_remap(adt, bezt->vec[2][0], NLATIME_CONVERT_MAP);
}
bezt->vec[1][0] = BKE_nla_tweakedit_remap(adt, bezt->vec[1][0], NLATIME_CONVERT_MAP);
return 0;
}
void ANIM_nla_mapping_apply_fcurve(AnimData *adt, FCurve *fcu, bool restore, bool only_keys)
{
KeyframeEditData ked = {{NULL}};
KeyframeEditFunc map_cb;
/* init edit data
* - AnimData is stored in 'data'
* - only_keys is stored in 'i1'
*/
ked.data = (void *)adt;
ked.i1 = (int)only_keys;
/* get editing callback */
if (restore) {
map_cb = bezt_nlamapping_restore;
}
else {
map_cb = bezt_nlamapping_apply;
}
/* apply to F-Curve */
ANIM_fcurve_keyframes_loop(&ked, fcu, NULL, map_cb, NULL);
}
/* *************************************************** */
/* UNITS CONVERSION MAPPING (required for drawing and editing keyframes) */
short ANIM_get_normalization_flags(bAnimContext *ac)
{
if (ac->sl->spacetype == SPACE_GRAPH) {
SpaceGraph *sipo = (SpaceGraph *)ac->sl;
bool use_normalization = (sipo->flag & SIPO_NORMALIZE) != 0;
bool freeze_normalization = (sipo->flag & SIPO_NORMALIZE_FREEZE) != 0;
return use_normalization ? (ANIM_UNITCONV_NORMALIZE |
(freeze_normalization ? ANIM_UNITCONV_NORMALIZE_FREEZE : 0)) :
0;
}
return 0;
}
static float normalization_factor_get(Scene *scene, FCurve *fcu, short flag, float *r_offset)
{
float factor = 1.0f, offset = 0.0f;
if (flag & ANIM_UNITCONV_RESTORE) {
if (r_offset) {
*r_offset = fcu->prev_offset;
}
return 1.0f / fcu->prev_norm_factor;
}
if (flag & ANIM_UNITCONV_NORMALIZE_FREEZE) {
if (r_offset) {
*r_offset = fcu->prev_offset;
}
if (fcu->prev_norm_factor == 0.0f) {
/* Happens when Auto Normalize was disabled before
* any curves were displayed.
*/
return 1.0f;
}
return fcu->prev_norm_factor;
}
if (G.moving & G_TRANSFORM_FCURVES) {
if (r_offset) {
*r_offset = fcu->prev_offset;
}
if (fcu->prev_norm_factor == 0.0f) {
/* Same as above. */
return 1.0f;
}
return fcu->prev_norm_factor;
}
fcu->prev_norm_factor = 1.0f;
if (fcu->bezt) {
const bool use_preview_only = PRVRANGEON;
const BezTriple *bezt;
int i;
float max_coord = -FLT_MAX;
float min_coord = FLT_MAX;
float range;
if (fcu->totvert < 1) {
return 1.0f;
}
for (i = 0, bezt = fcu->bezt; i < fcu->totvert; i++, bezt++) {
if (use_preview_only && !IN_RANGE_INCL(bezt->vec[1][0], scene->r.psfra, scene->r.pefra)) {
continue;
}
if (i == 0) {
/* We ignore extrapolation flags and handle here, and use the
* control point position only. so we normalize "interesting"
* part of the curve.
*
* Here we handle left extrapolation.
*/
max_coord = max_ff(max_coord, bezt->vec[1][1]);
min_coord = min_ff(min_coord, bezt->vec[1][1]);
}
else {
const BezTriple *prev_bezt = bezt - 1;
if (!ELEM(prev_bezt->ipo, BEZT_IPO_BEZ, BEZT_IPO_BACK, BEZT_IPO_ELASTIC)) {
/* The points on the curve will lie inside the start and end points.
* Calculate min/max using both previous and current CV.
*/
max_coord = max_ff(max_coord, bezt->vec[1][1]);
min_coord = min_ff(min_coord, bezt->vec[1][1]);
max_coord = max_ff(max_coord, prev_bezt->vec[1][1]);
min_coord = min_ff(min_coord, prev_bezt->vec[1][1]);
}
else {
const int resol = fcu->driver ?
32 :
min_ii((int)(5.0f * len_v2v2(bezt->vec[1], prev_bezt->vec[1])),
32);
if (resol < 2) {
max_coord = max_ff(max_coord, prev_bezt->vec[1][1]);
min_coord = min_ff(min_coord, prev_bezt->vec[1][1]);
}
else {
if (!ELEM(prev_bezt->ipo, BEZT_IPO_BACK, BEZT_IPO_ELASTIC)) {
/* Calculate min/max using bezier forward differencing. */
float data[120];
float v1[2], v2[2], v3[2], v4[2];
v1[0] = prev_bezt->vec[1][0];
v1[1] = prev_bezt->vec[1][1];
v2[0] = prev_bezt->vec[2][0];
v2[1] = prev_bezt->vec[2][1];
v3[0] = bezt->vec[0][0];
v3[1] = bezt->vec[0][1];
v4[0] = bezt->vec[1][0];
v4[1] = bezt->vec[1][1];
BKE_fcurve_correct_bezpart(v1, v2, v3, v4);
BKE_curve_forward_diff_bezier(
v1[0], v2[0], v3[0], v4[0], data, resol, sizeof(float[3]));
BKE_curve_forward_diff_bezier(
v1[1], v2[1], v3[1], v4[1], data + 1, resol, sizeof(float[3]));
for (int j = 0; j <= resol; ++j) {
const float *fp = &data[j * 3];
max_coord = max_ff(max_coord, fp[1]);
min_coord = min_ff(min_coord, fp[1]);
}
}
else {
/* Calculate min/max using full fcurve evaluation.
* [slower than bezier forward differencing but evaluates Back/Elastic interpolation
* as well]. */
float step_size = (bezt->vec[1][0] - prev_bezt->vec[1][0]) / resol;
for (int j = 0; j <= resol; j++) {
float eval_time = prev_bezt->vec[1][0] + step_size * j;
float eval_value = evaluate_fcurve_only_curve(fcu, eval_time);
max_coord = max_ff(max_coord, eval_value);
min_coord = min_ff(min_coord, eval_value);
}
}
}
}
}
}
if (max_coord > min_coord) {
range = max_coord - min_coord;
if (range > FLT_EPSILON) {
factor = 2.0f / range;
}
offset = -min_coord - range / 2.0f;
}
else if (max_coord == min_coord) {
factor = 1.0f;
offset = -min_coord;
}
}
BLI_assert(factor != 0.0f);
if (r_offset) {
*r_offset = offset;
}
fcu->prev_norm_factor = factor;
fcu->prev_offset = offset;
return factor;
}
float ANIM_unit_mapping_get_factor(Scene *scene, ID *id, FCurve *fcu, short flag, float *r_offset)
{
if (flag & ANIM_UNITCONV_NORMALIZE) {
return normalization_factor_get(scene, fcu, flag, r_offset);
}
if (r_offset) {
*r_offset = 0.0f;
}
/* sanity checks */
if (id && fcu && fcu->rna_path) {
PointerRNA ptr, id_ptr;
PropertyRNA *prop;
/* get RNA property that F-Curve affects */
RNA_id_pointer_create(id, &id_ptr);
if (RNA_path_resolve_property(&id_ptr, fcu->rna_path, &ptr, &prop)) {
/* rotations: radians <-> degrees? */
if (RNA_SUBTYPE_UNIT(RNA_property_subtype(prop)) == PROP_UNIT_ROTATION) {
/* if the radians flag is not set, default to using degrees which need conversions */
if ((scene) && (scene->unit.system_rotation == USER_UNIT_ROT_RADIANS) == 0) {
if (flag & ANIM_UNITCONV_RESTORE) {
return DEG2RADF(1.0f); /* degrees to radians */
}
return RAD2DEGF(1.0f); /* radians to degrees */
}
}
/* TODO: other rotation types here as necessary */
}
}
/* no mapping needs to occur... */
return 1.0f;
}
static bool find_prev_next_keyframes(struct bContext *C, int *r_nextfra, int *r_prevfra)
{
Scene *scene = CTX_data_scene(C);
Object *ob = CTX_data_active_object(C);
Mask *mask = CTX_data_edit_mask(C);
bDopeSheet ads = {NULL};
struct AnimKeylist *keylist = ED_keylist_create();
const ActKeyColumn *aknext, *akprev;
float cfranext, cfraprev;
bool donenext = false, doneprev = false;
int nextcount = 0, prevcount = 0;
cfranext = cfraprev = (float)(scene->r.cfra);
/* seed up dummy dopesheet context with flags to perform necessary filtering */
if ((scene->flag & SCE_KEYS_NO_SELONLY) == 0) {
/* only selected channels are included */
ads.filterflag |= ADS_FILTER_ONLYSEL;
}
/* populate tree with keyframe nodes */
scene_to_keylist(&ads, scene, keylist, 0);
gpencil_to_keylist(&ads, scene->gpd, keylist, false);
if (ob) {
ob_to_keylist(&ads, ob, keylist, 0);
gpencil_to_keylist(&ads, ob->data, keylist, false);
}
if (mask) {
MaskLayer *masklay = BKE_mask_layer_active(mask);
mask_to_keylist(&ads, masklay, keylist);
}
ED_keylist_prepare_for_direct_access(keylist);
/* TODO(jbakker): Keylists are ordered, no need to do any searching at all. */
/* find matching keyframe in the right direction */
do {
aknext = ED_keylist_find_next(keylist, cfranext);
if (aknext) {
if (scene->r.cfra == (int)aknext->cfra) {
/* make this the new starting point for the search and ignore */
cfranext = aknext->cfra;
}
else {
/* this changes the frame, so set the frame and we're done */
if (++nextcount == U.view_frame_keyframes) {
donenext = true;
}
}
cfranext = aknext->cfra;
}
} while ((aknext != NULL) && (donenext == false));
do {
akprev = ED_keylist_find_prev(keylist, cfraprev);
if (akprev) {
if (scene->r.cfra == (int)akprev->cfra) {
/* make this the new starting point for the search */
}
else {
/* this changes the frame, so set the frame and we're done */
if (++prevcount == U.view_frame_keyframes) {
doneprev = true;
}
}
cfraprev = akprev->cfra;
}
} while ((akprev != NULL) && (doneprev == false));
/* free temp stuff */
ED_keylist_free(keylist);
/* any success? */
if (doneprev || donenext) {
if (doneprev) {
*r_prevfra = cfraprev;
}
else {
*r_prevfra = scene->r.cfra - (cfranext - scene->r.cfra);
}
if (donenext) {
*r_nextfra = cfranext;
}
else {
*r_nextfra = scene->r.cfra + (scene->r.cfra - cfraprev);
}
return true;
}
return false;
}
void ANIM_center_frame(struct bContext *C, int smooth_viewtx)
{
ARegion *region = CTX_wm_region(C);
Scene *scene = CTX_data_scene(C);
float w = BLI_rctf_size_x(&region->v2d.cur);
rctf newrct;
int nextfra, prevfra;
switch (U.view_frame_type) {
case ZOOM_FRAME_MODE_SECONDS: {
const float fps = FPS;
newrct.xmax = scene->r.cfra + U.view_frame_seconds * fps + 1;
newrct.xmin = scene->r.cfra - U.view_frame_seconds * fps - 1;
newrct.ymax = region->v2d.cur.ymax;
newrct.ymin = region->v2d.cur.ymin;
break;
}
/* hardest case of all, look for all keyframes around frame and display those */
case ZOOM_FRAME_MODE_KEYFRAMES:
if (find_prev_next_keyframes(C, &nextfra, &prevfra)) {
newrct.xmax = nextfra;
newrct.xmin = prevfra;
newrct.ymax = region->v2d.cur.ymax;
newrct.ymin = region->v2d.cur.ymin;
break;
}
/* else drop through, keep range instead */
ATTR_FALLTHROUGH;
case ZOOM_FRAME_MODE_KEEP_RANGE:
default:
newrct.xmax = scene->r.cfra + (w / 2);
newrct.xmin = scene->r.cfra - (w / 2);
newrct.ymax = region->v2d.cur.ymax;
newrct.ymin = region->v2d.cur.ymin;
break;
}
UI_view2d_smooth_view(C, region, &newrct, smooth_viewtx);
}
/* *************************************************** */