2.5 - Action Editor: Copy/Paste

This can be activated using Ctrl-C/V and the buttons on the header.

It still uses an ugly global copy/paste buffer for now. In future, we could investigate alternative methods...
This commit is contained in:
Joshua Leung
2008-12-29 11:04:55 +00:00
parent 1d42afe561
commit 2c4c7004ae
5 changed files with 368 additions and 3 deletions

View File

@@ -89,9 +89,354 @@
/* GENERAL STUFF */
// TODO:
// - delete
// - insert key
// - copy/paste
/* ******************** Copy/Paste Keyframes Operator ************************* */
/* - The copy/paste buffer currently stores a set of Action Channels, with temporary
* IPO-blocks, and also temporary IpoCurves which only contain the selected keyframes.
* - Only pastes between compatable data is possible (i.e. same achan->name, ipo-curve type, etc.)
* Unless there is only one element in the buffer, names are also tested to check for compatability.
* - All pasted frames are offset by the same amount. This is calculated as the difference in the times of
* the current frame and the 'first keyframe' (i.e. the earliest one in all channels).
* - The earliest frame is calculated per copy operation.
*/
/* globals for copy/paste data (like for other copy/paste buffers) */
ListBase actcopybuf = {NULL, NULL};
static float actcopy_firstframe= 999999999.0f;
/* This function frees any MEM_calloc'ed copy/paste buffer data */
// XXX find some header to put this in!
void free_actcopybuf ()
{
bActionChannel *achan, *anext;
bConstraintChannel *conchan, *cnext;
for (achan= actcopybuf.first; achan; achan= anext) {
anext= achan->next;
if (achan->ipo) {
free_ipo(achan->ipo);
MEM_freeN(achan->ipo);
}
for (conchan=achan->constraintChannels.first; conchan; conchan=cnext) {
cnext= conchan->next;
if (conchan->ipo) {
free_ipo(conchan->ipo);
MEM_freeN(conchan->ipo);
}
BLI_freelinkN(&achan->constraintChannels, conchan);
}
BLI_freelinkN(&actcopybuf, achan);
}
actcopybuf.first= actcopybuf.last= NULL;
actcopy_firstframe= 999999999.0f;
}
/* ------------------- */
/* This function adds data to the copy/paste buffer, freeing existing data first
* Only the selected action channels gets their selected keyframes copied.
*/
static short copy_action_keys (bAnimContext *ac)
{
ListBase anim_data = {NULL, NULL};
bAnimListElem *ale;
int filter;
/* clear buffer first */
free_actcopybuf();
/* filter data */
filter= (ANIMFILTER_VISIBLE | ANIMFILTER_SEL | ANIMFILTER_IPOKEYS);
ANIM_animdata_filter(&anim_data, filter, ac->data, ac->datatype);
/* assume that each of these is an ipo-block */
for (ale= anim_data.first; ale; ale= ale->next) {
bActionChannel *achan;
Ipo *ipo= ale->key_data;
Ipo *ipn;
IpoCurve *icu, *icn;
BezTriple *bezt;
int i;
/* coerce an action-channel out of owner */
if (ale->ownertype == ANIMTYPE_ACHAN) {
bActionChannel *achanO= ale->owner;
achan= MEM_callocN(sizeof(bActionChannel), "ActCopyPasteAchan");
strcpy(achan->name, achanO->name);
}
else if (ale->ownertype == ANIMTYPE_SHAPEKEY) {
achan= MEM_callocN(sizeof(bActionChannel), "ActCopyPasteAchan");
strcpy(achan->name, "#ACP_ShapeKey");
}
else
continue;
BLI_addtail(&actcopybuf, achan);
/* add constraint channel if needed, then add new ipo-block */
if (ale->type == ANIMTYPE_CONCHAN) {
bConstraintChannel *conchanO= ale->data;
bConstraintChannel *conchan;
conchan= MEM_callocN(sizeof(bConstraintChannel), "ActCopyPasteConchan");
strcpy(conchan->name, conchanO->name);
BLI_addtail(&achan->constraintChannels, conchan);
conchan->ipo= ipn= MEM_callocN(sizeof(Ipo), "ActCopyPasteIpo");
}
else {
achan->ipo= ipn= MEM_callocN(sizeof(Ipo), "ActCopyPasteIpo");
}
ipn->blocktype = ipo->blocktype;
/* now loop through curves, and only copy selected keyframes */
for (icu= ipo->curve.first; icu; icu= icu->next) {
/* allocate a new curve */
icn= MEM_callocN(sizeof(IpoCurve), "ActCopyPasteIcu");
icn->blocktype = icu->blocktype;
icn->adrcode = icu->adrcode;
BLI_addtail(&ipn->curve, icn);
/* find selected BezTriples to add to the buffer (and set first frame) */
for (i=0, bezt=icu->bezt; i < icu->totvert; i++, bezt++) {
if (BEZSELECTED(bezt)) {
/* add to buffer ipo-curve */
insert_bezt_icu(icn, bezt);
/* check if this is the earliest frame encountered so far */
if (bezt->vec[1][0] < actcopy_firstframe)
actcopy_firstframe= bezt->vec[1][0];
}
}
}
}
/* check if anything ended up in the buffer */
if (ELEM(NULL, actcopybuf.first, actcopybuf.last))
// error("Nothing copied to buffer");
return -1;
/* free temp memory */
BLI_freelistN(&anim_data);
/* everything went fine */
return 0;
}
static short paste_action_keys (bAnimContext *ac)
{
ListBase anim_data = {NULL, NULL};
bAnimListElem *ale;
int filter;
const Scene *scene= (ac->scene);
const float offset = (float)(CFRA - actcopy_firstframe);
char *actname = NULL, *conname = NULL;
short no_name= 0;
/* check if buffer is empty */
if (ELEM(NULL, actcopybuf.first, actcopybuf.last)) {
//error("No data in buffer to paste");
return -1;
}
/* check if single channel in buffer (disregard names if so) */
if (actcopybuf.first == actcopybuf.last)
no_name= 1;
/* filter data */
filter= (ANIMFILTER_VISIBLE | ANIMFILTER_SEL | ANIMFILTER_FOREDIT | ANIMFILTER_IPOKEYS);
ANIM_animdata_filter(&anim_data, filter, ac->data, ac->datatype);
/* from selected channels */
for (ale= anim_data.first; ale; ale= ale->next) {
Ipo *ipo_src = NULL;
bActionChannel *achan;
IpoCurve *ico, *icu;
BezTriple *bezt;
int i;
/* find suitable IPO-block from buffer to paste from */
for (achan= actcopybuf.first; achan; achan= achan->next) {
/* try to match data */
if (ale->ownertype == ANIMTYPE_ACHAN) {
bActionChannel *achant= ale->owner;
/* check if we have a corresponding action channel */
if ((no_name) || (strcmp(achan->name, achant->name)==0)) {
actname= achant->name;
/* check if this is a constraint channel */
if (ale->type == ANIMTYPE_CONCHAN) {
bConstraintChannel *conchant= ale->data;
bConstraintChannel *conchan;
for (conchan=achan->constraintChannels.first; conchan; conchan=conchan->next) {
if (strcmp(conchan->name, conchant->name)==0) {
conname= conchant->name;
ipo_src= conchan->ipo;
break;
}
}
if (ipo_src) break;
}
else {
ipo_src= achan->ipo;
break;
}
}
}
else if (ale->ownertype == ANIMTYPE_SHAPEKEY) {
/* check if this action channel is "#ACP_ShapeKey" */
if ((no_name) || (strcmp(achan->name, "#ACP_ShapeKey")==0)) {
actname= NULL;
ipo_src= achan->ipo;
break;
}
}
}
/* this shouldn't happen, but it might */
if (ipo_src == NULL)
continue;
/* loop over curves, pasting keyframes */
for (ico= ipo_src->curve.first; ico; ico= ico->next) {
/* get IPO-curve to paste to (IPO-curve might not exist for destination, so gets created) */
icu= verify_ipocurve(ale->id, ico->blocktype, actname, conname, NULL, ico->adrcode, 1);
if (icu) {
/* just start pasting, with the the first keyframe on the current frame, and so on */
for (i=0, bezt=ico->bezt; i < ico->totvert; i++, bezt++) {
/* temporarily apply offset to src beztriple while copying */
bezt->vec[0][0] += offset;
bezt->vec[1][0] += offset;
bezt->vec[2][0] += offset;
/* insert the keyframe */
insert_bezt_icu(icu, bezt);
/* un-apply offset from src beztriple after copying */
bezt->vec[0][0] -= offset;
bezt->vec[1][0] -= offset;
bezt->vec[2][0] -= offset;
}
/* recalculate channel's handles? */
calchandles_ipocurve(icu);
}
}
}
/* free temp memory */
BLI_freelistN(&anim_data);
/* do depsgraph updates (for 3d-view)? */
#if 0
if ((ob) && (G.saction->pin==0)) {
if (ob->type == OB_ARMATURE)
DAG_object_flush_update(G.scene, ob, OB_RECALC_OB|OB_RECALC_DATA);
else
DAG_object_flush_update(G.scene, ob, OB_RECALC_OB);
}
#endif
return 0;
}
/* ------------------- */
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...
}
else {
if (copy_action_keys(&ac)) {
printf("Action Copy: No keyframes copied to copy-paste buffer\n");
uiPupmenuError(C, "No keyframes copied to copy-paste buffer");
}
}
/* set notifier tha things have changed */
ED_area_tag_redraw(CTX_wm_area(C)); // FIXME... should be updating 'keyframes' data context or so instead!
return OPERATOR_FINISHED;
}
void ACT_OT_keyframes_copy (wmOperatorType *ot)
{
PropertyRNA *prop;
/* identifiers */
ot->name= "Copy Keyframes";
ot->idname= "ACT_OT_keyframes_copy";
/* api callbacks */
ot->exec= actkeys_copy_exec;
ot->poll= ED_operator_areaactive;
/* flags */
ot->flag= OPTYPE_REGISTER/*|OPTYPE_UNDO*/;
}
static int actkeys_paste_exec(bContext *C, wmOperator *op)
{
bAnimContext ac;
/* get editor data */
if (ANIM_animdata_get_context(C, &ac) == 0)
return OPERATOR_CANCELLED;
/* paste keyframes */
if (ac.datatype == ANIMCONT_GPENCIL) {
// FIXME...
}
else {
if (paste_action_keys(&ac)) {
printf("Action Paste: Nothing to paste, as Copy-Paste buffer was empty.\n");
uiPupmenuError(C, "Nothing to paste, as Copy-Paste buffer was empty.");
}
}
/* validate keyframes after editing */
ANIM_editkeyframes_refresh(&ac);
/* set notifier tha things have changed */
ED_area_tag_redraw(CTX_wm_area(C)); // FIXME... should be updating 'keyframes' data context or so instead!
return OPERATOR_FINISHED;
}
void ACT_OT_keyframes_paste (wmOperatorType *ot)
{
PropertyRNA *prop;
/* identifiers */
ot->name= "Paste Keyframes";
ot->idname= "ACT_OT_keyframes_paste";
/* api callbacks */
ot->exec= actkeys_paste_exec;
ot->poll= ED_operator_areaactive;
/* flags */
ot->flag= OPTYPE_REGISTER/*|OPTYPE_UNDO*/;
}
/* ******************** Delete Keyframes Operator ************************* */

