2191 lines
65 KiB
C++
2191 lines
65 KiB
C++
/* SPDX-FileCopyrightText: 2001-2002 NaN Holding BV. All rights reserved.
|
|
*
|
|
* SPDX-License-Identifier: GPL-2.0-or-later */
|
|
|
|
/** \file
|
|
* \ingroup spaction
|
|
*/
|
|
|
|
#include <cfloat>
|
|
#include <cmath>
|
|
#include <cstdlib>
|
|
#include <cstring>
|
|
|
|
#include "BLI_listbase.h"
|
|
#include "BLI_map.hh"
|
|
#include "BLI_string.h"
|
|
#include "BLI_utildefines.h"
|
|
|
|
#include "BLT_translation.hh"
|
|
|
|
#include "DEG_depsgraph.hh"
|
|
|
|
#include "DNA_anim_types.h"
|
|
#include "DNA_gpencil_legacy_types.h"
|
|
#include "DNA_mask_types.h"
|
|
#include "DNA_scene_types.h"
|
|
|
|
#include "RNA_access.hh"
|
|
#include "RNA_define.hh"
|
|
#include "RNA_enum_types.hh"
|
|
|
|
#include "BKE_animsys.h"
|
|
#include "BKE_context.hh"
|
|
#include "BKE_fcurve.hh"
|
|
#include "BKE_global.hh"
|
|
#include "BKE_gpencil_legacy.h"
|
|
#include "BKE_grease_pencil.hh"
|
|
#include "BKE_nla.hh"
|
|
#include "BKE_report.hh"
|
|
|
|
#include "UI_interface_icons.hh"
|
|
#include "UI_view2d.hh"
|
|
|
|
#include "ANIM_action.hh"
|
|
#include "ANIM_animdata.hh"
|
|
#include "ANIM_fcurve.hh"
|
|
#include "ANIM_keyframing.hh"
|
|
#include "ED_anim_api.hh"
|
|
#include "ED_gpencil_legacy.hh"
|
|
#include "ED_grease_pencil.hh"
|
|
#include "ED_keyframes_edit.hh"
|
|
#include "ED_keyframing.hh"
|
|
#include "ED_markers.hh"
|
|
#include "ED_mask.hh"
|
|
#include "ED_screen.hh"
|
|
|
|
#include "WM_api.hh"
|
|
#include "WM_types.hh"
|
|
|
|
#include "action_intern.hh"
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Pose Markers: Localize 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 bool act_markers_make_local_poll(bContext *C)
|
|
{
|
|
SpaceAction *sact = CTX_wm_space_action(C);
|
|
|
|
/* 1) */
|
|
if (sact == nullptr) {
|
|
return false;
|
|
}
|
|
|
|
/* 2) */
|
|
if (ELEM(sact->mode, SACTCONT_ACTION, SACTCONT_SHAPEKEY) == 0) {
|
|
return false;
|
|
}
|
|
if (sact->action == nullptr) {
|
|
return false;
|
|
}
|
|
|
|
/* 3) */
|
|
if (sact->flag & SACTION_POSEMARKERS_SHOW) {
|
|
return false;
|
|
}
|
|
|
|
/* 4) */
|
|
return ED_markers_get_first_selected(ED_context_get_markers(C)) != nullptr;
|
|
}
|
|
|
|
static wmOperatorStatus act_markers_make_local_exec(bContext *C, wmOperator * /*op*/)
|
|
{
|
|
ListBase *markers = ED_context_get_markers(C);
|
|
|
|
SpaceAction *sact = CTX_wm_space_action(C);
|
|
bAction *act = (sact) ? sact->action : nullptr;
|
|
|
|
TimeMarker *marker, *markern = nullptr;
|
|
|
|
/* sanity checks */
|
|
if (ELEM(nullptr, markers, act)) {
|
|
return OPERATOR_CANCELLED;
|
|
}
|
|
|
|
/* migrate markers */
|
|
for (marker = static_cast<TimeMarker *>(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 pose-markers 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, nullptr);
|
|
WM_event_add_notifier(C, NC_ANIMATION | ND_MARKERS, nullptr);
|
|
|
|
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;
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Calculate Range
|
|
* \{ */
|
|
|
|
/* Get the min/max keyframes. */
|
|
static bool get_keyframe_extents(bAnimContext *ac, float *min, float *max, const short onlySel)
|
|
{
|
|
ListBase anim_data = {nullptr, nullptr};
|
|
eAnimFilter_Flags filter;
|
|
bool found = false;
|
|
|
|
/* Get data to filter, from Action or Dope-sheet. */
|
|
/* 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_NODUPLIS);
|
|
ANIM_animdata_filter(ac, &anim_data, filter, ac->data, eAnimCont_Types(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 */
|
|
LISTBASE_FOREACH (bAnimListElem *, ale, &anim_data) {
|
|
if (ale->datatype == ALE_GPFRAME) {
|
|
bGPDlayer *gpl = static_cast<bGPDlayer *>(ale->data);
|
|
|
|
/* Find GP-frame which is less than or equal to current-frame. */
|
|
LISTBASE_FOREACH (bGPDframe *, gpf, &gpl->frames) {
|
|
if (!onlySel || (gpf->flag & GP_FRAME_SELECT)) {
|
|
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 = static_cast<MaskLayer *>(ale->data);
|
|
/* Find mask layer which is less than or equal to current-frame. */
|
|
LISTBASE_FOREACH (MaskLayerShape *, masklay_shape, &masklay->splines_shapes) {
|
|
const float framenum = float(masklay_shape->frame);
|
|
*min = min_ff(*min, framenum);
|
|
*max = max_ff(*max, framenum);
|
|
found = true;
|
|
}
|
|
}
|
|
else if (ale->datatype == ALE_GREASE_PENCIL_CEL) {
|
|
const blender::bke::greasepencil::Layer &layer =
|
|
static_cast<GreasePencilLayer *>(ale->data)->wrap();
|
|
|
|
for (const auto [key, frame] : layer.frames().items()) {
|
|
if (onlySel && !frame.is_selected()) {
|
|
continue;
|
|
}
|
|
*min = min_ff(*min, float(key));
|
|
*max = max_ff(*max, float(key));
|
|
found = true;
|
|
}
|
|
}
|
|
else {
|
|
FCurve *fcu = (FCurve *)ale->key_data;
|
|
float tmin, tmax;
|
|
|
|
/* get range and apply necessary scaling before processing */
|
|
if (BKE_fcurve_calc_range(fcu, &tmin, &tmax, onlySel)) {
|
|
tmin = ANIM_nla_tweakedit_remap(ale, tmin, NLATIME_CONVERT_MAP);
|
|
tmax = ANIM_nla_tweakedit_remap(ale, 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;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (fabsf(*max - *min) < 0.001f) {
|
|
*min -= 0.0005f;
|
|
*max += 0.0005f;
|
|
}
|
|
|
|
/* free memory */
|
|
ANIM_animdata_freelist(&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;
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name View: Automatic Preview-Range Operator
|
|
* \{ */
|
|
|
|
static wmOperatorStatus actkeys_previewrange_exec(bContext *C, wmOperator * /*op*/)
|
|
{
|
|
bAnimContext ac;
|
|
Scene *scene;
|
|
float min, max;
|
|
|
|
/* get editor data */
|
|
if (ANIM_animdata_get_context(C, &ac) == 0) {
|
|
return OPERATOR_CANCELLED;
|
|
}
|
|
if (ac.scene == nullptr) {
|
|
return OPERATOR_CANCELLED;
|
|
}
|
|
|
|
/* set the range directly */
|
|
if (!get_keyframe_extents(&ac, &min, &max, true)) {
|
|
return OPERATOR_CANCELLED;
|
|
}
|
|
scene = ac.scene;
|
|
scene->r.flag |= SCER_PRV_RANGE;
|
|
scene->r.psfra = floorf(min);
|
|
scene->r.pefra = ceilf(max);
|
|
|
|
if (scene->r.psfra == scene->r.pefra) {
|
|
scene->r.pefra = scene->r.psfra + 1;
|
|
}
|
|
|
|
/* 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 = "Set Preview Range to Selected";
|
|
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;
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name View: All Operator
|
|
* \{ */
|
|
|
|
/**
|
|
* Find the extents of the active channel
|
|
*
|
|
* \param r_min: Bottom y-extent of channel.
|
|
* \param r_max: Top y-extent of channel.
|
|
* \return Success of finding a selected channel.
|
|
*/
|
|
static bool actkeys_channels_get_selected_extents(bAnimContext *ac, float *r_min, float *r_max)
|
|
{
|
|
ListBase anim_data = {nullptr, nullptr};
|
|
bAnimListElem *ale;
|
|
eAnimFilter_Flags filter;
|
|
|
|
/* NOTE: not bool, since we want prioritize individual channels over expanders. */
|
|
short found = 0;
|
|
|
|
/* get all items - we need to do it this way */
|
|
filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE | ANIMFILTER_LIST_CHANNELS);
|
|
ANIM_animdata_filter(ac, &anim_data, filter, ac->data, eAnimCont_Types(ac->datatype));
|
|
|
|
/* loop through all channels, finding the first one that's selected */
|
|
float ymax = ANIM_UI_get_first_channel_top(&ac->region->v2d);
|
|
const float channel_step = ANIM_UI_get_channel_step();
|
|
for (ale = static_cast<bAnimListElem *>(anim_data.first); ale;
|
|
ale = ale->next, ymax -= channel_step)
|
|
{
|
|
const bAnimChannelType *acf = ANIM_channel_get_typeinfo(ale);
|
|
|
|
/* must be selected... */
|
|
if (acf && acf->has_setting(ac, ale, ACHANNEL_SETTING_SELECT) &&
|
|
ANIM_channel_setting_get(ac, ale, ACHANNEL_SETTING_SELECT))
|
|
{
|
|
/* update best estimate */
|
|
*r_min = ymax - ANIM_UI_get_channel_height();
|
|
*r_max = ymax;
|
|
|
|
/* is this high enough priority yet? */
|
|
found = acf->channel_role;
|
|
|
|
/* only stop our search when we've found an actual channel
|
|
* - data-block expanders get less priority so that we don't abort prematurely
|
|
*/
|
|
if (found == ACHANNEL_ROLE_CHANNEL) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* free all temp data */
|
|
ANIM_animdata_freelist(&anim_data);
|
|
|
|
return (found != 0);
|
|
}
|
|
|
|
static wmOperatorStatus actkeys_viewall(bContext *C, const bool only_sel)
|
|
{
|
|
bAnimContext ac;
|
|
View2D *v2d;
|
|
float min, max;
|
|
bool found;
|
|
|
|
/* get editor data */
|
|
if (ANIM_animdata_get_context(C, &ac) == 0) {
|
|
return OPERATOR_CANCELLED;
|
|
}
|
|
v2d = &ac.region->v2d;
|
|
|
|
/* set the horizontal range, with an extra offset so that the extreme keys will be in view */
|
|
found = get_keyframe_extents(&ac, &min, &max, only_sel);
|
|
|
|
if (only_sel && (found == false)) {
|
|
return OPERATOR_CANCELLED;
|
|
}
|
|
|
|
if (fabsf(max - min) < 1.0f) {
|
|
/* Exception - center the single keyframe. */
|
|
float xwidth = BLI_rctf_size_x(&v2d->cur);
|
|
|
|
v2d->cur.xmin = min - xwidth / 2.0f;
|
|
v2d->cur.xmax = max + xwidth / 2.0f;
|
|
}
|
|
else {
|
|
/* Normal case - stretch the two keyframes out to fill the space, with extra spacing */
|
|
v2d->cur.xmin = min;
|
|
v2d->cur.xmax = max;
|
|
|
|
v2d->cur = ANIM_frame_range_view2d_add_xmargin(*v2d, v2d->cur);
|
|
}
|
|
|
|
/* set vertical range */
|
|
if (only_sel == false) {
|
|
/* view all -> the summary channel is usually the shows everything,
|
|
* and resides right at the top... */
|
|
v2d->cur.ymax = 0.0f;
|
|
v2d->cur.ymin = float(-BLI_rcti_size_y(&v2d->mask));
|
|
}
|
|
else {
|
|
/* locate first selected channel (or the active one), and frame those */
|
|
float ymin = v2d->cur.ymin;
|
|
float ymax = v2d->cur.ymax;
|
|
|
|
if (actkeys_channels_get_selected_extents(&ac, &ymin, &ymax)) {
|
|
/* recenter the view so that this range is in the middle */
|
|
float ymid = (ymax - ymin) / 2.0f + ymin;
|
|
float x_center;
|
|
|
|
UI_view2d_center_get(v2d, &x_center, nullptr);
|
|
UI_view2d_center_set(v2d, x_center, ymid);
|
|
}
|
|
}
|
|
|
|
/* 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 wmOperatorStatus actkeys_viewall_exec(bContext *C, wmOperator * /*op*/)
|
|
{
|
|
/* whole range */
|
|
return actkeys_viewall(C, false);
|
|
}
|
|
|
|
static wmOperatorStatus actkeys_viewsel_exec(bContext *C, wmOperator * /*op*/)
|
|
{
|
|
/* only selected */
|
|
return actkeys_viewall(C, true);
|
|
}
|
|
|
|
/* ......... */
|
|
|
|
void ACTION_OT_view_all(wmOperatorType *ot)
|
|
{
|
|
/* identifiers */
|
|
ot->name = "Frame 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 = 0;
|
|
}
|
|
|
|
void ACTION_OT_view_selected(wmOperatorType *ot)
|
|
{
|
|
/* identifiers */
|
|
ot->name = "Frame 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 = 0;
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name View: Frame Operator
|
|
* \{ */
|
|
|
|
static wmOperatorStatus actkeys_view_frame_exec(bContext *C, wmOperator *op)
|
|
{
|
|
const int smooth_viewtx = WM_operator_smooth_viewtx_get(op);
|
|
ANIM_center_frame(C, smooth_viewtx);
|
|
|
|
return OPERATOR_FINISHED;
|
|
}
|
|
|
|
void ACTION_OT_view_frame(wmOperatorType *ot)
|
|
{
|
|
/* identifiers */
|
|
ot->name = "Go to Current Frame";
|
|
ot->idname = "ACTION_OT_view_frame";
|
|
ot->description = "Move the view to the current frame";
|
|
|
|
/* API callbacks. */
|
|
ot->exec = actkeys_view_frame_exec;
|
|
ot->poll = ED_operator_action_active;
|
|
|
|
/* flags */
|
|
ot->flag = 0;
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Keyframes: Copy/Paste Operator
|
|
* \{ */
|
|
|
|
/* NOTE: the backend code for this is shared with the graph editor */
|
|
|
|
static bool copy_action_keys(bAnimContext *ac)
|
|
{
|
|
ListBase anim_data = {nullptr, nullptr};
|
|
eAnimFilter_Flags filter;
|
|
|
|
/* filter data */
|
|
filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE | ANIMFILTER_FCURVESONLY |
|
|
ANIMFILTER_NODUPLIS);
|
|
ANIM_animdata_filter(ac, &anim_data, filter, ac->data, eAnimCont_Types(ac->datatype));
|
|
|
|
/* copy keyframes */
|
|
const bool ok = copy_animedit_keys(ac, &anim_data);
|
|
|
|
/* clean up */
|
|
ANIM_animdata_freelist(&anim_data);
|
|
|
|
return ok;
|
|
}
|
|
|
|
static eKeyPasteError paste_action_keys(bAnimContext *ac,
|
|
const eKeyPasteOffset offset_mode,
|
|
const eKeyMergeMode merge_mode,
|
|
bool flip)
|
|
{
|
|
/* TODO: deduplicate this function and `paste_graph_keys()` in `graph_edit.cc`, */
|
|
|
|
/* Determine paste context. */
|
|
KeyframePasteContext paste_context{};
|
|
paste_context.offset_mode = offset_mode;
|
|
/* Value offset is always None because the user cannot see the effect of it. */
|
|
paste_context.value_offset_mode = KEYFRAME_PASTE_VALUE_OFFSET_NONE;
|
|
paste_context.merge_mode = merge_mode;
|
|
paste_context.flip = flip;
|
|
|
|
/* See how many slots are selected to paste into. This determines how slot matching is done:
|
|
* - Copied from one slot, paste into multiple: duplicate into each slot.
|
|
* - Otherwise: match slots by name. */
|
|
{
|
|
ListBase anim_data = {nullptr, nullptr};
|
|
const eAnimFilter_Flags filter = ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE |
|
|
ANIMFILTER_LIST_CHANNELS | ANIMFILTER_NODUPLIS |
|
|
ANIMFILTER_SEL;
|
|
ANIM_animdata_filter(ac, &anim_data, filter, ac->data, ac->datatype);
|
|
LISTBASE_FOREACH (const bAnimListElem *, ale, &anim_data) {
|
|
if (ale->datatype == ALE_ACTION_SLOT) {
|
|
paste_context.num_slots_selected++;
|
|
}
|
|
}
|
|
ANIM_animdata_freelist(&anim_data);
|
|
}
|
|
|
|
/* Find F-Curves to paste into, in two stages.
|
|
* - 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.
|
|
*/
|
|
ListBase anim_data = {nullptr, nullptr};
|
|
{
|
|
const eAnimFilter_Flags filter = ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE |
|
|
ANIMFILTER_FOREDIT | ANIMFILTER_FCURVESONLY |
|
|
ANIMFILTER_NODUPLIS;
|
|
paste_context.num_fcurves_selected = ANIM_animdata_filter(
|
|
ac, &anim_data, filter | ANIMFILTER_SEL, ac->data, ac->datatype);
|
|
if (paste_context.num_fcurves_selected == 0) {
|
|
ANIM_animdata_filter(ac, &anim_data, filter, ac->data, ac->datatype);
|
|
}
|
|
}
|
|
|
|
const eKeyPasteError ok = paste_animedit_keys(ac, &anim_data, paste_context);
|
|
|
|
/* clean up */
|
|
ANIM_animdata_freelist(&anim_data);
|
|
|
|
return ok;
|
|
}
|
|
|
|
/* ------------------- */
|
|
|
|
static blender::ed::greasepencil::KeyframeClipboard &get_grease_pencil_keyframe_clipboard()
|
|
{
|
|
static blender::ed::greasepencil::KeyframeClipboard clipboard;
|
|
return clipboard;
|
|
}
|
|
|
|
static wmOperatorStatus 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) {
|
|
if (ED_gpencil_anim_copybuf_copy(&ac) == false &&
|
|
blender::ed::greasepencil::grease_pencil_copy_keyframes(
|
|
&ac, get_grease_pencil_keyframe_clipboard()) == false)
|
|
{
|
|
/* check if anything ended up in the buffer */
|
|
BKE_report(op->reports, RPT_ERROR, "No keyframes copied to the internal clipboard");
|
|
return OPERATOR_CANCELLED;
|
|
}
|
|
}
|
|
else if (ac.datatype == ANIMCONT_MASK) {
|
|
/* FIXME: support this case. */
|
|
BKE_report(op->reports, RPT_ERROR, "Keyframe pasting is not available for mask mode");
|
|
return OPERATOR_CANCELLED;
|
|
}
|
|
else {
|
|
/* Both copy function needs to be evaluated to account for mixed selection */
|
|
const bool kf_ok = copy_action_keys(&ac);
|
|
const bool gpf_ok = ED_gpencil_anim_copybuf_copy(&ac) ||
|
|
blender::ed::greasepencil::grease_pencil_copy_keyframes(
|
|
&ac, get_grease_pencil_keyframe_clipboard());
|
|
|
|
if (!kf_ok && !gpf_ok) {
|
|
BKE_report(op->reports, RPT_ERROR, "No keyframes copied to the internal clipboard");
|
|
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 internal clipboard";
|
|
|
|
/* API callbacks. */
|
|
ot->exec = actkeys_copy_exec;
|
|
ot->poll = ED_operator_action_active;
|
|
|
|
/* flags */
|
|
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
|
|
}
|
|
|
|
static wmOperatorStatus actkeys_paste_exec(bContext *C, wmOperator *op)
|
|
{
|
|
bAnimContext ac;
|
|
|
|
const eKeyPasteOffset offset_mode = eKeyPasteOffset(RNA_enum_get(op->ptr, "offset"));
|
|
const eKeyMergeMode merge_mode = eKeyMergeMode(RNA_enum_get(op->ptr, "merge"));
|
|
const bool flipped = RNA_boolean_get(op->ptr, "flipped");
|
|
|
|
bool gpframes_inbuf = false;
|
|
|
|
/* 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 (ac.datatype == ANIMCONT_GPENCIL) {
|
|
if (ED_gpencil_anim_copybuf_paste(&ac, offset_mode) == false &&
|
|
blender::ed::greasepencil::grease_pencil_paste_keyframes(
|
|
&ac, offset_mode, merge_mode, get_grease_pencil_keyframe_clipboard()) == false)
|
|
{
|
|
BKE_report(op->reports, RPT_ERROR, "No data in the internal clipboard to paste");
|
|
return OPERATOR_CANCELLED;
|
|
}
|
|
}
|
|
else if (ac.datatype == ANIMCONT_MASK) {
|
|
/* FIXME: support this case. */
|
|
BKE_report(op->reports,
|
|
RPT_ERROR,
|
|
"Keyframe pasting is not available for Grease Pencil or mask mode");
|
|
return OPERATOR_CANCELLED;
|
|
}
|
|
else {
|
|
/* Both paste function needs to be evaluated to account for mixed selection */
|
|
const eKeyPasteError kf_empty = paste_action_keys(&ac, offset_mode, merge_mode, flipped);
|
|
/* non-zero return means an error occurred while trying to paste */
|
|
gpframes_inbuf = ED_gpencil_anim_copybuf_paste(&ac, offset_mode) ||
|
|
blender::ed::greasepencil::grease_pencil_paste_keyframes(
|
|
&ac, offset_mode, merge_mode, get_grease_pencil_keyframe_clipboard());
|
|
|
|
/* Only report an error if nothing was pasted, i.e. when both FCurve and GPencil failed. */
|
|
if (!gpframes_inbuf) {
|
|
switch (kf_empty) {
|
|
case KEYFRAME_PASTE_OK:
|
|
/* FCurve paste was ok, so it's all good. */
|
|
break;
|
|
|
|
case KEYFRAME_PASTE_NOWHERE_TO_PASTE:
|
|
BKE_report(op->reports, RPT_ERROR, "No selected F-Curves to paste into");
|
|
return OPERATOR_CANCELLED;
|
|
|
|
case KEYFRAME_PASTE_NOTHING_TO_PASTE:
|
|
BKE_report(op->reports, RPT_ERROR, "No data in the internal clipboard to paste");
|
|
return OPERATOR_CANCELLED;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Grease Pencil needs extra update to refresh the added keyframes. */
|
|
if (ac.datatype == ANIMCONT_GPENCIL || gpframes_inbuf) {
|
|
WM_event_add_notifier(C, NC_GPENCIL | ND_DATA, nullptr);
|
|
}
|
|
/* set notifier that keyframes have changed */
|
|
WM_event_add_notifier(C, NC_ANIMATION | ND_KEYFRAME | NA_EDITED, nullptr);
|
|
|
|
return OPERATOR_FINISHED;
|
|
}
|
|
|
|
static std::string actkeys_paste_get_description(bContext * /*C*/,
|
|
wmOperatorType * /*ot*/,
|
|
PointerRNA *ptr)
|
|
{
|
|
/* Custom description if the 'flipped' option is used. */
|
|
if (RNA_boolean_get(ptr, "flipped")) {
|
|
return BLI_strdup(TIP_("Paste keyframes from mirrored bones if they exist"));
|
|
}
|
|
|
|
/* Use the default description in the other cases. */
|
|
return "";
|
|
}
|
|
|
|
void ACTION_OT_paste(wmOperatorType *ot)
|
|
{
|
|
PropertyRNA *prop;
|
|
/* identifiers */
|
|
ot->name = "Paste Keyframes";
|
|
ot->idname = "ACTION_OT_paste";
|
|
ot->description =
|
|
"Paste keyframes from the internal clipboard for the selected channels, starting on the "
|
|
"current "
|
|
"frame";
|
|
|
|
/* API callbacks. */
|
|
// ot->invoke = WM_operator_props_popup; /* Better wait for action redo panel. */
|
|
ot->get_description = actkeys_paste_get_description;
|
|
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",
|
|
rna_enum_keyframe_paste_offset_items,
|
|
KEYFRAME_PASTE_OFFSET_CFRA_START,
|
|
"Offset",
|
|
"Paste time offset of keys");
|
|
RNA_def_enum(ot->srna,
|
|
"merge",
|
|
rna_enum_keyframe_paste_merge_items,
|
|
KEYFRAME_PASTE_MERGE_MIX,
|
|
"Type",
|
|
"Method of merging pasted keys and existing");
|
|
prop = RNA_def_boolean(
|
|
ot->srna, "flipped", false, "Flipped", "Paste keyframes from mirrored bones if they exist");
|
|
RNA_def_property_flag(prop, PROP_SKIP_SAVE);
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Keyframes: Insert Operator
|
|
* \{ */
|
|
|
|
/* defines for insert keyframes tool */
|
|
static const EnumPropertyItem prop_actkeys_insertkey_types[] = {
|
|
{1, "ALL", 0, "All Channels", ""},
|
|
{2, "SEL", 0, "Only Selected Channels", ""},
|
|
/* XXX not in all cases. */
|
|
{3, "GROUP", 0, "In Active Group", ""},
|
|
{0, nullptr, 0, nullptr, nullptr},
|
|
};
|
|
|
|
static void insert_gpencil_key(bAnimContext *ac,
|
|
bAnimListElem *ale,
|
|
const eGP_GetFrame_Mode add_frame_mode,
|
|
bGPdata **gpd_old)
|
|
{
|
|
Scene *scene = ac->scene;
|
|
bGPdata *gpd = (bGPdata *)ale->id;
|
|
bGPDlayer *gpl = (bGPDlayer *)ale->data;
|
|
BKE_gpencil_layer_frame_get(gpl, scene->r.cfra, add_frame_mode);
|
|
/* Check if the gpd changes to tag only once. */
|
|
if (gpd != *gpd_old) {
|
|
BKE_gpencil_tag(gpd);
|
|
*gpd_old = gpd;
|
|
}
|
|
}
|
|
|
|
static void insert_grease_pencil_key(bAnimContext *ac,
|
|
bAnimListElem *ale,
|
|
const bool hold_previous)
|
|
{
|
|
using namespace blender::bke::greasepencil;
|
|
Layer *layer = static_cast<Layer *>(ale->data);
|
|
GreasePencil *grease_pencil = reinterpret_cast<GreasePencil *>(ale->id);
|
|
const int current_frame_number = ac->scene->r.cfra;
|
|
|
|
if (layer->frames().contains(current_frame_number)) {
|
|
return;
|
|
}
|
|
|
|
bool changed = false;
|
|
if (hold_previous) {
|
|
const std::optional<int> active_frame_number = layer->start_frame_at(current_frame_number);
|
|
if (!active_frame_number) {
|
|
/* There is no active frame to hold to, or it's an end frame. Therefore just insert a blank
|
|
* frame. */
|
|
changed |= grease_pencil->insert_frame(*layer, current_frame_number) != nullptr;
|
|
}
|
|
else {
|
|
/* Duplicate the active frame. */
|
|
changed = grease_pencil->insert_duplicate_frame(
|
|
*layer, *active_frame_number, current_frame_number, false);
|
|
}
|
|
}
|
|
else {
|
|
/* Insert a blank frame. */
|
|
changed |= grease_pencil->insert_frame(*layer, current_frame_number) != nullptr;
|
|
}
|
|
|
|
if (changed) {
|
|
DEG_id_tag_update(&grease_pencil->id, ID_RECALC_GEOMETRY);
|
|
}
|
|
}
|
|
|
|
static void insert_fcurve_key(bAnimContext *ac,
|
|
bAnimListElem *ale,
|
|
const AnimationEvalContext anim_eval_context,
|
|
eInsertKeyFlags flag)
|
|
{
|
|
using namespace blender::animrig;
|
|
FCurve *fcu = (FCurve *)ale->key_data;
|
|
|
|
ReportList *reports = ac->reports;
|
|
Scene *scene = ac->scene;
|
|
ToolSettings *ts = scene->toolsettings;
|
|
|
|
/* These asserts are ensuring that the fcurve we're keying lives on an Action,
|
|
* rather than being an fcurve for e.g. a driver or NLA Strip. This should
|
|
* always hold true for this function, since all the other cases take
|
|
* different code paths before getting here. */
|
|
BLI_assert(ale->owner == nullptr);
|
|
BLI_assert(ale->fcurve_owner_id != nullptr);
|
|
BLI_assert(GS(ale->fcurve_owner_id->name) == ID_AC);
|
|
|
|
bAction *action = reinterpret_cast<bAction *>(ale->fcurve_owner_id);
|
|
ID *id = action_slot_get_id_for_keying(*ale->bmain, action->wrap(), ale->slot_handle, ale->id);
|
|
|
|
/* If we found an unambiguous ID to use for keying the channel, go through the
|
|
* normal keyframing code path. Otherwise, just directly key the fcurve
|
|
* itself. */
|
|
if (id) {
|
|
const std::optional<blender::StringRefNull> channel_group = fcu->grp ?
|
|
std::optional(fcu->grp->name) :
|
|
std::nullopt;
|
|
PointerRNA id_rna_pointer = RNA_id_pointer_create(id);
|
|
CombinedKeyingResult result = insert_keyframes(ac->bmain,
|
|
&id_rna_pointer,
|
|
channel_group,
|
|
{{fcu->rna_path, {}, fcu->array_index}},
|
|
std::nullopt,
|
|
anim_eval_context,
|
|
eBezTriple_KeyframeType(ts->keyframe_type),
|
|
flag);
|
|
if (result.get_count(SingleKeyingResult::SUCCESS) == 0) {
|
|
result.generate_reports(reports);
|
|
}
|
|
}
|
|
else {
|
|
/* TODO: when layered action strips are allowed to have time offsets, that
|
|
* mapping will need to be handled here. */
|
|
assert_baklava_phase_1_invariants(action->wrap());
|
|
|
|
/* Adjust current frame for NLA-scaling. */
|
|
const float cfra = ANIM_nla_tweakedit_remap(
|
|
ale, anim_eval_context.eval_time, NLATIME_CONVERT_UNMAP);
|
|
|
|
const float curval = evaluate_fcurve(fcu, cfra);
|
|
KeyframeSettings settings = get_keyframe_settings(true);
|
|
settings.keyframe_type = eBezTriple_KeyframeType(ts->keyframe_type);
|
|
insert_vert_fcurve(fcu, {cfra, curval}, settings, eInsertKeyFlags(0));
|
|
}
|
|
|
|
ale->update |= ANIM_UPDATE_DEFAULT;
|
|
}
|
|
|
|
/* this function is responsible for inserting new keyframes */
|
|
static void insert_action_keys(bAnimContext *ac, short mode)
|
|
{
|
|
ListBase anim_data = {nullptr, nullptr};
|
|
eAnimFilter_Flags filter;
|
|
|
|
Scene *scene = ac->scene;
|
|
ToolSettings *ts = scene->toolsettings;
|
|
eInsertKeyFlags flag;
|
|
|
|
eGP_GetFrame_Mode add_frame_mode;
|
|
bGPdata *gpd_old = nullptr;
|
|
|
|
/* filter data */
|
|
filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE | ANIMFILTER_FOREDIT |
|
|
ANIMFILTER_NODUPLIS);
|
|
if (mode == 2) {
|
|
filter |= ANIMFILTER_SEL;
|
|
}
|
|
else if (mode == 3) {
|
|
filter |= ANIMFILTER_ACTGROUPED;
|
|
}
|
|
|
|
ANIM_animdata_filter(ac, &anim_data, filter, ac->data, eAnimCont_Types(ac->datatype));
|
|
|
|
/* Init keyframing flag. */
|
|
flag = blender::animrig::get_keyframing_flags(scene);
|
|
|
|
/* GPLayers specific flags */
|
|
if (ts->gpencil_flags & GP_TOOL_FLAG_RETAIN_LAST) {
|
|
add_frame_mode = GP_GETFRAME_ADD_COPY;
|
|
}
|
|
else {
|
|
add_frame_mode = GP_GETFRAME_ADD_NEW;
|
|
}
|
|
const bool grease_pencil_hold_previous = ((ts->gpencil_flags & GP_TOOL_FLAG_RETAIN_LAST) != 0);
|
|
|
|
/* insert keyframes */
|
|
const AnimationEvalContext anim_eval_context = BKE_animsys_eval_context_construct(
|
|
ac->depsgraph, float(scene->r.cfra));
|
|
LISTBASE_FOREACH (bAnimListElem *, ale, &anim_data) {
|
|
switch (ale->type) {
|
|
case ANIMTYPE_GPLAYER:
|
|
insert_gpencil_key(ac, ale, add_frame_mode, &gpd_old);
|
|
break;
|
|
|
|
case ANIMTYPE_GREASE_PENCIL_LAYER:
|
|
insert_grease_pencil_key(ac, ale, grease_pencil_hold_previous);
|
|
break;
|
|
|
|
case ANIMTYPE_FCURVE:
|
|
insert_fcurve_key(ac, ale, anim_eval_context, flag);
|
|
break;
|
|
|
|
default:
|
|
BLI_assert_msg(false, "Keys cannot be inserted into this animation type.");
|
|
}
|
|
}
|
|
|
|
ANIM_animdata_update(ac, &anim_data);
|
|
ANIM_animdata_freelist(&anim_data);
|
|
}
|
|
|
|
/* ------------------- */
|
|
|
|
static wmOperatorStatus 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 (ac.datatype == ANIMCONT_MASK) {
|
|
BKE_report(op->reports, RPT_ERROR, "Insert Keyframes is not yet implemented for this mode");
|
|
return OPERATOR_CANCELLED;
|
|
}
|
|
|
|
/* what channels to affect? */
|
|
mode = RNA_enum_get(op->ptr, "type");
|
|
|
|
ANIM_deselect_keys_in_animation_editors(C);
|
|
|
|
/* insert keyframes */
|
|
insert_action_keys(&ac, mode);
|
|
|
|
/* set notifier that keyframes have changed */
|
|
if (ac.datatype == ANIMCONT_GPENCIL) {
|
|
WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, nullptr);
|
|
}
|
|
WM_event_add_notifier(C, NC_ANIMATION | ND_KEYFRAME | NA_ADDED, nullptr);
|
|
|
|
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", "");
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Keyframes: Duplicate Operator
|
|
* \{ */
|
|
|
|
static bool duplicate_action_keys(bAnimContext *ac)
|
|
{
|
|
ListBase anim_data = {nullptr, nullptr};
|
|
eAnimFilter_Flags filter;
|
|
bool changed = false;
|
|
|
|
/* filter data */
|
|
filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE | ANIMFILTER_FOREDIT |
|
|
ANIMFILTER_NODUPLIS);
|
|
ANIM_animdata_filter(ac, &anim_data, filter, ac->data, eAnimCont_Types(ac->datatype));
|
|
|
|
/* loop through filtered data and delete selected keys */
|
|
LISTBASE_FOREACH (bAnimListElem *, ale, &anim_data) {
|
|
if (ELEM(ale->type, ANIMTYPE_FCURVE, ANIMTYPE_NLACURVE)) {
|
|
changed |= duplicate_fcurve_keys((FCurve *)ale->key_data);
|
|
}
|
|
else if (ale->type == ANIMTYPE_GPLAYER) {
|
|
ED_gpencil_layer_frames_duplicate((bGPDlayer *)ale->data);
|
|
changed |= ED_gpencil_layer_frame_select_check((bGPDlayer *)ale->data);
|
|
}
|
|
else if (ale->type == ANIMTYPE_GREASE_PENCIL_LAYER) {
|
|
changed |= blender::ed::greasepencil::duplicate_selected_frames(
|
|
*reinterpret_cast<GreasePencil *>(ale->id),
|
|
static_cast<GreasePencilLayer *>(ale->data)->wrap());
|
|
}
|
|
else if (ale->type == ANIMTYPE_MASKLAYER) {
|
|
changed |= ED_masklayer_frames_duplicate((MaskLayer *)ale->data);
|
|
}
|
|
else {
|
|
BLI_assert(0);
|
|
}
|
|
|
|
ale->update |= ANIM_UPDATE_DEFAULT;
|
|
}
|
|
|
|
ANIM_animdata_update(ac, &anim_data);
|
|
ANIM_animdata_freelist(&anim_data);
|
|
|
|
return changed;
|
|
}
|
|
|
|
/* ------------------- */
|
|
|
|
static wmOperatorStatus actkeys_duplicate_exec(bContext *C, wmOperator * /*op*/)
|
|
{
|
|
bAnimContext ac;
|
|
|
|
/* get editor data */
|
|
if (ANIM_animdata_get_context(C, &ac) == 0) {
|
|
return OPERATOR_CANCELLED;
|
|
}
|
|
|
|
/* duplicate keyframes */
|
|
if (!duplicate_action_keys(&ac)) {
|
|
return OPERATOR_CANCELLED;
|
|
}
|
|
|
|
/* set notifier that keyframes have changed */
|
|
WM_event_add_notifier(C, NC_ANIMATION | ND_KEYFRAME | NA_ADDED, nullptr);
|
|
|
|
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->exec = actkeys_duplicate_exec;
|
|
ot->poll = ED_operator_action_active;
|
|
|
|
/* flags */
|
|
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Keyframes: Delete Operator
|
|
* \{ */
|
|
|
|
static bool delete_action_keys(bAnimContext *ac)
|
|
{
|
|
ListBase anim_data = {nullptr, nullptr};
|
|
eAnimFilter_Flags filter;
|
|
bool changed_final = false;
|
|
|
|
/* filter data */
|
|
filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE | ANIMFILTER_FOREDIT |
|
|
ANIMFILTER_NODUPLIS);
|
|
ANIM_animdata_filter(ac, &anim_data, filter, ac->data, eAnimCont_Types(ac->datatype));
|
|
|
|
/* loop through filtered data and delete selected keys */
|
|
LISTBASE_FOREACH (bAnimListElem *, ale, &anim_data) {
|
|
bool changed = false;
|
|
|
|
if (ale->type == ANIMTYPE_GPLAYER) {
|
|
changed = ED_gpencil_layer_frames_delete((bGPDlayer *)ale->data);
|
|
}
|
|
else if (ale->type == ANIMTYPE_GREASE_PENCIL_LAYER) {
|
|
GreasePencil *grease_pencil = reinterpret_cast<GreasePencil *>(ale->id);
|
|
changed = blender::ed::greasepencil::remove_all_selected_frames(
|
|
*grease_pencil, static_cast<GreasePencilLayer *>(ale->data)->wrap());
|
|
|
|
if (changed) {
|
|
DEG_id_tag_update(&grease_pencil->id, ID_RECALC_GEOMETRY);
|
|
}
|
|
}
|
|
else if (ale->type == ANIMTYPE_MASKLAYER) {
|
|
changed = ED_masklayer_frames_delete((MaskLayer *)ale->data);
|
|
}
|
|
else {
|
|
FCurve *fcu = (FCurve *)ale->key_data;
|
|
AnimData *adt = ale->adt;
|
|
|
|
/* delete selected keyframes only */
|
|
changed = BKE_fcurve_delete_keys_selected(fcu);
|
|
|
|
/* Only delete curve too if it won't be doing anything anymore */
|
|
if (BKE_fcurve_is_empty(fcu)) {
|
|
blender::animrig::animdata_fcurve_delete(adt, fcu);
|
|
ale->key_data = nullptr;
|
|
}
|
|
}
|
|
|
|
if (changed) {
|
|
ale->update |= ANIM_UPDATE_DEFAULT;
|
|
changed_final = true;
|
|
}
|
|
}
|
|
|
|
ANIM_animdata_update(ac, &anim_data);
|
|
ANIM_animdata_freelist(&anim_data);
|
|
|
|
return changed_final;
|
|
}
|
|
|
|
/* ------------------- */
|
|
|
|
static wmOperatorStatus actkeys_delete_exec(bContext *C, wmOperator * /*op*/)
|
|
{
|
|
bAnimContext ac;
|
|
|
|
/* get editor data */
|
|
if (ANIM_animdata_get_context(C, &ac) == 0) {
|
|
return OPERATOR_CANCELLED;
|
|
}
|
|
|
|
/* delete keyframes */
|
|
if (!delete_action_keys(&ac)) {
|
|
return OPERATOR_CANCELLED;
|
|
}
|
|
|
|
/* set notifier that keyframes have changed */
|
|
WM_event_add_notifier(C, NC_ANIMATION | ND_KEYFRAME | NA_REMOVED, nullptr);
|
|
|
|
return OPERATOR_FINISHED;
|
|
}
|
|
|
|
static wmOperatorStatus actkeys_delete_invoke(bContext *C,
|
|
wmOperator *op,
|
|
const wmEvent * /*event*/)
|
|
{
|
|
if (RNA_boolean_get(op->ptr, "confirm")) {
|
|
return WM_operator_confirm_ex(C,
|
|
op,
|
|
IFACE_("Delete selected keyframes?"),
|
|
nullptr,
|
|
IFACE_("Delete"),
|
|
ALERT_ICON_NONE,
|
|
false);
|
|
}
|
|
return actkeys_delete_exec(C, op);
|
|
}
|
|
|
|
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 = actkeys_delete_invoke;
|
|
ot->exec = actkeys_delete_exec;
|
|
ot->poll = ED_operator_action_active;
|
|
|
|
/* flags */
|
|
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
|
|
WM_operator_properties_confirm_or_exec(ot);
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Keyframes: Clean Operator
|
|
* \{ */
|
|
|
|
static void clean_action_keys(bAnimContext *ac, float thresh, bool clean_chan)
|
|
{
|
|
ListBase anim_data = {nullptr, nullptr};
|
|
eAnimFilter_Flags filter;
|
|
|
|
/* filter data */
|
|
filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE | ANIMFILTER_FOREDIT |
|
|
ANIMFILTER_FCURVESONLY | ANIMFILTER_NODUPLIS);
|
|
|
|
if (clean_chan) {
|
|
filter |= ANIMFILTER_SEL;
|
|
}
|
|
|
|
ANIM_animdata_filter(ac, &anim_data, filter, ac->data, eAnimCont_Types(ac->datatype));
|
|
|
|
const bool only_selected_keys = !clean_chan;
|
|
/* loop through filtered data and clean curves */
|
|
LISTBASE_FOREACH (bAnimListElem *, ale, &anim_data) {
|
|
clean_fcurve(ale, thresh, clean_chan, only_selected_keys);
|
|
|
|
ale->update |= ANIM_UPDATE_DEFAULT;
|
|
}
|
|
|
|
ANIM_animdata_update(ac, &anim_data);
|
|
ANIM_animdata_freelist(&anim_data);
|
|
}
|
|
|
|
/* ------------------- */
|
|
|
|
static wmOperatorStatus actkeys_clean_exec(bContext *C, wmOperator *op)
|
|
{
|
|
bAnimContext ac;
|
|
float thresh;
|
|
bool clean_chan;
|
|
|
|
/* get editor data */
|
|
if (ANIM_animdata_get_context(C, &ac) == 0) {
|
|
return OPERATOR_CANCELLED;
|
|
}
|
|
|
|
if (ELEM(ac.datatype, ANIMCONT_GPENCIL, ANIMCONT_MASK)) {
|
|
BKE_report(op->reports, RPT_ERROR, "Not implemented");
|
|
return OPERATOR_PASS_THROUGH;
|
|
}
|
|
|
|
/* get cleaning threshold */
|
|
thresh = RNA_float_get(op->ptr, "threshold");
|
|
clean_chan = RNA_boolean_get(op->ptr, "channels");
|
|
|
|
/* clean keyframes */
|
|
clean_action_keys(&ac, thresh, clean_chan);
|
|
|
|
/* set notifier that keyframes have changed */
|
|
WM_event_add_notifier(C, NC_ANIMATION | ND_KEYFRAME | NA_EDITED, nullptr);
|
|
|
|
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);
|
|
RNA_def_boolean(ot->srna, "channels", false, "Channels", "");
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Keyframes: Sample Operator
|
|
* \{ */
|
|
|
|
/* Evaluates the curves between each selected keyframe on each frame, and keys the value. */
|
|
static void bake_action_keys(bAnimContext *ac)
|
|
{
|
|
ListBase anim_data = {nullptr, nullptr};
|
|
eAnimFilter_Flags filter;
|
|
|
|
/* filter data */
|
|
filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE | ANIMFILTER_FOREDIT |
|
|
ANIMFILTER_FCURVESONLY | ANIMFILTER_NODUPLIS);
|
|
ANIM_animdata_filter(ac, &anim_data, filter, ac->data, eAnimCont_Types(ac->datatype));
|
|
|
|
/* Loop through filtered data and add keys between selected keyframes on every frame. */
|
|
LISTBASE_FOREACH (bAnimListElem *, ale, &anim_data) {
|
|
blender::animrig::bake_fcurve_segments((FCurve *)ale->key_data);
|
|
|
|
ale->update |= ANIM_UPDATE_DEPS;
|
|
}
|
|
|
|
ANIM_animdata_update(ac, &anim_data);
|
|
ANIM_animdata_freelist(&anim_data);
|
|
}
|
|
|
|
/* ------------------- */
|
|
|
|
static wmOperatorStatus actkeys_bake_exec(bContext *C, wmOperator *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)) {
|
|
BKE_report(op->reports, RPT_ERROR, "Not implemented");
|
|
return OPERATOR_PASS_THROUGH;
|
|
}
|
|
|
|
/* sample keyframes */
|
|
bake_action_keys(&ac);
|
|
|
|
/* set notifier that keyframes have changed */
|
|
WM_event_add_notifier(C, NC_ANIMATION | ND_KEYFRAME | NA_EDITED, nullptr);
|
|
|
|
return OPERATOR_FINISHED;
|
|
}
|
|
|
|
void ACTION_OT_bake_keys(wmOperatorType *ot)
|
|
{
|
|
/* identifiers */
|
|
ot->name = "Bake Keyframes";
|
|
ot->idname = "ACTION_OT_bake_keys";
|
|
ot->description = "Add keyframes on every frame between the selected keyframes";
|
|
|
|
/* API callbacks. */
|
|
ot->exec = actkeys_bake_exec;
|
|
ot->poll = ED_operator_action_active;
|
|
|
|
/* flags */
|
|
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Settings: 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 const 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, nullptr, 0, nullptr, nullptr},
|
|
};
|
|
|
|
/* this function is responsible for setting extrapolation mode for keyframes */
|
|
static void setexpo_action_keys(bAnimContext *ac, short mode)
|
|
{
|
|
ListBase anim_data = {nullptr, nullptr};
|
|
eAnimFilter_Flags filter;
|
|
|
|
/* filter data */
|
|
filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE | ANIMFILTER_FOREDIT |
|
|
ANIMFILTER_SEL | ANIMFILTER_FCURVESONLY | ANIMFILTER_NODUPLIS);
|
|
ANIM_animdata_filter(ac, &anim_data, filter, ac->data, eAnimCont_Types(ac->datatype));
|
|
|
|
/* loop through setting mode per F-Curve */
|
|
LISTBASE_FOREACH (bAnimListElem *, ale, &anim_data) {
|
|
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, fcu);
|
|
}
|
|
}
|
|
else if (mode == CLEAR_CYCLIC_EXPO) {
|
|
/* remove all the modifiers fitting this description */
|
|
FModifier *fcm, *fcn = nullptr;
|
|
|
|
for (fcm = static_cast<FModifier *>(fcu->modifiers.first); fcm; fcm = fcn) {
|
|
fcn = fcm->next;
|
|
|
|
if (fcm->type == FMODIFIER_TYPE_CYCLES) {
|
|
remove_fmodifier(&fcu->modifiers, fcm);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
ale->update |= ANIM_UPDATE_DEFAULT;
|
|
}
|
|
|
|
ANIM_animdata_update(ac, &anim_data);
|
|
ANIM_animdata_freelist(&anim_data);
|
|
}
|
|
|
|
/* ------------------- */
|
|
|
|
static wmOperatorStatus 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)) {
|
|
BKE_report(op->reports, RPT_ERROR, "Not implemented");
|
|
return OPERATOR_PASS_THROUGH;
|
|
}
|
|
|
|
/* get handle setting mode */
|
|
mode = RNA_enum_get(op->ptr, "type");
|
|
|
|
/* set handle type */
|
|
setexpo_action_keys(&ac, mode);
|
|
|
|
/* set notifier that keyframe properties have changed */
|
|
WM_event_add_notifier(C, NC_ANIMATION | ND_KEYFRAME_PROP, nullptr);
|
|
|
|
return OPERATOR_FINISHED;
|
|
}
|
|
|
|
void ACTION_OT_extrapolation_type(wmOperatorType *ot)
|
|
{
|
|
/* identifiers */
|
|
ot->name = "Set F-Curve 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", "");
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Settings: Set Interpolation-Type Operator
|
|
* \{ */
|
|
|
|
static wmOperatorStatus 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)) {
|
|
BKE_report(op->reports, RPT_ERROR, "Not implemented");
|
|
return OPERATOR_PASS_THROUGH;
|
|
}
|
|
|
|
/* get handle setting mode */
|
|
mode = RNA_enum_get(op->ptr, "type");
|
|
|
|
/* set handle type */
|
|
ANIM_animdata_keyframe_callback(&ac,
|
|
(ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE |
|
|
ANIMFILTER_FOREDIT | ANIMFILTER_NODUPLIS |
|
|
ANIMFILTER_FCURVESONLY),
|
|
ANIM_editkeyframes_ipo(mode));
|
|
|
|
/* set notifier that keyframe properties have changed */
|
|
WM_event_add_notifier(C, NC_ANIMATION | ND_KEYFRAME_PROP, nullptr);
|
|
|
|
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", rna_enum_beztriple_interpolation_mode_items, 0, "Type", "");
|
|
RNA_def_property_translation_context(ot->prop, BLT_I18NCONTEXT_ID_ACTION);
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Settings: Set Easing Operator
|
|
* \{ */
|
|
|
|
static wmOperatorStatus actkeys_easing_exec(bContext *C, wmOperator *op)
|
|
{
|
|
bAnimContext ac;
|
|
short mode;
|
|
|
|
/* get editor data */
|
|
if (ANIM_animdata_get_context(C, &ac) == 0) {
|
|
return OPERATOR_CANCELLED;
|
|
}
|
|
|
|
/* get handle setting mode */
|
|
mode = RNA_enum_get(op->ptr, "type");
|
|
|
|
/* set handle type */
|
|
ANIM_animdata_keyframe_callback(&ac,
|
|
(ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE |
|
|
ANIMFILTER_FOREDIT | ANIMFILTER_NODUPLIS |
|
|
ANIMFILTER_FCURVESONLY),
|
|
ANIM_editkeyframes_easing(mode));
|
|
|
|
/* set notifier that keyframe properties have changed */
|
|
WM_event_add_notifier(C, NC_ANIMATION | ND_KEYFRAME_PROP, nullptr);
|
|
|
|
return OPERATOR_FINISHED;
|
|
}
|
|
|
|
void ACTION_OT_easing_type(wmOperatorType *ot)
|
|
{
|
|
/* identifiers */
|
|
ot->name = "Set Keyframe Easing Type";
|
|
ot->idname = "ACTION_OT_easing_type";
|
|
ot->description =
|
|
"Set easing type for the F-Curve segments starting from the selected keyframes";
|
|
|
|
/* API callbacks. */
|
|
ot->invoke = WM_menu_invoke;
|
|
ot->exec = actkeys_easing_exec;
|
|
ot->poll = ED_operator_action_active;
|
|
|
|
/* flags */
|
|
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
|
|
|
|
/* id-props */
|
|
ot->prop = RNA_def_enum(
|
|
ot->srna, "type", rna_enum_beztriple_interpolation_easing_items, 0, "Type", "");
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Settings: 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 = {nullptr, nullptr};
|
|
eAnimFilter_Flags 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_FCURVESONLY | ANIMFILTER_NODUPLIS);
|
|
ANIM_animdata_filter(ac, &anim_data, filter, ac->data, eAnimCont_Types(ac->datatype));
|
|
|
|
/* Loop through setting flags for handles
|
|
* NOTE: we do not supply KeyframeEditData to the looper yet.
|
|
* Currently that's not necessary here.
|
|
*/
|
|
LISTBASE_FOREACH (bAnimListElem *, ale, &anim_data) {
|
|
FCurve *fcu = (FCurve *)ale->key_data;
|
|
|
|
/* any selected keyframes for editing? */
|
|
if (ANIM_fcurve_keyframes_loop(nullptr, fcu, nullptr, sel_cb, nullptr)) {
|
|
/* change type of selected handles */
|
|
ANIM_fcurve_keyframes_loop(nullptr, fcu, nullptr, edit_cb, BKE_fcurve_handles_recalc);
|
|
|
|
ale->update |= ANIM_UPDATE_DEFAULT;
|
|
}
|
|
}
|
|
|
|
ANIM_animdata_update(ac, &anim_data);
|
|
ANIM_animdata_freelist(&anim_data);
|
|
}
|
|
|
|
/* ------------------- */
|
|
|
|
static wmOperatorStatus 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)) {
|
|
BKE_report(op->reports, RPT_ERROR, "Not implemented");
|
|
return OPERATOR_PASS_THROUGH;
|
|
}
|
|
|
|
/* get handle setting mode */
|
|
mode = RNA_enum_get(op->ptr, "type");
|
|
|
|
/* set handle type */
|
|
sethandles_action_keys(&ac, mode);
|
|
|
|
/* set notifier that keyframe properties have changed */
|
|
WM_event_add_notifier(C, NC_ANIMATION | ND_KEYFRAME_PROP, nullptr);
|
|
|
|
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", rna_enum_keyframe_handle_type_items, 0, "Type", "");
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Settings: Set Keyframe-Type Operator
|
|
* \{ */
|
|
|
|
/* this function is responsible for setting keyframe type for keyframes */
|
|
static void setkeytype_action_keys(bAnimContext *ac, eBezTriple_KeyframeType mode)
|
|
{
|
|
ListBase anim_data = {nullptr, nullptr};
|
|
eAnimFilter_Flags filter;
|
|
KeyframeEditFunc set_cb = ANIM_editkeyframes_keytype(mode);
|
|
|
|
/* filter data */
|
|
filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE | ANIMFILTER_FOREDIT |
|
|
ANIMFILTER_NODUPLIS);
|
|
ANIM_animdata_filter(ac, &anim_data, filter, ac->data, eAnimCont_Types(ac->datatype));
|
|
|
|
/* Loop through setting BezTriple interpolation
|
|
* NOTE: we do not supply KeyframeEditData to the looper yet.
|
|
* Currently that's not necessary here.
|
|
*/
|
|
LISTBASE_FOREACH (bAnimListElem *, ale, &anim_data) {
|
|
switch (ale->type) {
|
|
case ANIMTYPE_GPLAYER:
|
|
ED_gpencil_layer_frames_keytype_set(static_cast<bGPDlayer *>(ale->data), mode);
|
|
ale->update |= ANIM_UPDATE_DEPS;
|
|
break;
|
|
|
|
case ANIMTYPE_GREASE_PENCIL_LAYER:
|
|
blender::ed::greasepencil::set_selected_frames_type(
|
|
static_cast<GreasePencilLayer *>(ale->data)->wrap(), mode);
|
|
ale->update |= ANIM_UPDATE_DEPS;
|
|
break;
|
|
|
|
case ANIMTYPE_FCURVE:
|
|
ANIM_fcurve_keyframes_loop(
|
|
nullptr, static_cast<FCurve *>(ale->key_data), nullptr, set_cb, nullptr);
|
|
ale->update |= ANIM_UPDATE_DEPS | ANIM_UPDATE_HANDLES;
|
|
break;
|
|
|
|
default:
|
|
BLI_assert_msg(false, "Keytype cannot be set into this animation type.");
|
|
}
|
|
}
|
|
|
|
ANIM_animdata_update(ac, &anim_data);
|
|
ANIM_animdata_freelist(&anim_data);
|
|
}
|
|
|
|
/* ------------------- */
|
|
|
|
static wmOperatorStatus actkeys_keytype_exec(bContext *C, wmOperator *op)
|
|
{
|
|
bAnimContext ac;
|
|
|
|
/* get editor data */
|
|
if (ANIM_animdata_get_context(C, &ac) == 0) {
|
|
return OPERATOR_CANCELLED;
|
|
}
|
|
|
|
if (ac.datatype == ANIMCONT_MASK) {
|
|
BKE_report(op->reports, RPT_ERROR, "Not implemented for Masks");
|
|
return OPERATOR_PASS_THROUGH;
|
|
}
|
|
|
|
const int mode = RNA_enum_get(op->ptr, "type");
|
|
setkeytype_action_keys(&ac, eBezTriple_KeyframeType(mode));
|
|
|
|
/* set notifier that keyframe properties have changed */
|
|
WM_event_add_notifier(C, NC_ANIMATION | ND_KEYFRAME_PROP, nullptr);
|
|
|
|
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", rna_enum_beztriple_keyframe_type_items, 0, "Type", "");
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Transform: Jump to Selected Frames Operator
|
|
* \{ */
|
|
|
|
static bool actkeys_framejump_poll(bContext *C)
|
|
{
|
|
/* prevent changes during render */
|
|
if (G.is_rendering) {
|
|
return false;
|
|
}
|
|
|
|
return ED_operator_action_active(C);
|
|
}
|
|
|
|
/* snap current-frame indicator to 'average time' of selected keyframe */
|
|
static wmOperatorStatus actkeys_framejump_exec(bContext *C, wmOperator * /*op*/)
|
|
{
|
|
bAnimContext ac;
|
|
ListBase anim_data = {nullptr, nullptr};
|
|
eAnimFilter_Flags filter;
|
|
KeyframeEditData ked = {{nullptr}};
|
|
|
|
/* 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_NODUPLIS);
|
|
ANIM_animdata_filter(&ac, &anim_data, filter, ac.data, eAnimCont_Types(ac.datatype));
|
|
|
|
LISTBASE_FOREACH (bAnimListElem *, ale, &anim_data) {
|
|
switch (ale->datatype) {
|
|
case ALE_GPFRAME: {
|
|
bGPDlayer *gpl = static_cast<bGPDlayer *>(ale->data);
|
|
|
|
LISTBASE_FOREACH (bGPDframe *, gpf, &gpl->frames) {
|
|
/* only if selected */
|
|
if (!(gpf->flag & GP_FRAME_SELECT)) {
|
|
continue;
|
|
}
|
|
/* store average time in float 1 (only do rounding at last step) */
|
|
ked.f1 += gpf->framenum;
|
|
|
|
/* increment number of items */
|
|
ked.i1++;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case ALE_GREASE_PENCIL_CEL: {
|
|
using namespace blender::bke::greasepencil;
|
|
const Layer &layer = *static_cast<Layer *>(ale->data);
|
|
for (auto [frame_number, frame] : layer.frames().items()) {
|
|
if (!frame.is_selected()) {
|
|
continue;
|
|
}
|
|
ked.f1 += frame_number;
|
|
ked.i1++;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case ALE_FCURVE: {
|
|
FCurve *fcurve = static_cast<FCurve *>(ale->key_data);
|
|
ANIM_nla_mapping_apply_if_needed_fcurve(ale, fcurve, false, true);
|
|
ANIM_fcurve_keyframes_loop(&ked, fcurve, nullptr, bezt_calc_average, nullptr);
|
|
ANIM_nla_mapping_apply_if_needed_fcurve(ale, fcurve, true, true);
|
|
break;
|
|
}
|
|
|
|
default:
|
|
BLI_assert_msg(false, "Cannot jump to keyframe into this animation type.");
|
|
}
|
|
}
|
|
|
|
ANIM_animdata_freelist(&anim_data);
|
|
|
|
/* set the new current frame value, based on the average time */
|
|
if (ked.i1) {
|
|
Scene *scene = ac.scene;
|
|
scene->r.cfra = round_fl_to_int(ked.f1 / ked.i1);
|
|
scene->r.subframe = 0.0f;
|
|
}
|
|
|
|
/* 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;
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Transform: Snap Keyframes Operator
|
|
* \{ */
|
|
|
|
/* defines for snap keyframes tool */
|
|
static const EnumPropertyItem prop_actkeys_snap_types[] = {
|
|
{ACTKEYS_SNAP_CFRA,
|
|
"CFRA",
|
|
0,
|
|
"Selection to Current Frame",
|
|
"Snap selected keyframes to the current frame"},
|
|
{ACTKEYS_SNAP_NEAREST_FRAME,
|
|
"NEAREST_FRAME",
|
|
0,
|
|
"Selection to Nearest Frame",
|
|
"Snap selected keyframes to the nearest (whole) frame "
|
|
"(use to fix accidental subframe offsets)"},
|
|
{ACTKEYS_SNAP_NEAREST_SECOND,
|
|
"NEAREST_SECOND",
|
|
0,
|
|
"Selection to Nearest Second",
|
|
"Snap selected keyframes to the nearest second"},
|
|
{ACTKEYS_SNAP_NEAREST_MARKER,
|
|
"NEAREST_MARKER",
|
|
0,
|
|
"Selection to Nearest Marker",
|
|
"Snap selected keyframes to the nearest marker"},
|
|
{0, nullptr, 0, nullptr, nullptr},
|
|
};
|
|
|
|
/* this function is responsible for snapping keyframes to frame-times */
|
|
static void snap_action_keys(bAnimContext *ac, short mode)
|
|
{
|
|
ListBase anim_data = {nullptr, nullptr};
|
|
eAnimFilter_Flags filter;
|
|
|
|
KeyframeEditData ked = {{nullptr}};
|
|
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_NODUPLIS);
|
|
}
|
|
ANIM_animdata_filter(ac, &anim_data, filter, ac->data, eAnimCont_Types(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 : nullptr;
|
|
ked.list.last = (ac->markers) ? ac->markers->last : nullptr;
|
|
}
|
|
|
|
/* snap keyframes */
|
|
LISTBASE_FOREACH (bAnimListElem *, ale, &anim_data) {
|
|
if (ale->type == ANIMTYPE_GPLAYER) {
|
|
ED_gpencil_layer_snap_frames(static_cast<bGPDlayer *>(ale->data), ac->scene, mode);
|
|
}
|
|
else if (ale->type == ANIMTYPE_GREASE_PENCIL_LAYER) {
|
|
GreasePencil *grease_pencil = reinterpret_cast<GreasePencil *>(ale->id);
|
|
GreasePencilLayer *layer = static_cast<GreasePencilLayer *>(ale->data);
|
|
|
|
const bool changed = blender::ed::greasepencil::snap_selected_frames(
|
|
*grease_pencil, layer->wrap(), *(ac->scene), static_cast<eEditKeyframes_Snap>(mode));
|
|
|
|
if (changed) {
|
|
DEG_id_tag_update(&grease_pencil->id, ID_RECALC_GEOMETRY);
|
|
}
|
|
}
|
|
else if (ale->type == ANIMTYPE_MASKLAYER) {
|
|
ED_masklayer_snap_frames(static_cast<MaskLayer *>(ale->data), ac->scene, mode);
|
|
}
|
|
else {
|
|
FCurve *fcurve = static_cast<FCurve *>(ale->key_data);
|
|
ANIM_nla_mapping_apply_if_needed_fcurve(ale, fcurve, false, false);
|
|
ANIM_fcurve_keyframes_loop(&ked, fcurve, nullptr, edit_cb, BKE_fcurve_handles_recalc);
|
|
BKE_fcurve_merge_duplicate_keys(
|
|
fcurve, SELECT, false); /* only use handles in graph editor */
|
|
ANIM_nla_mapping_apply_if_needed_fcurve(ale, fcurve, true, false);
|
|
}
|
|
|
|
ale->update |= ANIM_UPDATE_DEFAULT;
|
|
}
|
|
|
|
ANIM_animdata_update(ac, &anim_data);
|
|
ANIM_animdata_freelist(&anim_data);
|
|
}
|
|
|
|
/* ------------------- */
|
|
|
|
static wmOperatorStatus 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);
|
|
|
|
/* set notifier that keyframes have changed */
|
|
WM_event_add_notifier(C, NC_ANIMATION | ND_KEYFRAME | NA_EDITED, nullptr);
|
|
|
|
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", "");
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Transform: Mirror Keyframes Operator
|
|
* \{ */
|
|
|
|
/* defines for mirror keyframes tool */
|
|
static const 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 Zero Value",
|
|
"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, nullptr, 0, nullptr, nullptr},
|
|
};
|
|
|
|
/* this function is responsible for mirroring keyframes */
|
|
static void mirror_action_keys(bAnimContext *ac, short mode)
|
|
{
|
|
ListBase anim_data = {nullptr, nullptr};
|
|
eAnimFilter_Flags filter;
|
|
|
|
KeyframeEditData ked = {{nullptr}};
|
|
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 */
|
|
filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE | ANIMFILTER_FOREDIT |
|
|
ANIMFILTER_NODUPLIS);
|
|
ANIM_animdata_filter(ac, &anim_data, filter, ac->data, eAnimCont_Types(ac->datatype));
|
|
|
|
/* mirror keyframes */
|
|
LISTBASE_FOREACH (bAnimListElem *, ale, &anim_data) {
|
|
if (ale->type == ANIMTYPE_GPLAYER) {
|
|
ED_gpencil_layer_mirror_frames(static_cast<bGPDlayer *>(ale->data), ac->scene, mode);
|
|
}
|
|
else if (ale->type == ANIMTYPE_GREASE_PENCIL_LAYER) {
|
|
GreasePencil *grease_pencil = reinterpret_cast<GreasePencil *>(ale->id);
|
|
GreasePencilLayer *layer = static_cast<GreasePencilLayer *>(ale->data);
|
|
|
|
const bool changed = blender::ed::greasepencil::mirror_selected_frames(
|
|
*grease_pencil, layer->wrap(), *(ac->scene), static_cast<eEditKeyframes_Mirror>(mode));
|
|
|
|
if (changed) {
|
|
DEG_id_tag_update(&grease_pencil->id, ID_RECALC_GEOMETRY);
|
|
}
|
|
}
|
|
else if (ale->type == ANIMTYPE_MASKLAYER) {
|
|
/* TODO */
|
|
}
|
|
else {
|
|
FCurve *fcurve = static_cast<FCurve *>(ale->key_data);
|
|
ANIM_nla_mapping_apply_if_needed_fcurve(ale, fcurve, false, false);
|
|
ANIM_fcurve_keyframes_loop(&ked, fcurve, nullptr, edit_cb, BKE_fcurve_handles_recalc);
|
|
ANIM_nla_mapping_apply_if_needed_fcurve(ale, fcurve, true, false);
|
|
}
|
|
|
|
ale->update |= ANIM_UPDATE_DEFAULT;
|
|
}
|
|
|
|
ANIM_animdata_update(ac, &anim_data);
|
|
ANIM_animdata_freelist(&anim_data);
|
|
}
|
|
|
|
/* ------------------- */
|
|
|
|
static wmOperatorStatus 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;
|
|
}
|
|
|
|
/* get mirroring mode */
|
|
mode = RNA_enum_get(op->ptr, "type");
|
|
|
|
/* mirror keyframes */
|
|
mirror_action_keys(&ac, mode);
|
|
|
|
/* set notifier that keyframes have changed */
|
|
WM_event_add_notifier(C, NC_ANIMATION | ND_KEYFRAME | NA_EDITED, nullptr);
|
|
|
|
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", "");
|
|
}
|
|
|
|
/** \} */
|