740 lines
18 KiB
C
740 lines
18 KiB
C
|
/**
|
||
|
* $Id$
|
||
|
*
|
||
|
* ***** 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||
|
*
|
||
|
* The Original Code is Copyright (C) 2008, Blender Foundation
|
||
|
* This is a new part of Blender
|
||
|
*
|
||
|
* Contributor(s): Joshua Leung
|
||
|
*
|
||
|
* ***** END GPL LICENSE BLOCK *****
|
||
|
*/
|
||
|
|
||
|
#include <stdio.h>
|
||
|
#include <string.h>
|
||
|
#include <stdlib.h>
|
||
|
#include <stddef.h>
|
||
|
#include <math.h>
|
||
|
|
||
|
#include "MEM_guardedalloc.h"
|
||
|
|
||
|
#include "BMF_Api.h"
|
||
|
|
||
|
#include "BLI_arithb.h"
|
||
|
#include "BLI_blenlib.h"
|
||
|
|
||
|
#include "DNA_listBase.h"
|
||
|
#include "DNA_action_types.h"
|
||
|
#include "DNA_gpencil_types.h"
|
||
|
#include "DNA_scene_types.h"
|
||
|
#include "DNA_screen_types.h"
|
||
|
#include "DNA_space_types.h"
|
||
|
#include "DNA_userdef_types.h"
|
||
|
#include "DNA_view3d_types.h"
|
||
|
|
||
|
#include "BKE_global.h"
|
||
|
#include "BKE_utildefines.h"
|
||
|
#include "BKE_blender.h"
|
||
|
#include "BKE_ipo.h"
|
||
|
|
||
|
#include "BIF_gl.h"
|
||
|
#include "BIF_glutil.h"
|
||
|
|
||
|
#include "PIL_time.h"
|
||
|
|
||
|
#include "ED_anim_api.h"
|
||
|
#include "ED_keyframes_edit.h"
|
||
|
#include "ED_keyframes_draw.h"
|
||
|
#include "ED_gpencil.h"
|
||
|
#include "ED_util.h"
|
||
|
#include "ED_types.h"
|
||
|
|
||
|
#include "gpencil_intern.h"
|
||
|
|
||
|
/* XXX */
|
||
|
static void actdata_filter() {}
|
||
|
static void BIF_undo_push() {}
|
||
|
static void error() {}
|
||
|
static void *get_action_context() {return NULL;}
|
||
|
static int find_nearest_marker_time() {return 1;}
|
||
|
/* XXX */
|
||
|
|
||
|
|
||
|
/* ***************************************** */
|
||
|
/* NOTE ABOUT THIS FILE:
|
||
|
* This file contains code for editing Grease Pencil data in the Action Editor
|
||
|
* as a 'keyframes', so that a user can adjust the timing of Grease Pencil drawings.
|
||
|
* Therefore, this file mostly contains functions for selecting Grease-Pencil frames.
|
||
|
*/
|
||
|
/* ***************************************** */
|
||
|
/* Generics - Loopers */
|
||
|
|
||
|
/* Loops over the gp-frames for a gp-layer, and applies the given callback */
|
||
|
short gplayer_frames_looper (bGPDlayer *gpl, Scene *scene, short (*gpf_cb)(bGPDframe *, Scene *))
|
||
|
{
|
||
|
bGPDframe *gpf;
|
||
|
|
||
|
/* error checker */
|
||
|
if (gpl == NULL)
|
||
|
return 0;
|
||
|
|
||
|
/* do loop */
|
||
|
for (gpf= gpl->frames.first; gpf; gpf= gpf->next) {
|
||
|
/* execute callback */
|
||
|
if (gpf_cb(gpf, scene))
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
/* nothing to return */
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
/* ****************************************** */
|
||
|
/* Data Conversion Tools */
|
||
|
|
||
|
/* make a listing all the gp-frames in a layer as cfraelems */
|
||
|
void gplayer_make_cfra_list (bGPDlayer *gpl, ListBase *elems, short onlysel)
|
||
|
{
|
||
|
bGPDframe *gpf;
|
||
|
CfraElem *ce;
|
||
|
|
||
|
/* error checking */
|
||
|
if (ELEM(NULL, gpl, elems))
|
||
|
return;
|
||
|
|
||
|
/* loop through gp-frames, adding */
|
||
|
for (gpf= gpl->frames.first; gpf; gpf= gpf->next) {
|
||
|
if ((onlysel == 0) || (gpf->flag & GP_FRAME_SELECT)) {
|
||
|
ce= MEM_callocN(sizeof(CfraElem), "CfraElem");
|
||
|
|
||
|
ce->cfra= (float)gpf->framenum;
|
||
|
ce->sel= (gpf->flag & GP_FRAME_SELECT) ? 1 : 0;
|
||
|
|
||
|
BLI_addtail(elems, ce);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* ***************************************** */
|
||
|
/* Selection Tools */
|
||
|
|
||
|
/* check if one of the frames in this layer is selected */
|
||
|
short is_gplayer_frame_selected (bGPDlayer *gpl)
|
||
|
{
|
||
|
bGPDframe *gpf;
|
||
|
|
||
|
/* error checking */
|
||
|
if (gpl == NULL)
|
||
|
return 0;
|
||
|
|
||
|
/* stop at the first one found */
|
||
|
for (gpf= gpl->frames.first; gpf; gpf= gpf->next) {
|
||
|
if (gpf->flag & GP_FRAME_SELECT)
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
/* not found */
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
/* helper function - select gp-frame based on SELECT_* mode */
|
||
|
static void gpframe_select (bGPDframe *gpf, short select_mode)
|
||
|
{
|
||
|
switch (select_mode) {
|
||
|
case SELECT_ADD:
|
||
|
gpf->flag |= GP_FRAME_SELECT;
|
||
|
break;
|
||
|
case SELECT_SUBTRACT:
|
||
|
gpf->flag &= ~GP_FRAME_SELECT;
|
||
|
break;
|
||
|
case SELECT_INVERT:
|
||
|
gpf->flag ^= GP_FRAME_SELECT;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* set all/none/invert select (like above, but with SELECT_* modes) */
|
||
|
void select_gpencil_frames (bGPDlayer *gpl, short select_mode)
|
||
|
{
|
||
|
bGPDframe *gpf;
|
||
|
|
||
|
/* error checking */
|
||
|
if (gpl == NULL)
|
||
|
return;
|
||
|
|
||
|
/* handle according to mode */
|
||
|
for (gpf= gpl->frames.first; gpf; gpf= gpf->next) {
|
||
|
gpframe_select(gpf, select_mode);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* set all/none/invert select */
|
||
|
void set_gplayer_frame_selection (bGPDlayer *gpl, short mode)
|
||
|
{
|
||
|
/* error checking */
|
||
|
if (gpl == NULL)
|
||
|
return;
|
||
|
|
||
|
/* convert mode to select_mode */
|
||
|
switch (mode) {
|
||
|
case 2:
|
||
|
mode= SELECT_INVERT;
|
||
|
break;
|
||
|
case 1:
|
||
|
mode= SELECT_ADD;
|
||
|
break;
|
||
|
case 0:
|
||
|
mode= SELECT_SUBTRACT;
|
||
|
break;
|
||
|
default:
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
/* now call the standard function */
|
||
|
select_gpencil_frames (gpl, mode);
|
||
|
}
|
||
|
|
||
|
/* select the frame in this layer that occurs on this frame (there should only be one at most) */
|
||
|
void select_gpencil_frame (bGPDlayer *gpl, int selx, short select_mode)
|
||
|
{
|
||
|
bGPDframe *gpf;
|
||
|
|
||
|
/* search through frames for a match */
|
||
|
for (gpf= gpl->frames.first; gpf; gpf= gpf->next) {
|
||
|
/* there should only be one frame with this frame-number */
|
||
|
if (gpf->framenum == selx) {
|
||
|
gpframe_select(gpf, select_mode);
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* select the frames in this layer that occur within the bounds specified */
|
||
|
void borderselect_gplayer_frames (bGPDlayer *gpl, float min, float max, short select_mode)
|
||
|
{
|
||
|
bGPDframe *gpf;
|
||
|
|
||
|
/* only select those frames which are in bounds */
|
||
|
for (gpf= gpl->frames.first; gpf; gpf= gpf->next) {
|
||
|
if (IN_RANGE(gpf->framenum, min, max))
|
||
|
gpframe_select(gpf, select_mode);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
/* De-selects or inverts the selection of Layers for a grease-pencil block
|
||
|
* mode: 0 = default behaviour (select all), 1 = test if (de)select all, 2 = invert all
|
||
|
*/
|
||
|
void deselect_gpencil_layers (void *data, short mode)
|
||
|
{
|
||
|
ListBase act_data = {NULL, NULL};
|
||
|
bActListElem *ale;
|
||
|
int filter, sel=1;
|
||
|
|
||
|
/* filter data */
|
||
|
filter= ACTFILTER_VISIBLE;
|
||
|
actdata_filter(&act_data, filter, data, ACTCONT_GPENCIL);
|
||
|
|
||
|
/* See if we should be selecting or deselecting */
|
||
|
if (mode == 1) {
|
||
|
for (ale= act_data.first; ale; ale= ale->next) {
|
||
|
if (sel == 0)
|
||
|
break;
|
||
|
|
||
|
if (ale->flag & GP_LAYER_SELECT)
|
||
|
sel= 0;
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
sel= 0;
|
||
|
|
||
|
/* Now set the flags */
|
||
|
for (ale= act_data.first; ale; ale= ale->next) {
|
||
|
bGPDlayer *gpl= (bGPDlayer *)ale->data;
|
||
|
|
||
|
if (mode == 2)
|
||
|
gpl->flag ^= GP_LAYER_SELECT;
|
||
|
else if (sel)
|
||
|
gpl->flag |= GP_LAYER_SELECT;
|
||
|
else
|
||
|
gpl->flag &= ~GP_LAYER_SELECT;
|
||
|
|
||
|
gpl->flag &= ~GP_LAYER_ACTIVE;
|
||
|
}
|
||
|
|
||
|
/* Cleanup */
|
||
|
BLI_freelistN(&act_data);
|
||
|
}
|
||
|
|
||
|
/* ***************************************** */
|
||
|
/* Frame Editing Tools */
|
||
|
|
||
|
/* Delete selected grease-pencil layers */
|
||
|
void delete_gpencil_layers (void)
|
||
|
{
|
||
|
ListBase act_data = {NULL, NULL};
|
||
|
bActListElem *ale, *next;
|
||
|
void *data;
|
||
|
short datatype;
|
||
|
int filter;
|
||
|
|
||
|
/* determine what type of data we are operating on */
|
||
|
data = get_action_context(&datatype);
|
||
|
if (data == NULL) return;
|
||
|
if (datatype != ACTCONT_GPENCIL) return;
|
||
|
|
||
|
/* filter data */
|
||
|
filter= (ACTFILTER_VISIBLE | ACTFILTER_FOREDIT | ACTFILTER_CHANNELS | ACTFILTER_SEL);
|
||
|
actdata_filter(&act_data, filter, data, datatype);
|
||
|
|
||
|
/* clean up grease-pencil layers */
|
||
|
for (ale= act_data.first; ale; ale= next) {
|
||
|
bGPdata *gpd= (bGPdata *)ale->owner;
|
||
|
bGPDlayer *gpl= (bGPDlayer *)ale->data;
|
||
|
next= ale->next;
|
||
|
|
||
|
/* free layer and its data */
|
||
|
if (SEL_GPL(gpl)) {
|
||
|
free_gpencil_frames(gpl);
|
||
|
BLI_freelinkN(&gpd->layers, gpl);
|
||
|
}
|
||
|
|
||
|
/* free temp memory */
|
||
|
BLI_freelinkN(&act_data, ale);
|
||
|
}
|
||
|
|
||
|
BIF_undo_push("Delete GPencil Layers");
|
||
|
}
|
||
|
|
||
|
/* Delete selected frames */
|
||
|
void delete_gplayer_frames (bGPDlayer *gpl)
|
||
|
{
|
||
|
bGPDframe *gpf, *gpfn;
|
||
|
|
||
|
/* error checking */
|
||
|
if (gpl == NULL)
|
||
|
return;
|
||
|
|
||
|
/* check for frames to delete */
|
||
|
for (gpf= gpl->frames.first; gpf; gpf= gpfn) {
|
||
|
gpfn= gpf->next;
|
||
|
|
||
|
if (gpf->flag & GP_FRAME_SELECT)
|
||
|
gpencil_layer_delframe(gpl, gpf);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* Duplicate selected frames from given gp-layer */
|
||
|
void duplicate_gplayer_frames (bGPDlayer *gpl)
|
||
|
{
|
||
|
bGPDframe *gpf, *gpfn;
|
||
|
|
||
|
/* error checking */
|
||
|
if (gpl == NULL)
|
||
|
return;
|
||
|
|
||
|
/* duplicate selected frames */
|
||
|
for (gpf= gpl->frames.first; gpf; gpf= gpfn) {
|
||
|
gpfn= gpf->next;
|
||
|
|
||
|
/* duplicate this frame */
|
||
|
if (gpf->flag & GP_FRAME_SELECT) {
|
||
|
bGPDframe *gpfd;
|
||
|
|
||
|
/* duplicate frame, and deselect self */
|
||
|
gpfd= gpencil_frame_duplicate(gpf);
|
||
|
gpf->flag &= ~GP_FRAME_SELECT;
|
||
|
|
||
|
BLI_insertlinkafter(&gpl->frames, gpf, gpfd);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* -------------------------------------- */
|
||
|
/* Copy and Paste Tools */
|
||
|
/* - The copy/paste buffer currently stores a set of GP_Layers, with temporary
|
||
|
* GP_Frames with the necessary strokes
|
||
|
* - 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 gpcopybuf = {NULL, NULL};
|
||
|
static int gpcopy_firstframe= 999999999;
|
||
|
|
||
|
/* This function frees any MEM_calloc'ed copy/paste buffer data */
|
||
|
void free_gpcopybuf ()
|
||
|
{
|
||
|
free_gpencil_layers(&gpcopybuf);
|
||
|
|
||
|
gpcopybuf.first= gpcopybuf.last= NULL;
|
||
|
gpcopy_firstframe= 999999999;
|
||
|
}
|
||
|
|
||
|
/* This function adds data to the copy/paste buffer, freeing existing data first
|
||
|
* Only the selected GP-layers get their selected keyframes copied.
|
||
|
*/
|
||
|
void copy_gpdata ()
|
||
|
{
|
||
|
ListBase act_data = {NULL, NULL};
|
||
|
bActListElem *ale;
|
||
|
int filter;
|
||
|
void *data;
|
||
|
short datatype;
|
||
|
|
||
|
/* clear buffer first */
|
||
|
free_gpcopybuf();
|
||
|
|
||
|
/* get data */
|
||
|
data= get_action_context(&datatype);
|
||
|
if (data == NULL) return;
|
||
|
if (datatype != ACTCONT_GPENCIL) return;
|
||
|
|
||
|
/* filter data */
|
||
|
filter= (ACTFILTER_VISIBLE | ACTFILTER_SEL);
|
||
|
actdata_filter(&act_data, filter, data, datatype);
|
||
|
|
||
|
/* assume that each of these is an ipo-block */
|
||
|
for (ale= act_data.first; ale; ale= ale->next) {
|
||
|
bGPDlayer *gpls, *gpln;
|
||
|
bGPDframe *gpf, *gpfn;
|
||
|
|
||
|
/* get new layer to put into buffer */
|
||
|
gpls= (bGPDlayer *)ale->data;
|
||
|
gpln= MEM_callocN(sizeof(bGPDlayer), "GPCopyPasteLayer");
|
||
|
|
||
|
gpln->frames.first= gpln->frames.last= NULL;
|
||
|
strcpy(gpln->info, gpls->info);
|
||
|
|
||
|
BLI_addtail(&gpcopybuf, gpln);
|
||
|
|
||
|
/* loop over frames, and copy only selected frames */
|
||
|
for (gpf= gpls->frames.first; gpf; gpf= gpf->next) {
|
||
|
/* if frame is selected, make duplicate it and its strokes */
|
||
|
if (gpf->flag & GP_FRAME_SELECT) {
|
||
|
/* add frame to buffer */
|
||
|
gpfn= gpencil_frame_duplicate(gpf);
|
||
|
BLI_addtail(&gpln->frames, gpfn);
|
||
|
|
||
|
/* check if this is the earliest frame encountered so far */
|
||
|
if (gpf->framenum < gpcopy_firstframe)
|
||
|
gpcopy_firstframe= gpf->framenum;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* check if anything ended up in the buffer */
|
||
|
if (ELEM(NULL, gpcopybuf.first, gpcopybuf.last))
|
||
|
error("Nothing copied to buffer");
|
||
|
|
||
|
/* free temp memory */
|
||
|
BLI_freelistN(&act_data);
|
||
|
}
|
||
|
|
||
|
void paste_gpdata (Scene *scene)
|
||
|
{
|
||
|
ListBase act_data = {NULL, NULL};
|
||
|
bActListElem *ale;
|
||
|
int filter;
|
||
|
void *data;
|
||
|
short datatype;
|
||
|
|
||
|
const int offset = (CFRA - gpcopy_firstframe);
|
||
|
short no_name= 0;
|
||
|
|
||
|
/* check if buffer is empty */
|
||
|
if (ELEM(NULL, gpcopybuf.first, gpcopybuf.last)) {
|
||
|
error("No data in buffer to paste");
|
||
|
return;
|
||
|
}
|
||
|
/* check if single channel in buffer (disregard names if so) */
|
||
|
if (gpcopybuf.first == gpcopybuf.last)
|
||
|
no_name= 1;
|
||
|
|
||
|
/* get data */
|
||
|
data= get_action_context(&datatype);
|
||
|
if (data == NULL) return;
|
||
|
if (datatype != ACTCONT_GPENCIL) return;
|
||
|
|
||
|
/* filter data */
|
||
|
filter= (ACTFILTER_VISIBLE | ACTFILTER_SEL | ACTFILTER_FOREDIT);
|
||
|
actdata_filter(&act_data, filter, data, datatype);
|
||
|
|
||
|
/* from selected channels */
|
||
|
for (ale= act_data.first; ale; ale= ale->next) {
|
||
|
bGPDlayer *gpld= (bGPDlayer *)ale->data;
|
||
|
bGPDlayer *gpls= NULL;
|
||
|
bGPDframe *gpfs, *gpf;
|
||
|
|
||
|
/* find suitable layer from buffer to use to paste from */
|
||
|
for (gpls= gpcopybuf.first; gpls; gpls= gpls->next) {
|
||
|
/* check if layer name matches */
|
||
|
if ((no_name) || (strcmp(gpls->info, gpld->info)==0))
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
/* this situation might occur! */
|
||
|
if (gpls == NULL)
|
||
|
continue;
|
||
|
|
||
|
/* add frames from buffer */
|
||
|
for (gpfs= gpls->frames.first; gpfs; gpfs= gpfs->next) {
|
||
|
/* temporarily apply offset to buffer-frame while copying */
|
||
|
gpfs->framenum += offset;
|
||
|
|
||
|
/* get frame to copy data into (if no frame returned, then just ignore) */
|
||
|
gpf= gpencil_layer_getframe(gpld, gpfs->framenum, 1);
|
||
|
if (gpf) {
|
||
|
bGPDstroke *gps, *gpsn;
|
||
|
ScrArea *sa;
|
||
|
|
||
|
/* get area that gp-data comes from */
|
||
|
sa= gpencil_data_findowner((bGPdata *)ale->owner);
|
||
|
|
||
|
/* this should be the right frame... as it may be a pre-existing frame,
|
||
|
* must make sure that only compatible stroke types get copied over
|
||
|
* - we cannot just add a duplicate frame, as that would cause errors
|
||
|
* - need to check for compatible types to minimise memory usage (copying 'junk' over)
|
||
|
*/
|
||
|
for (gps= gpfs->strokes.first; gps; gps= gps->next) {
|
||
|
short stroke_ok;
|
||
|
|
||
|
/* if there's an area, check that it supports this type of stroke */
|
||
|
if (sa) {
|
||
|
stroke_ok= 0;
|
||
|
|
||
|
/* check if spacetype supports this type of stroke
|
||
|
* - NOTE: must sync this with gp_paint_initstroke() in gpencil.c
|
||
|
*/
|
||
|
switch (sa->spacetype) {
|
||
|
case SPACE_VIEW3D: /* 3D-View: either screen-aligned or 3d-space */
|
||
|
if ((gps->flag == 0) || (gps->flag & GP_STROKE_3DSPACE))
|
||
|
stroke_ok= 1;
|
||
|
break;
|
||
|
|
||
|
case SPACE_NODE: /* Nodes Editor: either screen-aligned or view-aligned */
|
||
|
case SPACE_IMAGE: /* Image Editor: either screen-aligned or view\image-aligned */
|
||
|
if ((gps->flag == 0) || (gps->flag & GP_STROKE_2DSPACE))
|
||
|
stroke_ok= 1;
|
||
|
break;
|
||
|
|
||
|
case SPACE_SEQ: /* Sequence Editor: either screen-aligned or view-aligned */
|
||
|
if ((gps->flag == 0) || (gps->flag & GP_STROKE_2DIMAGE))
|
||
|
stroke_ok= 1;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
stroke_ok= 1;
|
||
|
|
||
|
/* if stroke is ok, we make a copy of this stroke and add to frame */
|
||
|
if (stroke_ok) {
|
||
|
/* make a copy of stroke, then of its points array */
|
||
|
gpsn= MEM_dupallocN(gps);
|
||
|
gpsn->points= MEM_dupallocN(gps->points);
|
||
|
|
||
|
/* append stroke to frame */
|
||
|
BLI_addtail(&gpf->strokes, gpsn);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* if no strokes (i.e. new frame) added, free gpf */
|
||
|
if (gpf->strokes.first == NULL)
|
||
|
gpencil_layer_delframe(gpld, gpf);
|
||
|
}
|
||
|
|
||
|
/* unapply offset from buffer-frame */
|
||
|
gpfs->framenum -= offset;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* free temp memory */
|
||
|
BLI_freelistN(&act_data);
|
||
|
|
||
|
/* undo and redraw stuff */
|
||
|
BIF_undo_push("Paste Grease Pencil Frames");
|
||
|
}
|
||
|
|
||
|
/* -------------------------------------- */
|
||
|
/* Snap Tools */
|
||
|
|
||
|
static short snap_gpf_nearest (bGPDframe *gpf, Scene *scene)
|
||
|
{
|
||
|
if (gpf->flag & GP_FRAME_SELECT)
|
||
|
gpf->framenum= (int)(floor(gpf->framenum+0.5));
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static short snap_gpf_nearestsec (bGPDframe *gpf, Scene *scene)
|
||
|
{
|
||
|
float secf = (float)FPS;
|
||
|
if (gpf->flag & GP_FRAME_SELECT)
|
||
|
gpf->framenum= (int)(floor(gpf->framenum/secf + 0.5f) * secf);
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static short snap_gpf_cframe (bGPDframe *gpf, Scene *scene)
|
||
|
{
|
||
|
if (gpf->flag & GP_FRAME_SELECT)
|
||
|
gpf->framenum= (int)CFRA;
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static short snap_gpf_nearmarker (bGPDframe *gpf, Scene *scene)
|
||
|
{
|
||
|
if (gpf->flag & GP_FRAME_SELECT)
|
||
|
gpf->framenum= (int)find_nearest_marker_time((float)gpf->framenum);
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
|
||
|
/* snap selected frames to ... */
|
||
|
void snap_gplayer_frames (bGPDlayer *gpl, Scene *scene, short mode)
|
||
|
{
|
||
|
switch (mode) {
|
||
|
case 1: /* snap to nearest frame */
|
||
|
gplayer_frames_looper(gpl, scene, snap_gpf_nearest);
|
||
|
break;
|
||
|
case 2: /* snap to current frame */
|
||
|
gplayer_frames_looper(gpl, scene, snap_gpf_cframe);
|
||
|
break;
|
||
|
case 3: /* snap to nearest marker */
|
||
|
gplayer_frames_looper(gpl, scene, snap_gpf_nearmarker);
|
||
|
break;
|
||
|
case 4: /* snap to nearest second */
|
||
|
gplayer_frames_looper(gpl, scene, snap_gpf_nearestsec);
|
||
|
break;
|
||
|
default: /* just in case */
|
||
|
gplayer_frames_looper(gpl, scene, snap_gpf_nearest);
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* -------------------------------------- */
|
||
|
/* Mirror Tools */
|
||
|
|
||
|
static short mirror_gpf_cframe (bGPDframe *gpf, Scene *scene)
|
||
|
{
|
||
|
int diff;
|
||
|
|
||
|
if (gpf->flag & GP_FRAME_SELECT) {
|
||
|
diff= CFRA - gpf->framenum;
|
||
|
gpf->framenum= CFRA;
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static short mirror_gpf_yaxis (bGPDframe *gpf, Scene *scene)
|
||
|
{
|
||
|
int diff;
|
||
|
|
||
|
if (gpf->flag & GP_FRAME_SELECT) {
|
||
|
diff= -gpf->framenum;
|
||
|
gpf->framenum= diff;
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static short mirror_gpf_xaxis (bGPDframe *gpf, Scene *scene)
|
||
|
{
|
||
|
int diff;
|
||
|
|
||
|
if (gpf->flag & GP_FRAME_SELECT) {
|
||
|
diff= -gpf->framenum;
|
||
|
gpf->framenum= diff;
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static short mirror_gpf_marker (bGPDframe *gpf, Scene *scene)
|
||
|
{
|
||
|
static TimeMarker *marker;
|
||
|
static short initialised = 0;
|
||
|
int diff;
|
||
|
|
||
|
/* In order for this mirror function to work without
|
||
|
* any extra arguments being added, we use the case
|
||
|
* of bezt==NULL to denote that we should find the
|
||
|
* marker to mirror over. The static pointer is safe
|
||
|
* to use this way, as it will be set to null after
|
||
|
* each cycle in which this is called.
|
||
|
*/
|
||
|
|
||
|
if (gpf) {
|
||
|
/* mirroring time */
|
||
|
if ((gpf->flag & GP_FRAME_SELECT) && (marker)) {
|
||
|
diff= (marker->frame - gpf->framenum);
|
||
|
gpf->framenum= (marker->frame + diff);
|
||
|
}
|
||
|
}
|
||
|
else {
|
||
|
/* initialisation time */
|
||
|
if (initialised) {
|
||
|
/* reset everything for safety */
|
||
|
marker = NULL;
|
||
|
initialised = 0;
|
||
|
}
|
||
|
else {
|
||
|
/* try to find a marker */
|
||
|
for (marker= scene->markers.first; marker; marker=marker->next) {
|
||
|
if (marker->flag & SELECT) {
|
||
|
initialised = 1;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (initialised == 0)
|
||
|
marker = NULL;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
|
||
|
/* mirror selected gp-frames on... */
|
||
|
void mirror_gplayer_frames (bGPDlayer *gpl, Scene *scene, short mode)
|
||
|
{
|
||
|
switch (mode) {
|
||
|
case 1: /* mirror over current frame */
|
||
|
gplayer_frames_looper(gpl, scene, mirror_gpf_cframe);
|
||
|
break;
|
||
|
case 2: /* mirror over frame 0 */
|
||
|
gplayer_frames_looper(gpl, scene, mirror_gpf_yaxis);
|
||
|
break;
|
||
|
case 3: /* mirror over value 0 */
|
||
|
gplayer_frames_looper(gpl, scene, mirror_gpf_xaxis);
|
||
|
break;
|
||
|
case 4: /* mirror over marker */
|
||
|
mirror_gpf_marker(NULL, NULL);
|
||
|
gplayer_frames_looper(gpl, scene, mirror_gpf_marker);
|
||
|
mirror_gpf_marker(NULL, NULL);
|
||
|
break;
|
||
|
default: /* just in case */
|
||
|
gplayer_frames_looper(gpl, scene, mirror_gpf_yaxis);
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* ***************************************** */
|