View File

@@ -461,6 +461,13 @@ static void do_action_buttons(bContext *C, void *arg, int event)
case B_REDR:
ED_region_tag_redraw(CTX_wm_region(C));
break;
case B_ACTCOPYKEYS:
WM_operator_name_call(C, "ACT_OT_keyframes_copy", WM_OP_EXEC_REGION_WIN, NULL);
break;
case B_ACTPASTEKEYS:
WM_operator_name_call(C, "ACT_OT_keyframes_paste", WM_OP_EXEC_REGION_WIN, NULL);
break;
}
}

View File

@@ -73,6 +73,9 @@ enum {
/* ***************************************** */
/* action_edit_keyframes.c */
void ACT_OT_keyframes_copy(struct wmOperatorType *ot);
void ACT_OT_keyframes_paste(struct wmOperatorType *ot);
void ACT_OT_keyframes_delete(struct wmOperatorType *ot);
void ACT_OT_keyframes_clean(struct wmOperatorType *ot);
void ACT_OT_keyframes_sample(struct wmOperatorType *ot);

View File

@@ -80,6 +80,8 @@ void action_operatortypes(void)
WM_operatortype_append(ACT_OT_keyframes_sample);
WM_operatortype_append(ACT_OT_keyframes_clean);
WM_operatortype_append(ACT_OT_keyframes_delete);
WM_operatortype_append(ACT_OT_keyframes_copy);
WM_operatortype_append(ACT_OT_keyframes_paste);
}
/* ************************** registration - keymaps **********************************/
@@ -127,6 +129,11 @@ static void action_keymap_keyframes (wmWindowManager *wm, ListBase *keymap)
WM_keymap_add_item(keymap, "ACT_OT_keyframes_delete", XKEY, KM_PRESS, 0, 0);
WM_keymap_add_item(keymap, "ACT_OT_keyframes_delete", DELKEY, KM_PRESS, 0, 0);
/* copy/paste */
// XXX - should we keep these?
WM_keymap_add_item(keymap, "ACT_OT_keyframes_copy", CKEY, KM_PRESS, KM_CTRL, 0);
WM_keymap_add_item(keymap, "ACT_OT_keyframes_paste", VKEY, KM_PRESS, KM_CTRL, 0);
/* transform system */
transform_keymap_for_space(wm, keymap, SPACE_ACTION);
}

View File

@@ -166,6 +166,9 @@ extern ListBase editelems;
extern wchar_t *copybuf;
extern wchar_t *copybufinfo;
// XXX copy/paste buffer stuff...
extern void free_actcopybuf();
/* called in creator.c even... tsk, split this! */
void WM_exit(bContext *C)
{
@@ -219,7 +222,7 @@ void WM_exit(bContext *C)
free_blender(); /* blender.c, does entire library */
// free_matcopybuf();
// free_ipocopybuf();
// free_actcopybuf();
free_actcopybuf();
// free_vertexpaint();
// free_imagepaint();