954 lines
29 KiB
C
954 lines
29 KiB
C
/* SPDX-License-Identifier: GPL-2.0-or-later
|
|
* Copyright 2001-2002 NaN Holding BV. All rights reserved. */
|
|
|
|
/** \file
|
|
* \ingroup edtransform
|
|
*/
|
|
|
|
#include "DNA_anim_types.h"
|
|
#include "DNA_gpencil_legacy_types.h"
|
|
#include "DNA_mask_types.h"
|
|
|
|
#include "MEM_guardedalloc.h"
|
|
|
|
#include "BLI_listbase.h"
|
|
#include "BLI_math.h"
|
|
#include "BLI_rect.h"
|
|
|
|
#include "BKE_context.h"
|
|
#include "BKE_fcurve.h"
|
|
#include "BKE_gpencil_legacy.h"
|
|
#include "BKE_key.h"
|
|
#include "BKE_layer.h"
|
|
#include "BKE_mask.h"
|
|
#include "BKE_nla.h"
|
|
|
|
#include "ED_anim_api.h"
|
|
#include "ED_keyframes_edit.h"
|
|
#include "ED_markers.h"
|
|
|
|
#include "WM_api.h"
|
|
#include "WM_types.h"
|
|
|
|
#include "transform.h"
|
|
#include "transform_snap.h"
|
|
|
|
#include "transform_convert.h"
|
|
|
|
/* helper struct for gp-frame transforms */
|
|
typedef struct tGPFtransdata {
|
|
union {
|
|
float val; /* where transdata writes transform */
|
|
float loc[3]; /* #td->val and #td->loc share the same pointer. */
|
|
};
|
|
int *sdata; /* pointer to gpf->framenum */
|
|
} tGPFtransdata;
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Action Transform Creation
|
|
* \{ */
|
|
|
|
/* fully select selected beztriples, but only include if it's on the right side of cfra */
|
|
static int count_fcurve_keys(FCurve *fcu, char side, float cfra, bool is_prop_edit)
|
|
{
|
|
BezTriple *bezt;
|
|
int i, count = 0, count_all = 0;
|
|
|
|
if (ELEM(NULL, fcu, fcu->bezt)) {
|
|
return count;
|
|
}
|
|
|
|
/* only include points that occur on the right side of cfra */
|
|
for (i = 0, bezt = fcu->bezt; i < fcu->totvert; i++, bezt++) {
|
|
if (FrameOnMouseSide(side, bezt->vec[1][0], cfra)) {
|
|
/* no need to adjust the handle selection since they are assumed
|
|
* selected (like graph editor with SIPO_NOHANDLES) */
|
|
if (bezt->f2 & SELECT) {
|
|
count++;
|
|
}
|
|
|
|
count_all++;
|
|
}
|
|
}
|
|
|
|
if (is_prop_edit && count > 0) {
|
|
return count_all;
|
|
}
|
|
return count;
|
|
}
|
|
|
|
/* fully select selected beztriples, but only include if it's on the right side of cfra */
|
|
static int count_gplayer_frames(bGPDlayer *gpl, char side, float cfra, bool is_prop_edit)
|
|
{
|
|
bGPDframe *gpf;
|
|
int count = 0, count_all = 0;
|
|
|
|
if (gpl == NULL) {
|
|
return count;
|
|
}
|
|
|
|
/* only include points that occur on the right side of cfra */
|
|
for (gpf = gpl->frames.first; gpf; gpf = gpf->next) {
|
|
if (FrameOnMouseSide(side, (float)gpf->framenum, cfra)) {
|
|
if (gpf->flag & GP_FRAME_SELECT) {
|
|
count++;
|
|
}
|
|
count_all++;
|
|
}
|
|
}
|
|
|
|
if (is_prop_edit && count > 0) {
|
|
return count_all;
|
|
}
|
|
return count;
|
|
}
|
|
|
|
/* fully select selected beztriples, but only include if it's on the right side of cfra */
|
|
static int count_masklayer_frames(MaskLayer *masklay, char side, float cfra, bool is_prop_edit)
|
|
{
|
|
MaskLayerShape *masklayer_shape;
|
|
int count = 0, count_all = 0;
|
|
|
|
if (masklay == NULL) {
|
|
return count;
|
|
}
|
|
|
|
/* only include points that occur on the right side of cfra */
|
|
for (masklayer_shape = masklay->splines_shapes.first; masklayer_shape;
|
|
masklayer_shape = masklayer_shape->next)
|
|
{
|
|
if (FrameOnMouseSide(side, (float)masklayer_shape->frame, cfra)) {
|
|
if (masklayer_shape->flag & MASK_SHAPE_SELECT) {
|
|
count++;
|
|
}
|
|
count_all++;
|
|
}
|
|
}
|
|
|
|
if (is_prop_edit && count > 0) {
|
|
return count_all;
|
|
}
|
|
return count;
|
|
}
|
|
|
|
/* This function assigns the information to transdata */
|
|
static void TimeToTransData(
|
|
TransData *td, TransData2D *td2d, BezTriple *bezt, AnimData *adt, float ypos)
|
|
{
|
|
float *time = bezt->vec[1];
|
|
|
|
/* Setup #TransData2D. */
|
|
td2d->loc[0] = *time;
|
|
td2d->loc2d = time;
|
|
td2d->h1 = bezt->vec[0];
|
|
td2d->h2 = bezt->vec[2];
|
|
copy_v2_v2(td2d->ih1, td2d->h1);
|
|
copy_v2_v2(td2d->ih2, td2d->h2);
|
|
|
|
/* Setup #TransData. */
|
|
|
|
/* Usually #td2d->loc is used here.
|
|
* But this is for when the original location is not float[3]. */
|
|
td->loc = time;
|
|
|
|
copy_v3_v3(td->iloc, td->loc);
|
|
td->val = time;
|
|
td->ival = *(time);
|
|
td->center[0] = td->ival;
|
|
td->center[1] = ypos;
|
|
|
|
/* Store the AnimData where this keyframe exists as a keyframe of the
|
|
* active action as #td->extra. */
|
|
td->extra = adt;
|
|
|
|
if (bezt->f2 & SELECT) {
|
|
td->flag |= TD_SELECTED;
|
|
}
|
|
|
|
/* Set flags to move handles as necessary. */
|
|
td->flag |= TD_MOVEHANDLE1 | TD_MOVEHANDLE2;
|
|
}
|
|
|
|
/* This function advances the address to which td points to, so it must return
|
|
* the new address so that the next time new transform data is added, it doesn't
|
|
* overwrite the existing ones... i.e. td = IcuToTransData(td, icu, ob, side, cfra);
|
|
*
|
|
* The 'side' argument is needed for the extend mode. 'B' = both sides, 'R'/'L' mean only data
|
|
* on the named side are used.
|
|
*/
|
|
static TransData *ActionFCurveToTransData(TransData *td,
|
|
TransData2D **td2dv,
|
|
FCurve *fcu,
|
|
AnimData *adt,
|
|
char side,
|
|
float cfra,
|
|
bool is_prop_edit,
|
|
float ypos)
|
|
{
|
|
BezTriple *bezt;
|
|
TransData2D *td2d = *td2dv;
|
|
int i;
|
|
|
|
if (ELEM(NULL, fcu, fcu->bezt)) {
|
|
return td;
|
|
}
|
|
|
|
for (i = 0, bezt = fcu->bezt; i < fcu->totvert; i++, bezt++) {
|
|
/* only add selected keyframes (for now, proportional edit is not enabled) */
|
|
if (is_prop_edit || (bezt->f2 & SELECT)) { /* note this MUST match count_fcurve_keys(),
|
|
* so can't use BEZT_ISSEL_ANY() macro */
|
|
/* only add if on the right 'side' of the current frame */
|
|
if (FrameOnMouseSide(side, bezt->vec[1][0], cfra)) {
|
|
TimeToTransData(td, td2d, bezt, adt, ypos);
|
|
|
|
td++;
|
|
td2d++;
|
|
}
|
|
}
|
|
}
|
|
|
|
*td2dv = td2d;
|
|
|
|
return td;
|
|
}
|
|
|
|
/**
|
|
* This function advances the address to which td points to, so it must return
|
|
* the new address so that the next time new transform data is added, it doesn't
|
|
* overwrite the existing ones: e.g. `td += GPLayerToTransData(td, ...);`
|
|
*
|
|
* \param side: is needed for the extend mode. 'B' = both sides,
|
|
* 'R'/'L' mean only data on the named side are used.
|
|
*/
|
|
static int GPLayerToTransData(TransData *td,
|
|
tGPFtransdata *tfd,
|
|
bGPDlayer *gpl,
|
|
char side,
|
|
float cfra,
|
|
bool is_prop_edit,
|
|
float ypos)
|
|
{
|
|
bGPDframe *gpf;
|
|
int count = 0;
|
|
|
|
/* check for select frames on right side of current frame */
|
|
for (gpf = gpl->frames.first; gpf; gpf = gpf->next) {
|
|
const bool is_selected = (gpf->flag & GP_FRAME_SELECT) != 0;
|
|
if (is_prop_edit || is_selected) {
|
|
if (FrameOnMouseSide(side, (float)gpf->framenum, cfra)) {
|
|
tfd->val = (float)gpf->framenum;
|
|
tfd->sdata = &gpf->framenum;
|
|
|
|
td->val = td->loc = &tfd->val;
|
|
td->ival = td->iloc[0] = tfd->val;
|
|
|
|
td->center[0] = td->ival;
|
|
td->center[1] = ypos;
|
|
|
|
if (is_selected) {
|
|
td->flag = TD_SELECTED;
|
|
}
|
|
|
|
/* Advance `td` now. */
|
|
td++;
|
|
tfd++;
|
|
count++;
|
|
}
|
|
}
|
|
}
|
|
|
|
return count;
|
|
}
|
|
|
|
/* refer to comment above #GPLayerToTransData, this is the same but for masks */
|
|
static int MaskLayerToTransData(TransData *td,
|
|
tGPFtransdata *tfd,
|
|
MaskLayer *masklay,
|
|
char side,
|
|
float cfra,
|
|
bool is_prop_edit,
|
|
float ypos)
|
|
{
|
|
MaskLayerShape *masklay_shape;
|
|
int count = 0;
|
|
|
|
/* check for select frames on right side of current frame */
|
|
for (masklay_shape = masklay->splines_shapes.first; masklay_shape;
|
|
masklay_shape = masklay_shape->next)
|
|
{
|
|
if (is_prop_edit || (masklay_shape->flag & MASK_SHAPE_SELECT)) {
|
|
if (FrameOnMouseSide(side, (float)masklay_shape->frame, cfra)) {
|
|
tfd->val = (float)masklay_shape->frame;
|
|
tfd->sdata = &masklay_shape->frame;
|
|
|
|
td->val = td->loc = &tfd->val;
|
|
td->ival = td->iloc[0] = tfd->val;
|
|
|
|
td->center[0] = td->ival;
|
|
td->center[1] = ypos;
|
|
|
|
/* advance td now */
|
|
td++;
|
|
tfd++;
|
|
count++;
|
|
}
|
|
}
|
|
}
|
|
|
|
return count;
|
|
}
|
|
|
|
static void createTransActionData(bContext *C, TransInfo *t)
|
|
{
|
|
Scene *scene = t->scene;
|
|
TransData *td = NULL;
|
|
TransData2D *td2d = NULL;
|
|
tGPFtransdata *tfd = NULL;
|
|
|
|
rcti *mask = &t->region->v2d.mask;
|
|
rctf *datamask = &t->region->v2d.cur;
|
|
|
|
float xsize = BLI_rctf_size_x(datamask);
|
|
float ysize = BLI_rctf_size_y(datamask);
|
|
float xmask = BLI_rcti_size_x(mask);
|
|
float ymask = BLI_rcti_size_y(mask);
|
|
|
|
bAnimContext ac;
|
|
ListBase anim_data = {NULL, NULL};
|
|
bAnimListElem *ale;
|
|
int filter;
|
|
const bool is_prop_edit = (t->flag & T_PROP_EDIT) != 0;
|
|
|
|
int count = 0;
|
|
int gpf_count = 0;
|
|
float cfra;
|
|
float ypos = 1.0f / ((ysize / xsize) * (xmask / ymask)) * BLI_rctf_cent_y(&t->region->v2d.cur);
|
|
|
|
/* determine what type of data we are operating on */
|
|
if (ANIM_animdata_get_context(C, &ac) == 0) {
|
|
return;
|
|
}
|
|
|
|
/* filter data */
|
|
filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_FOREDIT);
|
|
ANIM_animdata_filter(&ac, &anim_data, filter, ac.data, ac.datatype);
|
|
|
|
/* which side of the current frame should be allowed */
|
|
if (t->mode == TFM_TIME_EXTEND) {
|
|
t->frame_side = transform_convert_frame_side_dir_get(t, (float)scene->r.cfra);
|
|
}
|
|
else {
|
|
/* normal transform - both sides of current frame are considered */
|
|
t->frame_side = 'B';
|
|
}
|
|
|
|
/* loop 1: fully select F-Curve keys and count how many BezTriples are selected */
|
|
for (ale = anim_data.first; ale; ale = ale->next) {
|
|
AnimData *adt = ANIM_nla_mapping_get(&ac, ale);
|
|
int adt_count = 0;
|
|
/* convert current-frame to action-time (slightly less accurate, especially under
|
|
* higher scaling ratios, but is faster than converting all points)
|
|
*/
|
|
if (adt) {
|
|
cfra = BKE_nla_tweakedit_remap(adt, (float)scene->r.cfra, NLATIME_CONVERT_UNMAP);
|
|
}
|
|
else {
|
|
cfra = (float)scene->r.cfra;
|
|
}
|
|
|
|
if (ELEM(ale->type, ANIMTYPE_FCURVE, ANIMTYPE_NLACURVE)) {
|
|
adt_count = count_fcurve_keys(ale->key_data, t->frame_side, cfra, is_prop_edit);
|
|
}
|
|
else if (ale->type == ANIMTYPE_GPLAYER) {
|
|
adt_count = count_gplayer_frames(ale->data, t->frame_side, cfra, is_prop_edit);
|
|
}
|
|
else if (ale->type == ANIMTYPE_MASKLAYER) {
|
|
adt_count = count_masklayer_frames(ale->data, t->frame_side, cfra, is_prop_edit);
|
|
}
|
|
else {
|
|
BLI_assert(0);
|
|
}
|
|
|
|
if (adt_count > 0) {
|
|
if (ELEM(ale->type, ANIMTYPE_GPLAYER, ANIMTYPE_MASKLAYER)) {
|
|
gpf_count += adt_count;
|
|
}
|
|
count += adt_count;
|
|
ale->tag = true;
|
|
}
|
|
}
|
|
|
|
/* stop if trying to build list if nothing selected */
|
|
if (count == 0 && gpf_count == 0) {
|
|
/* cleanup temp list */
|
|
ANIM_animdata_freelist(&anim_data);
|
|
return;
|
|
}
|
|
|
|
TransDataContainer *tc = TRANS_DATA_CONTAINER_FIRST_SINGLE(t);
|
|
|
|
/* allocate memory for data */
|
|
tc->data_len = count;
|
|
|
|
tc->data = MEM_callocN(tc->data_len * sizeof(TransData), "TransData(Action Editor)");
|
|
tc->data_2d = MEM_callocN(tc->data_len * sizeof(TransData2D), "transdata2d");
|
|
td = tc->data;
|
|
td2d = tc->data_2d;
|
|
|
|
if (ELEM(ac.datatype, ANIMCONT_GPENCIL, ANIMCONT_MASK, ANIMCONT_DOPESHEET, ANIMCONT_TIMELINE)) {
|
|
tc->data_gpf_len = gpf_count;
|
|
tc->custom.type.data = tfd = MEM_callocN(sizeof(tGPFtransdata) * gpf_count, "tGPFtransdata");
|
|
tc->custom.type.use_free = true;
|
|
}
|
|
|
|
/* loop 2: build transdata array */
|
|
for (ale = anim_data.first; ale; ale = ale->next) {
|
|
|
|
if (is_prop_edit && !ale->tag) {
|
|
continue;
|
|
}
|
|
|
|
cfra = (float)scene->r.cfra;
|
|
|
|
{
|
|
AnimData *adt;
|
|
adt = ANIM_nla_mapping_get(&ac, ale);
|
|
if (adt) {
|
|
cfra = BKE_nla_tweakedit_remap(adt, cfra, NLATIME_CONVERT_UNMAP);
|
|
}
|
|
}
|
|
|
|
if (ale->type == ANIMTYPE_GPLAYER) {
|
|
bGPDlayer *gpl = (bGPDlayer *)ale->data;
|
|
int i;
|
|
|
|
i = GPLayerToTransData(td, tfd, gpl, t->frame_side, cfra, is_prop_edit, ypos);
|
|
td += i;
|
|
tfd += i;
|
|
}
|
|
else if (ale->type == ANIMTYPE_MASKLAYER) {
|
|
MaskLayer *masklay = (MaskLayer *)ale->data;
|
|
int i;
|
|
|
|
i = MaskLayerToTransData(td, tfd, masklay, t->frame_side, cfra, is_prop_edit, ypos);
|
|
td += i;
|
|
tfd += i;
|
|
}
|
|
else {
|
|
AnimData *adt = ANIM_nla_mapping_get(&ac, ale);
|
|
FCurve *fcu = (FCurve *)ale->key_data;
|
|
|
|
td = ActionFCurveToTransData(td, &td2d, fcu, adt, t->frame_side, cfra, is_prop_edit, ypos);
|
|
}
|
|
}
|
|
|
|
/* calculate distances for proportional editing */
|
|
if (is_prop_edit) {
|
|
td = tc->data;
|
|
|
|
for (ale = anim_data.first; ale; ale = ale->next) {
|
|
AnimData *adt;
|
|
|
|
/* F-Curve may not have any keyframes */
|
|
if (!ale->tag) {
|
|
continue;
|
|
}
|
|
|
|
adt = ANIM_nla_mapping_get(&ac, ale);
|
|
if (adt) {
|
|
cfra = BKE_nla_tweakedit_remap(adt, (float)scene->r.cfra, NLATIME_CONVERT_UNMAP);
|
|
}
|
|
else {
|
|
cfra = (float)scene->r.cfra;
|
|
}
|
|
|
|
if (ale->type == ANIMTYPE_GPLAYER) {
|
|
bGPDlayer *gpl = (bGPDlayer *)ale->data;
|
|
bGPDframe *gpf;
|
|
|
|
for (gpf = gpl->frames.first; gpf; gpf = gpf->next) {
|
|
if (gpf->flag & GP_FRAME_SELECT) {
|
|
td->dist = td->rdist = 0.0f;
|
|
}
|
|
else {
|
|
bGPDframe *gpf_iter;
|
|
int min = INT_MAX;
|
|
for (gpf_iter = gpl->frames.first; gpf_iter; gpf_iter = gpf_iter->next) {
|
|
if (gpf_iter->flag & GP_FRAME_SELECT) {
|
|
if (FrameOnMouseSide(t->frame_side, (float)gpf_iter->framenum, cfra)) {
|
|
int val = abs(gpf->framenum - gpf_iter->framenum);
|
|
if (val < min) {
|
|
min = val;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
td->dist = td->rdist = min;
|
|
}
|
|
td++;
|
|
}
|
|
}
|
|
else if (ale->type == ANIMTYPE_MASKLAYER) {
|
|
MaskLayer *masklay = (MaskLayer *)ale->data;
|
|
MaskLayerShape *masklay_shape;
|
|
|
|
for (masklay_shape = masklay->splines_shapes.first; masklay_shape;
|
|
masklay_shape = masklay_shape->next)
|
|
{
|
|
if (FrameOnMouseSide(t->frame_side, (float)masklay_shape->frame, cfra)) {
|
|
if (masklay_shape->flag & MASK_SHAPE_SELECT) {
|
|
td->dist = td->rdist = 0.0f;
|
|
}
|
|
else {
|
|
MaskLayerShape *masklay_iter;
|
|
int min = INT_MAX;
|
|
for (masklay_iter = masklay->splines_shapes.first; masklay_iter;
|
|
masklay_iter = masklay_iter->next) {
|
|
if (masklay_iter->flag & MASK_SHAPE_SELECT) {
|
|
if (FrameOnMouseSide(t->frame_side, (float)masklay_iter->frame, cfra)) {
|
|
int val = abs(masklay_shape->frame - masklay_iter->frame);
|
|
if (val < min) {
|
|
min = val;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
td->dist = td->rdist = min;
|
|
}
|
|
td++;
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
FCurve *fcu = (FCurve *)ale->key_data;
|
|
BezTriple *bezt;
|
|
int i;
|
|
|
|
for (i = 0, bezt = fcu->bezt; i < fcu->totvert; i++, bezt++) {
|
|
if (FrameOnMouseSide(t->frame_side, bezt->vec[1][0], cfra)) {
|
|
if (bezt->f2 & SELECT) {
|
|
td->dist = td->rdist = 0.0f;
|
|
}
|
|
else {
|
|
BezTriple *bezt_iter;
|
|
int j;
|
|
float min = FLT_MAX;
|
|
for (j = 0, bezt_iter = fcu->bezt; j < fcu->totvert; j++, bezt_iter++) {
|
|
if (bezt_iter->f2 & SELECT) {
|
|
if (FrameOnMouseSide(t->frame_side, (float)bezt_iter->vec[1][0], cfra)) {
|
|
float val = fabs(bezt->vec[1][0] - bezt_iter->vec[1][0]);
|
|
if (val < min) {
|
|
min = val;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
td->dist = td->rdist = min;
|
|
}
|
|
td++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* cleanup temp list */
|
|
ANIM_animdata_freelist(&anim_data);
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Action Transform Flush
|
|
* \{ */
|
|
|
|
/* This function helps flush transdata written to tempdata into the gp-frames. */
|
|
static void flushTransIntFrameActionData(TransInfo *t)
|
|
{
|
|
TransDataContainer *tc = TRANS_DATA_CONTAINER_FIRST_SINGLE(t);
|
|
tGPFtransdata *tfd = tc->custom.type.data;
|
|
|
|
/* flush data!
|
|
* Expects data_gpf_len to be set in the data container. */
|
|
for (int i = 0; i < tc->data_gpf_len; i++, tfd++) {
|
|
*(tfd->sdata) = round_fl_to_int(tfd->val);
|
|
}
|
|
}
|
|
|
|
static void recalcData_actedit(TransInfo *t)
|
|
{
|
|
ViewLayer *view_layer = t->view_layer;
|
|
SpaceAction *saction = (SpaceAction *)t->area->spacedata.first;
|
|
|
|
bAnimContext ac = {NULL};
|
|
ListBase anim_data = {NULL, NULL};
|
|
bAnimListElem *ale;
|
|
int filter;
|
|
|
|
BKE_view_layer_synced_ensure(t->scene, t->view_layer);
|
|
|
|
/* initialize relevant anim-context 'context' data from TransInfo data */
|
|
/* NOTE: sync this with the code in ANIM_animdata_get_context() */
|
|
ac.bmain = CTX_data_main(t->context);
|
|
ac.scene = t->scene;
|
|
ac.view_layer = t->view_layer;
|
|
ac.obact = BKE_view_layer_active_object_get(view_layer);
|
|
ac.area = t->area;
|
|
ac.region = t->region;
|
|
ac.sl = (t->area) ? t->area->spacedata.first : NULL;
|
|
ac.spacetype = (t->area) ? t->area->spacetype : 0;
|
|
ac.regiontype = (t->region) ? t->region->regiontype : 0;
|
|
|
|
ANIM_animdata_context_getdata(&ac);
|
|
|
|
/* perform flush */
|
|
if (ELEM(ac.datatype, ANIMCONT_GPENCIL, ANIMCONT_MASK, ANIMCONT_DOPESHEET, ANIMCONT_TIMELINE)) {
|
|
/* flush transform values back to actual coordinates */
|
|
flushTransIntFrameActionData(t);
|
|
}
|
|
|
|
/* Flush 2d vector. */
|
|
TransDataContainer *tc = TRANS_DATA_CONTAINER_FIRST_SINGLE(t);
|
|
const short autosnap = getAnimEdit_SnapMode(t);
|
|
TransData *td;
|
|
TransData2D *td2d;
|
|
int i = 0;
|
|
for (td = tc->data, td2d = tc->data_2d; i < tc->data_len; i++, td++, td2d++) {
|
|
if ((autosnap != SACTSNAP_OFF) && (t->state != TRANS_CANCEL) && !(td->flag & TD_NOTIMESNAP)) {
|
|
transform_snap_anim_flush_data(t, td, autosnap, td->loc);
|
|
}
|
|
|
|
/* Constrain Y. */
|
|
td->loc[1] = td->iloc[1];
|
|
|
|
transform_convert_flush_handle2D(td, td2d, 0.0f);
|
|
}
|
|
|
|
if (ac.datatype != ANIMCONT_MASK) {
|
|
/* Get animdata blocks visible in editor,
|
|
* assuming that these will be the ones where things changed. */
|
|
filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_ANIMDATA);
|
|
ANIM_animdata_filter(&ac, &anim_data, filter, ac.data, ac.datatype);
|
|
|
|
/* just tag these animdata-blocks to recalc, assuming that some data there changed
|
|
* BUT only do this if realtime updates are enabled
|
|
*/
|
|
if ((saction->flag & SACTION_NOREALTIMEUPDATES) == 0) {
|
|
for (ale = anim_data.first; ale; ale = ale->next) {
|
|
/* set refresh tags for objects using this animation */
|
|
ANIM_list_elem_update(CTX_data_main(t->context), t->scene, ale);
|
|
}
|
|
}
|
|
|
|
/* now free temp channels */
|
|
ANIM_animdata_freelist(&anim_data);
|
|
}
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Special After Transform Action
|
|
* \{ */
|
|
|
|
static int masklay_shape_cmp_frame(void *thunk, const void *a, const void *b)
|
|
{
|
|
const MaskLayerShape *frame_a = a;
|
|
const MaskLayerShape *frame_b = b;
|
|
|
|
if (frame_a->frame < frame_b->frame) {
|
|
return -1;
|
|
}
|
|
if (frame_a->frame > frame_b->frame) {
|
|
return 1;
|
|
}
|
|
*((bool *)thunk) = true;
|
|
/* selected last */
|
|
if ((frame_a->flag & MASK_SHAPE_SELECT) && ((frame_b->flag & MASK_SHAPE_SELECT) == 0)) {
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static void posttrans_mask_clean(Mask *mask)
|
|
{
|
|
MaskLayer *masklay;
|
|
|
|
for (masklay = mask->masklayers.first; masklay; masklay = masklay->next) {
|
|
MaskLayerShape *masklay_shape, *masklay_shape_next;
|
|
bool is_double = false;
|
|
|
|
BLI_listbase_sort_r(&masklay->splines_shapes, masklay_shape_cmp_frame, &is_double);
|
|
|
|
if (is_double) {
|
|
for (masklay_shape = masklay->splines_shapes.first; masklay_shape;
|
|
masklay_shape = masklay_shape_next)
|
|
{
|
|
masklay_shape_next = masklay_shape->next;
|
|
if (masklay_shape_next && masklay_shape->frame == masklay_shape_next->frame) {
|
|
BKE_mask_layer_shape_unlink(masklay, masklay_shape);
|
|
}
|
|
}
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
for (masklay_shape = masklay->splines_shapes.first; masklay_shape;
|
|
masklay_shape = masklay_shape->next)
|
|
{
|
|
BLI_assert(!masklay_shape->next || masklay_shape->frame < masklay_shape->next->frame);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
WM_main_add_notifier(NC_MASK | NA_EDITED, mask);
|
|
}
|
|
|
|
/* Called by special_aftertrans_update to make sure selected gp-frames replace
|
|
* any other gp-frames which may reside on that frame (that are not selected).
|
|
* It also makes sure gp-frames are still stored in chronological order after
|
|
* transform.
|
|
*/
|
|
static void posttrans_gpd_clean(bGPdata *gpd)
|
|
{
|
|
bGPDlayer *gpl;
|
|
|
|
for (gpl = gpd->layers.first; gpl; gpl = gpl->next) {
|
|
bGPDframe *gpf, *gpfn;
|
|
bool is_double = false;
|
|
|
|
BKE_gpencil_layer_frames_sort(gpl, &is_double);
|
|
|
|
if (is_double) {
|
|
for (gpf = gpl->frames.first; gpf; gpf = gpfn) {
|
|
gpfn = gpf->next;
|
|
if (gpfn && gpf->framenum == gpfn->framenum) {
|
|
BKE_gpencil_layer_frame_delete(gpl, gpf);
|
|
}
|
|
}
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
for (gpf = gpl->frames.first; gpf; gpf = gpf->next) {
|
|
BLI_assert(!gpf->next || gpf->framenum < gpf->next->framenum);
|
|
}
|
|
#endif
|
|
}
|
|
/* set cache flag to dirty */
|
|
DEG_id_tag_update(&gpd->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY);
|
|
|
|
WM_main_add_notifier(NC_GPENCIL | NA_EDITED, gpd);
|
|
}
|
|
|
|
/* Called by special_aftertrans_update to make sure selected keyframes replace
|
|
* any other keyframes which may reside on that frame (that is not selected).
|
|
* remake_action_ipos should have already been called
|
|
*/
|
|
static void posttrans_action_clean(bAnimContext *ac, bAction *act)
|
|
{
|
|
ListBase anim_data = {NULL, NULL};
|
|
bAnimListElem *ale;
|
|
int filter;
|
|
|
|
/* filter data */
|
|
filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_FOREDIT | ANIMFILTER_FCURVESONLY);
|
|
ANIM_animdata_filter(ac, &anim_data, filter, act, ANIMCONT_ACTION);
|
|
|
|
/* loop through relevant data, removing keyframes as appropriate
|
|
* - all keyframes are converted in/out of global time
|
|
*/
|
|
for (ale = anim_data.first; ale; ale = ale->next) {
|
|
AnimData *adt = ANIM_nla_mapping_get(ac, ale);
|
|
|
|
if (adt) {
|
|
ANIM_nla_mapping_apply_fcurve(adt, ale->key_data, 0, 0);
|
|
BKE_fcurve_merge_duplicate_keys(
|
|
ale->key_data, SELECT, false); /* only use handles in graph editor */
|
|
ANIM_nla_mapping_apply_fcurve(adt, ale->key_data, 1, 0);
|
|
}
|
|
else {
|
|
BKE_fcurve_merge_duplicate_keys(
|
|
ale->key_data, SELECT, false); /* only use handles in graph editor */
|
|
}
|
|
}
|
|
|
|
/* free temp data */
|
|
ANIM_animdata_freelist(&anim_data);
|
|
}
|
|
|
|
static void special_aftertrans_update__actedit(bContext *C, TransInfo *t)
|
|
{
|
|
SpaceAction *saction = (SpaceAction *)t->area->spacedata.first;
|
|
bAnimContext ac;
|
|
|
|
const bool canceled = (t->state == TRANS_CANCEL);
|
|
const bool duplicate = (t->mode == TFM_TIME_DUPLICATE);
|
|
|
|
/* initialize relevant anim-context 'context' data */
|
|
if (ANIM_animdata_get_context(C, &ac) == 0) {
|
|
return;
|
|
}
|
|
|
|
Object *ob = ac.obact;
|
|
|
|
if (ELEM(ac.datatype, ANIMCONT_DOPESHEET, ANIMCONT_SHAPEKEY, ANIMCONT_TIMELINE)) {
|
|
ListBase anim_data = {NULL, NULL};
|
|
bAnimListElem *ale;
|
|
short filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_FOREDIT);
|
|
|
|
/* get channels to work on */
|
|
ANIM_animdata_filter(&ac, &anim_data, filter, ac.data, ac.datatype);
|
|
|
|
for (ale = anim_data.first; ale; ale = ale->next) {
|
|
switch (ale->datatype) {
|
|
case ALE_GPFRAME:
|
|
ale->id->tag &= ~LIB_TAG_DOIT;
|
|
posttrans_gpd_clean((bGPdata *)ale->id);
|
|
break;
|
|
|
|
case ALE_FCURVE: {
|
|
AnimData *adt = ANIM_nla_mapping_get(&ac, ale);
|
|
FCurve *fcu = (FCurve *)ale->key_data;
|
|
|
|
/* 3 cases here for curve cleanups:
|
|
* 1) NOTRANSKEYCULL on -> cleanup of duplicates shouldn't be done
|
|
* 2) canceled == 0 -> user confirmed the transform,
|
|
* so duplicates should be removed
|
|
* 3) canceled + duplicate -> user canceled the transform,
|
|
* but we made duplicates, so get rid of these
|
|
*/
|
|
if ((saction->flag & SACTION_NOTRANSKEYCULL) == 0 && ((canceled == 0) || (duplicate))) {
|
|
if (adt) {
|
|
ANIM_nla_mapping_apply_fcurve(adt, fcu, 0, 0);
|
|
BKE_fcurve_merge_duplicate_keys(
|
|
fcu, SELECT, false); /* only use handles in graph editor */
|
|
ANIM_nla_mapping_apply_fcurve(adt, fcu, 1, 0);
|
|
}
|
|
else {
|
|
BKE_fcurve_merge_duplicate_keys(
|
|
fcu, SELECT, false); /* only use handles in graph editor */
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
default:
|
|
BLI_assert_msg(false, "Keys cannot be transformed into this animation type.");
|
|
}
|
|
}
|
|
|
|
/* free temp memory */
|
|
ANIM_animdata_freelist(&anim_data);
|
|
}
|
|
else if (ac.datatype == ANIMCONT_ACTION) { /* TODO: just integrate into the above. */
|
|
/* Depending on the lock status, draw necessary views */
|
|
/* FIXME: some of this stuff is not good. */
|
|
if (ob) {
|
|
if (ob->pose || BKE_key_from_object(ob)) {
|
|
DEG_id_tag_update(&ob->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY | ID_RECALC_ANIMATION);
|
|
}
|
|
else {
|
|
DEG_id_tag_update(&ob->id, ID_RECALC_TRANSFORM);
|
|
}
|
|
}
|
|
|
|
/* 3 cases here for curve cleanups:
|
|
* 1) NOTRANSKEYCULL on -> cleanup of duplicates shouldn't be done
|
|
* 2) canceled == 0 -> user confirmed the transform,
|
|
* so duplicates should be removed.
|
|
* 3) canceled + duplicate -> user canceled the transform,
|
|
* but we made duplicates, so get rid of these.
|
|
*/
|
|
if ((saction->flag & SACTION_NOTRANSKEYCULL) == 0 && ((canceled == 0) || (duplicate))) {
|
|
posttrans_action_clean(&ac, (bAction *)ac.data);
|
|
}
|
|
}
|
|
else if (ac.datatype == ANIMCONT_GPENCIL) {
|
|
/* remove duplicate frames and also make sure points are in order! */
|
|
/* 3 cases here for curve cleanups:
|
|
* 1) NOTRANSKEYCULL on -> cleanup of duplicates shouldn't be done
|
|
* 2) canceled == 0 -> user confirmed the transform,
|
|
* so duplicates should be removed
|
|
* 3) canceled + duplicate -> user canceled the transform,
|
|
* but we made duplicates, so get rid of these
|
|
*/
|
|
if ((saction->flag & SACTION_NOTRANSKEYCULL) == 0 && ((canceled == 0) || (duplicate))) {
|
|
ListBase anim_data = {NULL, NULL};
|
|
const int filter = ANIMFILTER_DATA_VISIBLE;
|
|
ANIM_animdata_filter(&ac, &anim_data, filter, ac.data, ac.datatype);
|
|
|
|
LISTBASE_FOREACH (bAnimListElem *, ale, &anim_data) {
|
|
if (ale->datatype == ALE_GPFRAME) {
|
|
ale->id->tag &= ~LIB_TAG_DOIT;
|
|
posttrans_gpd_clean((bGPdata *)ale->id);
|
|
}
|
|
}
|
|
ANIM_animdata_freelist(&anim_data);
|
|
}
|
|
}
|
|
else if (ac.datatype == ANIMCONT_MASK) {
|
|
/* remove duplicate frames and also make sure points are in order! */
|
|
/* 3 cases here for curve cleanups:
|
|
* 1) NOTRANSKEYCULL on:
|
|
* Cleanup of duplicates shouldn't be done.
|
|
* 2) canceled == 0:
|
|
* User confirmed the transform, so duplicates should be removed.
|
|
* 3) Canceled + duplicate:
|
|
* User canceled the transform, but we made duplicates, so get rid of these.
|
|
*/
|
|
if ((saction->flag & SACTION_NOTRANSKEYCULL) == 0 && ((canceled == 0) || (duplicate))) {
|
|
ListBase anim_data = {NULL, NULL};
|
|
const int filter = ANIMFILTER_DATA_VISIBLE;
|
|
ANIM_animdata_filter(&ac, &anim_data, filter, ac.data, ac.datatype);
|
|
|
|
LISTBASE_FOREACH (bAnimListElem *, ale, &anim_data) {
|
|
if (ale->datatype == ALE_MASKLAY) {
|
|
ale->id->tag &= ~LIB_TAG_DOIT;
|
|
posttrans_mask_clean((Mask *)ale->id);
|
|
}
|
|
}
|
|
ANIM_animdata_freelist(&anim_data);
|
|
}
|
|
}
|
|
|
|
/* marker transform, not especially nice but we may want to move markers
|
|
* at the same time as keyframes in the dope sheet.
|
|
*/
|
|
if ((saction->flag & SACTION_MARKERS_MOVE) && (canceled == 0)) {
|
|
if (t->mode == TFM_TIME_TRANSLATE) {
|
|
#if 0
|
|
if (ELEM(t->frame_side, 'L', 'R')) { /* TFM_TIME_EXTEND */
|
|
/* same as below */
|
|
ED_markers_post_apply_transform(
|
|
ED_context_get_markers(C), t->scene, t->mode, t->values_final[0], t->frame_side);
|
|
}
|
|
else /* TFM_TIME_TRANSLATE */
|
|
#endif
|
|
{
|
|
ED_markers_post_apply_transform(
|
|
ED_context_get_markers(C), t->scene, t->mode, t->values_final[0], t->frame_side);
|
|
}
|
|
}
|
|
else if (t->mode == TFM_TIME_SCALE) {
|
|
ED_markers_post_apply_transform(
|
|
ED_context_get_markers(C), t->scene, t->mode, t->values_final[0], t->frame_side);
|
|
}
|
|
}
|
|
|
|
/* make sure all F-Curves are set correctly */
|
|
if (!ELEM(ac.datatype, ANIMCONT_GPENCIL)) {
|
|
ANIM_editkeyframes_refresh(&ac);
|
|
}
|
|
|
|
/* clear flag that was set for time-slide drawing */
|
|
saction->flag &= ~SACTION_MOVING;
|
|
}
|
|
|
|
/** \} */
|
|
|
|
TransConvertTypeInfo TransConvertType_Action = {
|
|
/*flags*/ (T_POINTS | T_2D_EDIT),
|
|
/*createTransData*/ createTransActionData,
|
|
/*recalcData*/ recalcData_actedit,
|
|
/*special_aftertrans_update*/ special_aftertrans_update__actedit,
|
|
};
|