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/space_action/action_edit.c
Campbell Barton 66f6ace938 fix for action editor view-selected behaving strangely.
- when an fcurve had no selected keyframes, a default fallback value was used which caused view-selected to include frame 1, even when no selected frames were there.

- the vertical axis was always reset, ideally we would center vertically too but the way this operator currently works we only know about the frame range,
  now don't change the vertical scroll when viewing selected since it would always jump to the top of the screen (view-all still acts this way).
2013-07-18 02:59:28 +00:00

1620 lines
46 KiB
C

/*
* ***** BEGIN GPL LICENSE BLOCK *****
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* The Original Code is Copyright (C) 2001-2002 by NaN Holding BV.
* All rights reserved.
*
* The Original Code is: all of this file.
*
* Contributor(s): Joshua Leung
*
* ***** END GPL LICENSE BLOCK *****
*/
/** \file blender/editors/space_action/action_edit.c
* \ingroup spaction
*/
#include <math.h>
#include <stdlib.h>
#include <string.h>
#include <float.h>
#include "BLI_blenlib.h"
#include "BLI_math.h"
#include "BLI_utildefines.h"
#include "DNA_anim_types.h"
#include "DNA_gpencil_types.h"
#include "DNA_object_types.h"
#include "DNA_scene_types.h"
#include "DNA_mask_types.h"
#include "RNA_access.h"
#include "RNA_define.h"
#include "RNA_enum_types.h"
#include "BKE_action.h"
#include "BKE_fcurve.h"
#include "BKE_global.h"
#include "BKE_main.h"
#include "BKE_nla.h"
#include "BKE_context.h"
#include "BKE_report.h"
#include "UI_view2d.h"
#include "ED_anim_api.h"
#include "ED_gpencil.h"
#include "ED_keyframing.h"
#include "ED_keyframes_edit.h"
#include "ED_screen.h"
#include "ED_transform.h"
#include "ED_markers.h"
#include "ED_mask.h"
#include "WM_api.h"
#include "WM_types.h"
#include "UI_interface.h"
#include "action_intern.h"
/* ************************************************************************** */
/* ACTION MANAGEMENT */
/* ******************** New Action Operator *********************** */
static int act_new_exec(bContext *C, wmOperator *UNUSED(op))
{
PointerRNA ptr, idptr;
PropertyRNA *prop;
/* hook into UI */
uiIDContextProperty(C, &ptr, &prop);
if (prop) {
bAction *action = NULL, *oldact = NULL;
PointerRNA oldptr;
/* create action - the way to do this depends on whether we've got an
* existing one there already, in which case we make a copy of it
* (which is useful for "versioning" actions within the same file)
*/
oldptr = RNA_property_pointer_get(&ptr, prop);
oldact = (bAction *)oldptr.id.data;
if (oldact && GS(oldact->id.name) == ID_AC) {
/* make a copy of the existing action */
action = BKE_action_copy(oldact);
}
else {
Main *bmain = CTX_data_main(C);
/* just make a new (empty) action */
action = add_empty_action(bmain, "Action");
}
/* when creating new ID blocks, use is already 1 (fake user),
* but RNA pointer use also increases user, so this compensates it
*/
action->id.us--;
RNA_id_pointer_create(&action->id, &idptr);
RNA_property_pointer_set(&ptr, prop, idptr);
RNA_property_update(C, &ptr, prop);
}
/* set notifier that keyframes have changed */
WM_event_add_notifier(C, NC_ANIMATION | ND_KEYFRAME | NA_EDITED, NULL);
return OPERATOR_FINISHED;
}
void ACTION_OT_new(wmOperatorType *ot)
{
/* identifiers */
ot->name = "New Action";
ot->idname = "ACTION_OT_new";
ot->description = "Create new action";
/* api callbacks */
ot->exec = act_new_exec;
/* NOTE: this is used in the NLA too... */
//ot->poll = ED_operator_action_active;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
}
/* ************************************************************************** */
/* POSE MARKERS STUFF */
/* *************************** Localise Markers ***************************** */
/* ensure that there is:
* 1) an active action editor
* 2) that the mode will have an active action available
* 3) that the set of markers being shown are the scene markers, not the list we're merging
* 4) that there are some selected markers
*/
static int act_markers_make_local_poll(bContext *C)
{
SpaceAction *sact = CTX_wm_space_action(C);
/* 1) */
if (sact == NULL)
return 0;
/* 2) */
if (ELEM(sact->mode, SACTCONT_ACTION, SACTCONT_SHAPEKEY) == 0)
return 0;
if (sact->action == NULL)
return 0;
/* 3) */
if (sact->flag & SACTION_POSEMARKERS_SHOW)
return 0;
/* 4) */
return ED_markers_get_first_selected(ED_context_get_markers(C)) != NULL;
}
static int act_markers_make_local_exec(bContext *C, wmOperator *UNUSED(op))
{
ListBase *markers = ED_context_get_markers(C);
SpaceAction *sact = CTX_wm_space_action(C);
bAction *act = (sact) ? sact->action : NULL;
TimeMarker *marker, *markern = NULL;
/* sanity checks */
if (ELEM(NULL, markers, act))
return OPERATOR_CANCELLED;
/* migrate markers */
for (marker = markers->first; marker; marker = markern) {
markern = marker->next;
/* move if marker is selected */
if (marker->flag & SELECT) {
BLI_remlink(markers, marker);
BLI_addtail(&act->markers, marker);
}
}
/* now enable the "show posemarkers only" setting, so that we can see that something did happen */
sact->flag |= SACTION_POSEMARKERS_SHOW;
/* notifiers - both sets, as this change affects both */
WM_event_add_notifier(C, NC_SCENE | ND_MARKERS, NULL);
WM_event_add_notifier(C, NC_ANIMATION | ND_MARKERS, NULL);
return OPERATOR_FINISHED;
}
void ACTION_OT_markers_make_local(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Make Markers Local";
ot->idname = "ACTION_OT_markers_make_local";
ot->description = "Move selected scene markers to the active Action as local 'pose' markers";
/* callbacks */
ot->exec = act_markers_make_local_exec;
ot->poll = act_markers_make_local_poll;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
}
/* ************************************************************************** */
/* KEYFRAME-RANGE STUFF */
/* *************************** Calculate Range ************************** */
/* Get the min/max keyframes*/
static bool get_keyframe_extents(bAnimContext *ac, float *min, float *max, const short onlySel)
{
ListBase anim_data = {NULL, NULL};
bAnimListElem *ale;
int filter;
bool found = false;
/* get data to filter, from Action or Dopesheet */
/* XXX: what is sel doing here?!
* Commented it, was breaking things (eg. the "auto preview range" tool). */
filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE /*| ANIMFILTER_SEL *//*| ANIMFILTER_CURVESONLY*/ | ANIMFILTER_NODUPLIS);
ANIM_animdata_filter(ac, &anim_data, filter, ac->data, ac->datatype);
/* set large values to try to override */
*min = 999999999.0f;
*max = -999999999.0f;
/* check if any channels to set range with */
if (anim_data.first) {
/* go through channels, finding max extents */
for (ale = anim_data.first; ale; ale = ale->next) {
AnimData *adt = ANIM_nla_mapping_get(ac, ale);
if (ale->datatype == ALE_GPFRAME) {
bGPDlayer *gpl = ale->data;
bGPDframe *gpf;
/* find gp-frame which is less than or equal to cframe */
for (gpf = gpl->frames.first; gpf; gpf = gpf->next) {
const float framenum = (float)gpf->framenum;
*min = min_ff(*min, framenum);
*max = max_ff(*max, framenum);
found = true;
}
}
else if (ale->datatype == ALE_MASKLAY) {
MaskLayer *masklay = ale->data;
MaskLayerShape *masklay_shape;
/* find mask layer which is less than or equal to cframe */
for (masklay_shape = masklay->splines_shapes.first;
masklay_shape;
masklay_shape = masklay_shape->next)
{
const float framenum = (float)masklay_shape->frame;
*min = min_ff(*min, framenum);
*max = max_ff(*max, framenum);
found = true;
}
}
else {
FCurve *fcu = (FCurve *)ale->key_data;
float tmin, tmax;
/* get range and apply necessary scaling before processing */
if (calc_fcurve_range(fcu, &tmin, &tmax, onlySel, TRUE)) {
if (adt) {
tmin = BKE_nla_tweakedit_remap(adt, tmin, NLATIME_CONVERT_MAP);
tmax = BKE_nla_tweakedit_remap(adt, tmax, NLATIME_CONVERT_MAP);
}
/* try to set cur using these values, if they're more extreme than previously set values */
*min = min_ff(*min, tmin);
*max = max_ff(*max, tmax);
found = true;
}
}
}
/* free memory */
BLI_freelistN(&anim_data);
}
else {
/* set default range */
if (ac->scene) {
*min = (float)ac->scene->r.sfra;
*max = (float)ac->scene->r.efra;
}
else {
*min = -5;
*max = 100;
}
}
return found;
}
/* ****************** Automatic Preview-Range Operator ****************** */
static int actkeys_previewrange_exec(bContext *C, wmOperator *UNUSED(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 == NULL)
return OPERATOR_CANCELLED;
else
scene = ac.scene;
/* set the range directly */
get_keyframe_extents(&ac, &min, &max, FALSE);
scene->r.flag |= SCER_PRV_RANGE;
scene->r.psfra = (int)floor(min + 0.5f);
scene->r.pefra = (int)floor(max + 0.5f);
/* 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 ACTION_OT_previewrange_set(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Auto-Set Preview Range";
ot->idname = "ACTION_OT_previewrange_set";
ot->description = "Set Preview Range based on extents of selected Keyframes";
/* api callbacks */
ot->exec = actkeys_previewrange_exec;
ot->poll = ED_operator_action_active;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
}
/* ****************** View-All Operator ****************** */
static int actkeys_viewall(bContext *C, const bool only_sel, const bool only_xaxis)
{
bAnimContext ac;
View2D *v2d;
float extra;
bool found;
/* get editor data */
if (ANIM_animdata_get_context(C, &ac) == 0)
return OPERATOR_CANCELLED;
v2d = &ac.ar->v2d;
/* set the horizontal range, with an extra offset so that the extreme keys will be in view */
found = get_keyframe_extents(&ac, &v2d->cur.xmin, &v2d->cur.xmax, only_sel);
if (only_sel && (found == false))
return OPERATOR_CANCELLED;
extra = 0.1f * BLI_rctf_size_x(&v2d->cur);
v2d->cur.xmin -= extra;
v2d->cur.xmax += extra;
/* set vertical range */
if (only_xaxis == false) {
v2d->cur.ymax = 0.0f;
v2d->cur.ymin = (float)-BLI_rcti_size_y(&v2d->mask);
}
/* do View2D syncing */
UI_view2d_sync(CTX_wm_screen(C), CTX_wm_area(C), v2d, V2D_LOCK_COPY);
/* just redraw this view */
ED_area_tag_redraw(CTX_wm_area(C));
return OPERATOR_FINISHED;
}
/* ......... */
static int actkeys_viewall_exec(bContext *C, wmOperator *UNUSED(op))
{
/* whole range */
return actkeys_viewall(C, false, false);
}
static int actkeys_viewsel_exec(bContext *C, wmOperator *UNUSED(op))
{
/* only selected */
return actkeys_viewall(C, true, true);
}
void ACTION_OT_view_all(wmOperatorType *ot)
{
/* identifiers */
ot->name = "View All";
ot->idname = "ACTION_OT_view_all";
ot->description = "Reset viewable area to show full keyframe range";
/* api callbacks */
ot->exec = actkeys_viewall_exec;
ot->poll = ED_operator_action_active;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
}
void ACTION_OT_view_selected(wmOperatorType *ot)
{
/* identifiers */
ot->name = "View Selected";
ot->idname = "ACTION_OT_view_selected";
ot->description = "Reset viewable area to show selected keyframes range";
/* api callbacks */
ot->exec = actkeys_viewsel_exec;
ot->poll = ED_operator_action_active;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
}
/* ************************************************************************** */
/* GENERAL STUFF */
/* ******************** Copy/Paste Keyframes Operator ************************* */
/* NOTE: the backend code for this is shared with the graph editor */
static short copy_action_keys(bAnimContext *ac)
{
ListBase anim_data = {NULL, NULL};
int filter, ok = 0;
/* clear buffer first */
free_anim_copybuf();
/* filter data */
filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE /*| ANIMFILTER_CURVESONLY*/ | ANIMFILTER_NODUPLIS);
ANIM_animdata_filter(ac, &anim_data, filter, ac->data, ac->datatype);
/* copy keyframes */
ok = copy_animedit_keys(ac, &anim_data);
/* clean up */
BLI_freelistN(&anim_data);
return ok;
}
static short paste_action_keys(bAnimContext *ac,
const eKeyPasteOffset offset_mode, const eKeyMergeMode merge_mode)
{
ListBase anim_data = {NULL, NULL};
int filter, ok = 0;
/* filter data
* - First time we try to filter more strictly, allowing only selected channels
* to allow copying animation between channels
* - Second time, we loosen things up if nothing was found the first time, allowing
* users to just paste keyframes back into the original curve again [#31670]
*/
filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE | ANIMFILTER_FOREDIT /*| ANIMFILTER_CURVESONLY*/ | ANIMFILTER_NODUPLIS);
if (ANIM_animdata_filter(ac, &anim_data, filter | ANIMFILTER_SEL, ac->data, ac->datatype) == 0)
ANIM_animdata_filter(ac, &anim_data, filter, ac->data, ac->datatype);
/* paste keyframes */
ok = paste_animedit_keys(ac, &anim_data, offset_mode, merge_mode);
/* clean up */
BLI_freelistN(&anim_data);
return ok;
}
/* ------------------- */
static int actkeys_copy_exec(bContext *C, wmOperator *op)
{
bAnimContext ac;
/* get editor data */
if (ANIM_animdata_get_context(C, &ac) == 0)
return OPERATOR_CANCELLED;
/* copy keyframes */
if (ac.datatype == ANIMCONT_GPENCIL) {
/* FIXME... */
BKE_report(op->reports, RPT_ERROR, "Keyframe pasting is not available for grease pencil mode");
return OPERATOR_CANCELLED;
}
else if (ac.datatype == ANIMCONT_MASK) {
/* FIXME... */
BKE_report(op->reports, RPT_ERROR, "Keyframe pasting is not available for mask mode");
return OPERATOR_CANCELLED;
}
else {
if (copy_action_keys(&ac)) {
BKE_report(op->reports, RPT_ERROR, "No keyframes copied to keyframes copy/paste buffer");
return OPERATOR_CANCELLED;
}
}
return OPERATOR_FINISHED;
}
void ACTION_OT_copy(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Copy Keyframes";
ot->idname = "ACTION_OT_copy";
ot->description = "Copy selected keyframes to the copy/paste buffer";
/* api callbacks */
ot->exec = actkeys_copy_exec;
ot->poll = ED_operator_action_active;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
}
static int actkeys_paste_exec(bContext *C, wmOperator *op)
{
bAnimContext ac;
const eKeyPasteOffset offset_mode = RNA_enum_get(op->ptr, "offset");
const eKeyMergeMode merge_mode = RNA_enum_get(op->ptr, "merge");
/* get editor data */
if (ANIM_animdata_get_context(C, &ac) == 0)
return OPERATOR_CANCELLED;
/* ac.reports by default will be the global reports list, which won't show warnings */
ac.reports = op->reports;
/* paste keyframes */
if (ELEM(ac.datatype, ANIMCONT_GPENCIL, ANIMCONT_MASK)) {
/* FIXME... */
BKE_report(op->reports, RPT_ERROR, "Keyframe pasting is not available for grease pencil or mask mode");
return OPERATOR_CANCELLED;
}
else {
/* non-zero return means an error occurred while trying to paste */
if (paste_action_keys(&ac, offset_mode, merge_mode)) {
return OPERATOR_CANCELLED;
}
}
/* validate keyframes after editing */
ANIM_editkeyframes_refresh(&ac);
/* set notifier that keyframes have changed */
WM_event_add_notifier(C, NC_ANIMATION | ND_KEYFRAME | NA_EDITED, NULL);
return OPERATOR_FINISHED;
}
void ACTION_OT_paste(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Paste Keyframes";
ot->idname = "ACTION_OT_paste";
ot->description = "Paste keyframes from copy/paste buffer for the selected channels, starting on the current frame";
/* api callbacks */
// ot->invoke = WM_operator_props_popup; // better wait for action redo panel
ot->exec = actkeys_paste_exec;
ot->poll = ED_operator_action_active;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
/* props */
RNA_def_enum(ot->srna, "offset", keyframe_paste_offset_items, KEYFRAME_PASTE_OFFSET_CFRA_START, "Offset", "Paste time offset of keys");
RNA_def_enum(ot->srna, "merge", keyframe_paste_merge_items, KEYFRAME_PASTE_MERGE_MIX, "Type", "Method of merging pasted keys and existing");
}
/* ******************** Insert Keyframes Operator ************************* */
/* defines for insert keyframes tool */
static EnumPropertyItem prop_actkeys_insertkey_types[] = {
{1, "ALL", 0, "All Channels", ""},
{2, "SEL", 0, "Only Selected Channels", ""},
{3, "GROUP", 0, "In Active Group", ""}, /* XXX not in all cases */
{0, NULL, 0, NULL, NULL}
};
/* this function is responsible for snapping keyframes to frame-times */
static void insert_action_keys(bAnimContext *ac, short mode)
{
ListBase anim_data = {NULL, NULL};
bAnimListElem *ale;
int filter;
ReportList *reports = ac->reports;
Scene *scene = ac->scene;
short flag = 0;
/* filter data */
filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE | ANIMFILTER_FOREDIT /*| ANIMFILTER_CURVESONLY*/ | ANIMFILTER_NODUPLIS);
if (mode == 2) filter |= ANIMFILTER_SEL;
else if (mode == 3) filter |= ANIMFILTER_ACTGROUPED;
ANIM_animdata_filter(ac, &anim_data, filter, ac->data, ac->datatype);
/* init keyframing flag */
flag = ANIM_get_keyframing_flags(scene, 1);
/* insert keyframes */
for (ale = anim_data.first; ale; ale = ale->next) {
AnimData *adt = ANIM_nla_mapping_get(ac, ale);
FCurve *fcu = (FCurve *)ale->key_data;
float cfra;
/* adjust current frame for NLA-scaling */
if (adt)
cfra = BKE_nla_tweakedit_remap(adt, (float)CFRA, NLATIME_CONVERT_UNMAP);
else
cfra = (float)CFRA;
/* if there's an id */
if (ale->id)
insert_keyframe(reports, ale->id, NULL, ((fcu->grp) ? (fcu->grp->name) : (NULL)), fcu->rna_path, fcu->array_index, cfra, flag);
else
insert_vert_fcurve(fcu, cfra, fcu->curval, 0);
}
BLI_freelistN(&anim_data);
}
/* ------------------- */
static int actkeys_insertkey_exec(bContext *C, wmOperator *op)
{
bAnimContext ac;
short mode;
/* get editor data */
if (ANIM_animdata_get_context(C, &ac) == 0)
return OPERATOR_CANCELLED;
if (ELEM(ac.datatype, ANIMCONT_GPENCIL, ANIMCONT_MASK))
return OPERATOR_CANCELLED;
/* what channels to affect? */
mode = RNA_enum_get(op->ptr, "type");
/* insert keyframes */
insert_action_keys(&ac, mode);
/* validate keyframes after editing */
ANIM_editkeyframes_refresh(&ac);
/* set notifier that keyframes have changed */
WM_event_add_notifier(C, NC_ANIMATION | ND_KEYFRAME | NA_EDITED, NULL);
return OPERATOR_FINISHED;
}
void ACTION_OT_keyframe_insert(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Insert Keyframes";
ot->idname = "ACTION_OT_keyframe_insert";
ot->description = "Insert keyframes for the specified channels";
/* api callbacks */
ot->invoke = WM_menu_invoke;
ot->exec = actkeys_insertkey_exec;
ot->poll = ED_operator_action_active;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
/* id-props */
ot->prop = RNA_def_enum(ot->srna, "type", prop_actkeys_insertkey_types, 0, "Type", "");
}
/* ******************** Duplicate Keyframes Operator ************************* */
static void duplicate_action_keys(bAnimContext *ac)
{
ListBase anim_data = {NULL, NULL};
bAnimListElem *ale;
int filter;
/* filter data */
if (ELEM(ac->datatype, ANIMCONT_GPENCIL, ANIMCONT_MASK))
filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE | ANIMFILTER_FOREDIT | ANIMFILTER_NODUPLIS);
else
filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE | ANIMFILTER_FOREDIT /*| ANIMFILTER_CURVESONLY*/ | ANIMFILTER_NODUPLIS);
ANIM_animdata_filter(ac, &anim_data, filter, ac->data, ac->datatype);
/* loop through filtered data and delete selected keys */
for (ale = anim_data.first; ale; ale = ale->next) {
if (ale->type == ANIMTYPE_FCURVE)
duplicate_fcurve_keys((FCurve *)ale->key_data);
else if (ale->type == ANIMTYPE_GPLAYER)
ED_gplayer_frames_duplicate((bGPDlayer *)ale->data);
else if (ale->type == ANIMTYPE_MASKLAYER)
ED_masklayer_frames_duplicate((MaskLayer *)ale->data);
else
BLI_assert(0);
}
/* free filtered list */
BLI_freelistN(&anim_data);
}
/* ------------------- */
static int actkeys_duplicate_exec(bContext *C, wmOperator *UNUSED(op))
{
bAnimContext ac;
/* get editor data */
if (ANIM_animdata_get_context(C, &ac) == 0)
return OPERATOR_CANCELLED;
/* duplicate keyframes */
duplicate_action_keys(&ac);
/* validate keyframes after editing */
if (!ELEM(ac.datatype, ANIMCONT_GPENCIL, ANIMCONT_MASK))
ANIM_editkeyframes_refresh(&ac);
/* set notifier that keyframes have changed */
WM_event_add_notifier(C, NC_ANIMATION | ND_KEYFRAME | NA_EDITED, NULL);
return OPERATOR_FINISHED;
}
static int actkeys_duplicate_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(event))
{
actkeys_duplicate_exec(C, op);
return OPERATOR_FINISHED;
}
void ACTION_OT_duplicate(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Duplicate Keyframes";
ot->idname = "ACTION_OT_duplicate";
ot->description = "Make a copy of all selected keyframes";
/* api callbacks */
ot->invoke = actkeys_duplicate_invoke;
ot->exec = actkeys_duplicate_exec;
ot->poll = ED_operator_action_active;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
}
/* ******************** Delete Keyframes Operator ************************* */
static void delete_action_keys(bAnimContext *ac)
{
ListBase anim_data = {NULL, NULL};
bAnimListElem *ale;
int filter;
/* filter data */
if (ELEM(ac->datatype, ANIMCONT_GPENCIL, ANIMCONT_MASK))
filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE | ANIMFILTER_FOREDIT | ANIMFILTER_NODUPLIS);
else
filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE | ANIMFILTER_FOREDIT /*| ANIMFILTER_CURVESONLY*/ | ANIMFILTER_NODUPLIS);
ANIM_animdata_filter(ac, &anim_data, filter, ac->data, ac->datatype);
/* loop through filtered data and delete selected keys */
for (ale = anim_data.first; ale; ale = ale->next) {
if (ale->type == ANIMTYPE_GPLAYER) {
ED_gplayer_frames_delete((bGPDlayer *)ale->data);
}
else if (ale->type == ANIMTYPE_MASKLAYER) {
ED_masklayer_frames_delete((MaskLayer *)ale->data);
}
else {
FCurve *fcu = (FCurve *)ale->key_data;
AnimData *adt = ale->adt;
/* delete selected keyframes only */
delete_fcurve_keys(fcu);
/* Only delete curve too if it won't be doing anything anymore */
if ((fcu->totvert == 0) && (list_has_suitable_fmodifier(&fcu->modifiers, 0, FMI_TYPE_GENERATE_CURVE) == 0))
ANIM_fcurve_delete_from_animdata(ac, adt, fcu);
}
}
/* free filtered list */
BLI_freelistN(&anim_data);
}
/* ------------------- */
static int actkeys_delete_exec(bContext *C, wmOperator *UNUSED(op))
{
bAnimContext ac;
/* get editor data */
if (ANIM_animdata_get_context(C, &ac) == 0)
return OPERATOR_CANCELLED;
/* delete keyframes */
delete_action_keys(&ac);
/* validate keyframes after editing */
if (!ELEM(ac.datatype, ANIMCONT_GPENCIL, ANIMCONT_MASK))
ANIM_editkeyframes_refresh(&ac);
/* set notifier that keyframes have changed */
WM_event_add_notifier(C, NC_ANIMATION | ND_KEYFRAME | NA_EDITED, NULL);
return OPERATOR_FINISHED;
}
void ACTION_OT_delete(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Delete Keyframes";
ot->idname = "ACTION_OT_delete";
ot->description = "Remove all selected keyframes";
/* api callbacks */
ot->invoke = WM_operator_confirm;
ot->exec = actkeys_delete_exec;
ot->poll = ED_operator_action_active;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
}
/* ******************** Clean Keyframes Operator ************************* */
static void clean_action_keys(bAnimContext *ac, float thresh)
{
ListBase anim_data = {NULL, NULL};
bAnimListElem *ale;
int filter;
/* filter data */
filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE | ANIMFILTER_FOREDIT | ANIMFILTER_SEL /*| ANIMFILTER_CURVESONLY*/ | ANIMFILTER_NODUPLIS);
ANIM_animdata_filter(ac, &anim_data, filter, ac->data, ac->datatype);
/* loop through filtered data and clean curves */
for (ale = anim_data.first; ale; ale = ale->next)
clean_fcurve((FCurve *)ale->key_data, thresh);
/* free temp data */
BLI_freelistN(&anim_data);
}
/* ------------------- */
static int actkeys_clean_exec(bContext *C, wmOperator *op)
{
bAnimContext ac;
float thresh;
/* get editor data */
if (ANIM_animdata_get_context(C, &ac) == 0)
return OPERATOR_CANCELLED;
if (ELEM(ac.datatype, ANIMCONT_GPENCIL, ANIMCONT_MASK))
return OPERATOR_PASS_THROUGH;
/* get cleaning threshold */
thresh = RNA_float_get(op->ptr, "threshold");
/* clean keyframes */
clean_action_keys(&ac, thresh);
/* validate keyframes after editing */
ANIM_editkeyframes_refresh(&ac);
/* set notifier that keyframes have changed */
WM_event_add_notifier(C, NC_ANIMATION | ND_KEYFRAME | NA_EDITED, NULL);
return OPERATOR_FINISHED;
}
void ACTION_OT_clean(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Clean Keyframes";
ot->idname = "ACTION_OT_clean";
ot->description = "Simplify F-Curves by removing closely spaced keyframes";
/* api callbacks */
//ot->invoke = // XXX we need that number popup for this!
ot->exec = actkeys_clean_exec;
ot->poll = ED_operator_action_active;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
/* properties */
ot->prop = RNA_def_float(ot->srna, "threshold", 0.001f, 0.0f, FLT_MAX, "Threshold", "", 0.0f, 1000.0f);
}
/* ******************** Sample Keyframes Operator *********************** */
/* Evaluates the curves between each selected keyframe on each frame, and keys the value */
static void sample_action_keys(bAnimContext *ac)
{
ListBase anim_data = {NULL, NULL};
bAnimListElem *ale;
int filter;
/* filter data */
filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE | ANIMFILTER_FOREDIT /*| ANIMFILTER_CURVESONLY*/ | ANIMFILTER_NODUPLIS);
ANIM_animdata_filter(ac, &anim_data, filter, ac->data, ac->datatype);
/* loop through filtered data and add keys between selected keyframes on every frame */
for (ale = anim_data.first; ale; ale = ale->next)
sample_fcurve((FCurve *)ale->key_data);
/* admin and redraws */
BLI_freelistN(&anim_data);
}
/* ------------------- */
static int actkeys_sample_exec(bContext *C, wmOperator *UNUSED(op))
{
bAnimContext ac;
/* get editor data */
if (ANIM_animdata_get_context(C, &ac) == 0)
return OPERATOR_CANCELLED;
if (ELEM(ac.datatype, ANIMCONT_GPENCIL, ANIMCONT_MASK))
return OPERATOR_PASS_THROUGH;
/* sample keyframes */
sample_action_keys(&ac);
/* validate keyframes after editing */
ANIM_editkeyframes_refresh(&ac);
/* set notifier that keyframes have changed */
WM_event_add_notifier(C, NC_ANIMATION | ND_KEYFRAME | NA_EDITED, NULL);
return OPERATOR_FINISHED;
}
void ACTION_OT_sample(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Sample Keyframes";
ot->idname = "ACTION_OT_sample";
ot->description = "Add keyframes on every frame between the selected keyframes";
/* api callbacks */
ot->exec = actkeys_sample_exec;
ot->poll = ED_operator_action_active;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
}
/* ************************************************************************** */
/* SETTINGS STUFF */
/* ******************** Set Extrapolation-Type Operator *********************** */
/* defines for make/clear cyclic extrapolation tools */
#define MAKE_CYCLIC_EXPO -1
#define CLEAR_CYCLIC_EXPO -2
/* defines for set extrapolation-type for selected keyframes tool */
static EnumPropertyItem prop_actkeys_expo_types[] = {
{FCURVE_EXTRAPOLATE_CONSTANT, "CONSTANT", 0, "Constant Extrapolation", "Values on endpoint keyframes are held"},
{FCURVE_EXTRAPOLATE_LINEAR, "LINEAR", 0, "Linear Extrapolation", "Straight-line slope of end segments are extended past the endpoint keyframes"},
{MAKE_CYCLIC_EXPO, "MAKE_CYCLIC", 0, "Make Cyclic (F-Modifier)", "Add Cycles F-Modifier if one doesn't exist already"},
{CLEAR_CYCLIC_EXPO, "CLEAR_CYCLIC", 0, "Clear Cyclic (F-Modifier)", "Remove Cycles F-Modifier if not needed anymore"},
{0, NULL, 0, NULL, NULL}
};
/* this function is responsible for setting extrapolation mode for keyframes */
static void setexpo_action_keys(bAnimContext *ac, short mode)
{
ListBase anim_data = {NULL, NULL};
bAnimListElem *ale;
int filter;
/* filter data */
filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE | ANIMFILTER_FOREDIT | ANIMFILTER_SEL /*| ANIMFILTER_CURVESONLY*/ | ANIMFILTER_NODUPLIS);
ANIM_animdata_filter(ac, &anim_data, filter, ac->data, ac->datatype);
/* loop through setting mode per F-Curve */
for (ale = anim_data.first; ale; ale = ale->next) {
FCurve *fcu = (FCurve *)ale->data;
if (mode >= 0) {
/* just set mode setting */
fcu->extend = mode;
}
else {
/* shortcuts for managing Cycles F-Modifiers to make it easier to toggle cyclic animation
* without having to go through FModifier UI in Graph Editor to do so
*/
if (mode == MAKE_CYCLIC_EXPO) {
/* only add if one doesn't exist */
if (list_has_suitable_fmodifier(&fcu->modifiers, FMODIFIER_TYPE_CYCLES, -1) == 0) {
/* TODO: add some more preset versions which set different extrapolation options? */
add_fmodifier(&fcu->modifiers, FMODIFIER_TYPE_CYCLES);
}
}
else if (mode == CLEAR_CYCLIC_EXPO) {
/* remove all the modifiers fitting this description */
FModifier *fcm, *fcn = NULL;
for (fcm = fcu->modifiers.first; fcm; fcm = fcn) {
fcn = fcm->next;
if (fcm->type == FMODIFIER_TYPE_CYCLES)
remove_fmodifier(&fcu->modifiers, fcm);
}
}
}
}
/* cleanup */
BLI_freelistN(&anim_data);
}
/* ------------------- */
static int actkeys_expo_exec(bContext *C, wmOperator *op)
{
bAnimContext ac;
short mode;
/* get editor data */
if (ANIM_animdata_get_context(C, &ac) == 0)
return OPERATOR_CANCELLED;
if (ELEM(ac.datatype, ANIMCONT_GPENCIL, ANIMCONT_MASK))
return OPERATOR_PASS_THROUGH;
/* get handle setting mode */
mode = RNA_enum_get(op->ptr, "type");
/* set handle type */
setexpo_action_keys(&ac, mode);
/* validate keyframes after editing */
ANIM_editkeyframes_refresh(&ac);
/* set notifier that keyframe properties have changed */
WM_event_add_notifier(C, NC_ANIMATION | ND_KEYFRAME_PROP, NULL);
return OPERATOR_FINISHED;
}
void ACTION_OT_extrapolation_type(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Set Keyframe Extrapolation";
ot->idname = "ACTION_OT_extrapolation_type";
ot->description = "Set extrapolation mode for selected F-Curves";
/* api callbacks */
ot->invoke = WM_menu_invoke;
ot->exec = actkeys_expo_exec;
ot->poll = ED_operator_action_active;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
/* id-props */
ot->prop = RNA_def_enum(ot->srna, "type", prop_actkeys_expo_types, 0, "Type", "");
}
/* ******************** Set Interpolation-Type Operator *********************** */
/* this function is responsible for setting interpolation mode for keyframes */
static void setipo_action_keys(bAnimContext *ac, short mode)
{
ListBase anim_data = {NULL, NULL};
bAnimListElem *ale;
int filter;
KeyframeEditFunc set_cb = ANIM_editkeyframes_ipo(mode);
/* filter data */
filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE | ANIMFILTER_FOREDIT /*| ANIMFILTER_CURVESONLY*/ | ANIMFILTER_NODUPLIS);
ANIM_animdata_filter(ac, &anim_data, filter, ac->data, ac->datatype);
/* loop through setting BezTriple interpolation
* Note: we do not supply KeyframeEditData to the looper yet. Currently that's not necessary here...
*/
for (ale = anim_data.first; ale; ale = ale->next)
ANIM_fcurve_keyframes_loop(NULL, ale->key_data, NULL, set_cb, calchandles_fcurve);
/* cleanup */
BLI_freelistN(&anim_data);
}
/* ------------------- */
static int actkeys_ipo_exec(bContext *C, wmOperator *op)
{
bAnimContext ac;
short mode;
/* get editor data */
if (ANIM_animdata_get_context(C, &ac) == 0)
return OPERATOR_CANCELLED;
if (ELEM(ac.datatype, ANIMCONT_GPENCIL, ANIMCONT_MASK))
return OPERATOR_PASS_THROUGH;
/* get handle setting mode */
mode = RNA_enum_get(op->ptr, "type");
/* set handle type */
setipo_action_keys(&ac, mode);
/* validate keyframes after editing */
ANIM_editkeyframes_refresh(&ac);
/* set notifier that keyframe properties have changed */
WM_event_add_notifier(C, NC_ANIMATION | ND_KEYFRAME_PROP, NULL);
return OPERATOR_FINISHED;
}
void ACTION_OT_interpolation_type(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Set Keyframe Interpolation";
ot->idname = "ACTION_OT_interpolation_type";
ot->description = "Set interpolation mode for the F-Curve segments starting from the selected keyframes";
/* api callbacks */
ot->invoke = WM_menu_invoke;
ot->exec = actkeys_ipo_exec;
ot->poll = ED_operator_action_active;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
/* id-props */
ot->prop = RNA_def_enum(ot->srna, "type", beztriple_interpolation_mode_items, 0, "Type", "");
}
/* ******************** Set Handle-Type Operator *********************** */
/* this function is responsible for setting handle-type of selected keyframes */
static void sethandles_action_keys(bAnimContext *ac, short mode)
{
ListBase anim_data = {NULL, NULL};
bAnimListElem *ale;
int filter;
KeyframeEditFunc edit_cb = ANIM_editkeyframes_handles(mode);
KeyframeEditFunc sel_cb = ANIM_editkeyframes_ok(BEZT_OK_SELECTED);
/* filter data */
filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE | ANIMFILTER_FOREDIT /*| ANIMFILTER_CURVESONLY*/ | ANIMFILTER_NODUPLIS);
ANIM_animdata_filter(ac, &anim_data, filter, ac->data, ac->datatype);
/* loop through setting flags for handles
* Note: we do not supply KeyframeEditData to the looper yet. Currently that's not necessary here...
*/
for (ale = anim_data.first; ale; ale = ale->next) {
FCurve *fcu = (FCurve *)ale->key_data;
/* any selected keyframes for editing? */
if (ANIM_fcurve_keyframes_loop(NULL, fcu, NULL, sel_cb, NULL)) {
/* change type of selected handles */
ANIM_fcurve_keyframes_loop(NULL, fcu, NULL, edit_cb, calchandles_fcurve);
}
}
/* cleanup */
BLI_freelistN(&anim_data);
}
/* ------------------- */
static int actkeys_handletype_exec(bContext *C, wmOperator *op)
{
bAnimContext ac;
short mode;
/* get editor data */
if (ANIM_animdata_get_context(C, &ac) == 0)
return OPERATOR_CANCELLED;
if (ELEM(ac.datatype, ANIMCONT_GPENCIL, ANIMCONT_MASK))
return OPERATOR_PASS_THROUGH;
/* get handle setting mode */
mode = RNA_enum_get(op->ptr, "type");
/* set handle type */
sethandles_action_keys(&ac, mode);
/* validate keyframes after editing */
ANIM_editkeyframes_refresh(&ac);
/* set notifier that keyframe properties have changed */
WM_event_add_notifier(C, NC_ANIMATION | ND_KEYFRAME_PROP, NULL);
return OPERATOR_FINISHED;
}
void ACTION_OT_handle_type(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Set Keyframe Handle Type";
ot->idname = "ACTION_OT_handle_type";
ot->description = "Set type of handle for selected keyframes";
/* api callbacks */
ot->invoke = WM_menu_invoke;
ot->exec = actkeys_handletype_exec;
ot->poll = ED_operator_action_active;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
/* id-props */
ot->prop = RNA_def_enum(ot->srna, "type", keyframe_handle_type_items, 0, "Type", "");
}
/* ******************** Set Keyframe-Type Operator *********************** */
/* this function is responsible for setting interpolation mode for keyframes */
static void setkeytype_action_keys(bAnimContext *ac, short mode)
{
ListBase anim_data = {NULL, NULL};
bAnimListElem *ale;
int filter;
KeyframeEditFunc set_cb = ANIM_editkeyframes_keytype(mode);
/* filter data */
filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE | ANIMFILTER_FOREDIT /*| ANIMFILTER_CURVESONLY*/ | ANIMFILTER_NODUPLIS);
ANIM_animdata_filter(ac, &anim_data, filter, ac->data, ac->datatype);
/* loop through setting BezTriple interpolation
* Note: we do not supply KeyframeEditData to the looper yet. Currently that's not necessary here...
*/
for (ale = anim_data.first; ale; ale = ale->next)
ANIM_fcurve_keyframes_loop(NULL, ale->key_data, NULL, set_cb, NULL);
/* cleanup */
BLI_freelistN(&anim_data);
}
/* ------------------- */
static int actkeys_keytype_exec(bContext *C, wmOperator *op)
{
bAnimContext ac;
short mode;
/* get editor data */
if (ANIM_animdata_get_context(C, &ac) == 0)
return OPERATOR_CANCELLED;
if (ELEM(ac.datatype, ANIMCONT_GPENCIL, ANIMCONT_MASK))
return OPERATOR_PASS_THROUGH;
/* get handle setting mode */
mode = RNA_enum_get(op->ptr, "type");
/* set handle type */
setkeytype_action_keys(&ac, mode);
/* validate keyframes after editing */
ANIM_editkeyframes_refresh(&ac);
/* set notifier that keyframe properties have changed */
WM_event_add_notifier(C, NC_ANIMATION | ND_KEYFRAME_PROP, NULL);
return OPERATOR_FINISHED;
}
void ACTION_OT_keyframe_type(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Set Keyframe Type";
ot->idname = "ACTION_OT_keyframe_type";
ot->description = "Set type of keyframe for the selected keyframes";
/* api callbacks */
ot->invoke = WM_menu_invoke;
ot->exec = actkeys_keytype_exec;
ot->poll = ED_operator_action_active;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
/* id-props */
ot->prop = RNA_def_enum(ot->srna, "type", beztriple_keyframe_type_items, 0, "Type", "");
}
/* ************************************************************************** */
/* TRANSFORM STUFF */
/* ***************** Jump to Selected Frames Operator *********************** */
static int actkeys_framejump_poll(bContext *C)
{
/* prevent changes during render */
if (G.is_rendering)
return 0;
return ED_operator_action_active(C);
}
/* snap current-frame indicator to 'average time' of selected keyframe */
static int actkeys_framejump_exec(bContext *C, wmOperator *UNUSED(op))
{
bAnimContext ac;
ListBase anim_data = {NULL, NULL};
bAnimListElem *ale;
int filter;
KeyframeEditData ked = {{NULL}};
/* get editor data */
if (ANIM_animdata_get_context(C, &ac) == 0)
return OPERATOR_CANCELLED;
/* init edit data */
/* loop over action data, averaging values */
filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE /*| ANIMFILTER_CURVESONLY */ | ANIMFILTER_NODUPLIS);
ANIM_animdata_filter(&ac, &anim_data, filter, ac.data, ac.datatype);
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, 1);
ANIM_fcurve_keyframes_loop(&ked, ale->key_data, NULL, bezt_calc_average, NULL);
ANIM_nla_mapping_apply_fcurve(adt, ale->key_data, 1, 1);
}
else
ANIM_fcurve_keyframes_loop(&ked, ale->key_data, NULL, bezt_calc_average, NULL);
}
BLI_freelistN(&anim_data);
/* set the new current frame value, based on the average time */
if (ked.i1) {
Scene *scene = ac.scene;
CFRA = (int)floor((ked.f1 / ked.i1) + 0.5f);
SUBFRA = 0.f;
}
/* set notifier that things have changed */
WM_event_add_notifier(C, NC_SCENE | ND_FRAME, ac.scene);
return OPERATOR_FINISHED;
}
void ACTION_OT_frame_jump(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Jump to Keyframes";
ot->idname = "ACTION_OT_frame_jump";
ot->description = "Set the current frame to the average frame value of selected keyframes";
/* api callbacks */
ot->exec = actkeys_framejump_exec;
ot->poll = actkeys_framejump_poll;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
}
/* ******************** Snap Keyframes Operator *********************** */
/* defines for snap keyframes tool */
static EnumPropertyItem prop_actkeys_snap_types[] = {
{ACTKEYS_SNAP_CFRA, "CFRA", 0, "Current frame",
"Snap selected keyframes to the current frame"},
{ACTKEYS_SNAP_NEAREST_FRAME, "NEAREST_FRAME", 0, "Nearest Frame",
"Snap selected keyframes to the nearest (whole) frame (use to fix accidental sub-frame offsets)"},
{ACTKEYS_SNAP_NEAREST_SECOND, "NEAREST_SECOND", 0, "Nearest Second",
"Snap selected keyframes to the nearest second"},
{ACTKEYS_SNAP_NEAREST_MARKER, "NEAREST_MARKER", 0, "Nearest Marker",
"Snap selected keyframes to the nearest marker"},
{0, NULL, 0, NULL, NULL}
};
/* this function is responsible for snapping keyframes to frame-times */
static void snap_action_keys(bAnimContext *ac, short mode)
{
ListBase anim_data = {NULL, NULL};
bAnimListElem *ale;
int filter;
KeyframeEditData ked = {{NULL}};
KeyframeEditFunc edit_cb;
/* filter data */
if (ELEM(ac->datatype, ANIMCONT_GPENCIL, ANIMCONT_MASK))
filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE | ANIMFILTER_FOREDIT);
else
filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE | ANIMFILTER_FOREDIT /*| ANIMFILTER_CURVESONLY*/ | ANIMFILTER_NODUPLIS);
ANIM_animdata_filter(ac, &anim_data, filter, ac->data, ac->datatype);
/* get beztriple editing callbacks */
edit_cb = ANIM_editkeyframes_snap(mode);
ked.scene = ac->scene;
if (mode == ACTKEYS_SNAP_NEAREST_MARKER) {
ked.list.first = (ac->markers) ? ac->markers->first : NULL;
ked.list.last = (ac->markers) ? ac->markers->last : NULL;
}
/* snap keyframes */
for (ale = anim_data.first; ale; ale = ale->next) {
AnimData *adt = ANIM_nla_mapping_get(ac, ale);
if (ale->type == ANIMTYPE_GPLAYER) {
ED_gplayer_snap_frames(ale->data, ac->scene, mode);
}
else if (ale->type == ANIMTYPE_MASKLAYER) {
ED_masklayer_snap_frames(ale->data, ac->scene, mode);
}
else if (adt) {
ANIM_nla_mapping_apply_fcurve(adt, ale->key_data, 0, 1);
ANIM_fcurve_keyframes_loop(&ked, ale->key_data, NULL, edit_cb, calchandles_fcurve);
ANIM_nla_mapping_apply_fcurve(adt, ale->key_data, 1, 1);
}
else {
ANIM_fcurve_keyframes_loop(&ked, ale->key_data, NULL, edit_cb, calchandles_fcurve);
}
}
BLI_freelistN(&anim_data);
}
/* ------------------- */
static int actkeys_snap_exec(bContext *C, wmOperator *op)
{
bAnimContext ac;
short mode;
/* get editor data */
if (ANIM_animdata_get_context(C, &ac) == 0)
return OPERATOR_CANCELLED;
/* get snapping mode */
mode = RNA_enum_get(op->ptr, "type");
/* snap keyframes */
snap_action_keys(&ac, mode);
/* validate keyframes after editing */
if (!ELEM(ac.datatype, ANIMCONT_GPENCIL, ANIMCONT_MASK))
ANIM_editkeyframes_refresh(&ac);
/* set notifier that keyframes have changed */
WM_event_add_notifier(C, NC_ANIMATION | ND_KEYFRAME | NA_EDITED, NULL);
return OPERATOR_FINISHED;
}
void ACTION_OT_snap(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Snap Keys";
ot->idname = "ACTION_OT_snap";
ot->description = "Snap selected keyframes to the times specified";
/* api callbacks */
ot->invoke = WM_menu_invoke;
ot->exec = actkeys_snap_exec;
ot->poll = ED_operator_action_active;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
/* id-props */
ot->prop = RNA_def_enum(ot->srna, "type", prop_actkeys_snap_types, 0, "Type", "");
}
/* ******************** Mirror Keyframes Operator *********************** */
/* defines for mirror keyframes tool */
static EnumPropertyItem prop_actkeys_mirror_types[] = {
{ACTKEYS_MIRROR_CFRA, "CFRA", 0, "By Times over Current frame",
"Flip times of selected keyframes using the current frame as the mirror line"},
{ACTKEYS_MIRROR_XAXIS, "XAXIS", 0, "By Values over Value=0",
"Flip values of selected keyframes (i.e. negative values become positive, and vice versa)"},
{ACTKEYS_MIRROR_MARKER, "MARKER", 0, "By Times over First Selected Marker",
"Flip times of selected keyframes using the first selected marker as the reference point"},
{0, NULL, 0, NULL, NULL}
};
/* this function is responsible for mirroring keyframes */
static void mirror_action_keys(bAnimContext *ac, short mode)
{
ListBase anim_data = {NULL, NULL};
bAnimListElem *ale;
int filter;
KeyframeEditData ked = {{NULL}};
KeyframeEditFunc edit_cb;
/* get beztriple editing callbacks */
edit_cb = ANIM_editkeyframes_mirror(mode);
ked.scene = ac->scene;
/* for 'first selected marker' mode, need to find first selected marker first! */
/* XXX should this be made into a helper func in the API? */
if (mode == ACTKEYS_MIRROR_MARKER) {
TimeMarker *marker = ED_markers_get_first_selected(ac->markers);
if (marker)
ked.f1 = (float)marker->frame;
else
return;
}
/* filter data */
if (ELEM(ac->datatype, ANIMCONT_GPENCIL, ANIMCONT_MASK))
filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE | ANIMFILTER_FOREDIT | ANIMFILTER_NODUPLIS);
else
filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE | ANIMFILTER_FOREDIT /*| ANIMFILTER_CURVESONLY*/ | ANIMFILTER_NODUPLIS);
ANIM_animdata_filter(ac, &anim_data, filter, ac->data, ac->datatype);
/* mirror keyframes */
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, 1);
ANIM_fcurve_keyframes_loop(&ked, ale->key_data, NULL, edit_cb, calchandles_fcurve);
ANIM_nla_mapping_apply_fcurve(adt, ale->key_data, 1, 1);
}
//else if (ale->type == ACTTYPE_GPLAYER)
// snap_gplayer_frames(ale->data, mode);
else
ANIM_fcurve_keyframes_loop(&ked, ale->key_data, NULL, edit_cb, calchandles_fcurve);
}
BLI_freelistN(&anim_data);
}
/* ------------------- */
static int actkeys_mirror_exec(bContext *C, wmOperator *op)
{
bAnimContext ac;
short mode;
/* get editor data */
if (ANIM_animdata_get_context(C, &ac) == 0)
return OPERATOR_CANCELLED;
/* XXX... */
if (ELEM(ac.datatype, ANIMCONT_GPENCIL, ANIMCONT_MASK))
return OPERATOR_PASS_THROUGH;
/* get mirroring mode */
mode = RNA_enum_get(op->ptr, "type");
/* mirror keyframes */
mirror_action_keys(&ac, mode);
/* validate keyframes after editing */
ANIM_editkeyframes_refresh(&ac);
/* set notifier that keyframes have changed */
WM_event_add_notifier(C, NC_ANIMATION | ND_KEYFRAME | NA_EDITED, NULL);
return OPERATOR_FINISHED;
}
void ACTION_OT_mirror(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Mirror Keys";
ot->idname = "ACTION_OT_mirror";
ot->description = "Flip selected keyframes over the selected mirror line";
/* api callbacks */
ot->invoke = WM_menu_invoke;
ot->exec = actkeys_mirror_exec;
ot->poll = ED_operator_action_active;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
/* id-props */
ot->prop = RNA_def_enum(ot->srna, "type", prop_actkeys_mirror_types, 0, "Type", "");
}
/* ************************************************************************** */