/* SPDX-License-Identifier: GPL-2.0-or-later * Copyright 2008 Blender Foundation. */ /** \file * \ingroup edgpencil */ #include #include #include #include #include #include "MEM_guardedalloc.h" #include "BLI_blenlib.h" #include "BLI_utildefines.h" #include "DNA_gpencil_types.h" #include "DNA_scene_types.h" #include "BKE_fcurve.h" #include "BKE_gpencil.h" #include "BKE_report.h" #include "ED_anim_api.h" #include "ED_gpencil.h" #include "ED_keyframes_edit.h" #include "ED_markers.h" #include "WM_api.h" #include "DEG_depsgraph.h" /* ***************************************** */ /* 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 */ bool ED_gpencil_layer_frames_looper(bGPDlayer *gpl, Scene *scene, bool (*gpf_cb)(bGPDframe *, Scene *)) { /* error checker */ if (gpl == NULL) { return false; } /* do loop */ LISTBASE_FOREACH (bGPDframe *, gpf, &gpl->frames) { /* execute callback */ if (gpf_cb(gpf, scene)) { return true; } } /* nothing to return */ return false; } /* ****************************************** */ /* Data Conversion Tools */ void ED_gpencil_layer_make_cfra_list(bGPDlayer *gpl, ListBase *elems, bool onlysel) { CfraElem *ce; /* error checking */ if (ELEM(NULL, gpl, elems)) { return; } /* loop through gp-frames, adding */ LISTBASE_FOREACH (bGPDframe *, gpf, &gpl->frames) { 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 */ bool ED_gpencil_layer_frame_select_check(const bGPDlayer *gpl) { /* error checking */ if (gpl == NULL) { return false; } /* stop at the first one found */ LISTBASE_FOREACH (const bGPDframe *, gpf, &gpl->frames) { if (gpf->flag & GP_FRAME_SELECT) { return true; } } /* not found */ return false; } /* helper function - select gp-frame based on SELECT_* mode */ static void gpencil_frame_select(bGPDframe *gpf, short select_mode) { if (gpf == NULL) { return; } 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; } } void ED_gpencil_select_frames(bGPDlayer *gpl, short select_mode) { /* error checking */ if (gpl == NULL) { return; } /* handle according to mode */ LISTBASE_FOREACH (bGPDframe *, gpf, &gpl->frames) { gpencil_frame_select(gpf, select_mode); } } void ED_gpencil_layer_frame_select_set(bGPDlayer *gpl, short mode) { /* error checking */ if (gpl == NULL) { return; } /* now call the standard function */ ED_gpencil_select_frames(gpl, mode); } void ED_gpencil_select_frame(bGPDlayer *gpl, int selx, short select_mode) { bGPDframe *gpf; if (gpl == NULL) { return; } gpf = BKE_gpencil_layer_frame_find(gpl, selx); if (gpf) { gpencil_frame_select(gpf, select_mode); } } void ED_gpencil_layer_frames_select_box(bGPDlayer *gpl, float min, float max, short select_mode) { if (gpl == NULL) { return; } /* only select those frames which are in bounds */ LISTBASE_FOREACH (bGPDframe *, gpf, &gpl->frames) { if (IN_RANGE(gpf->framenum, min, max)) { gpencil_frame_select(gpf, select_mode); } } } void ED_gpencil_layer_frames_select_region(KeyframeEditData *ked, bGPDlayer *gpl, short tool, short select_mode) { if (gpl == NULL) { return; } /* only select frames which are within the region */ LISTBASE_FOREACH (bGPDframe *, gpf, &gpl->frames) { /* construct a dummy point coordinate to do this testing with */ float pt[2] = {0}; pt[0] = gpf->framenum; pt[1] = ked->channel_y; /* check the necessary regions */ if (tool == BEZT_OK_CHANNEL_LASSO) { /* Lasso */ if (keyframe_region_lasso_test(ked->data, pt)) { gpencil_frame_select(gpf, select_mode); } } else if (tool == BEZT_OK_CHANNEL_CIRCLE) { /* Circle */ if (keyframe_region_circle_test(ked->data, pt)) { gpencil_frame_select(gpf, select_mode); } } } } void ED_gpencil_set_active_channel(bGPdata *gpd, bGPDlayer *gpl) { gpl->flag |= GP_LAYER_SELECT; /* Update other layer status. */ if (BKE_gpencil_layer_active_get(gpd) != gpl) { BKE_gpencil_layer_active_set(gpd, gpl); BKE_gpencil_layer_autolock_set(gpd, false); WM_main_add_notifier(NC_GPENCIL | ND_DATA | NA_EDITED, NULL); } } /* ***************************************** */ /* Frame Editing Tools */ bool ED_gpencil_layer_frames_delete(bGPDlayer *gpl) { bool changed = false; /* error checking */ if (gpl == NULL) { return false; } /* check for frames to delete */ LISTBASE_FOREACH_MUTABLE (bGPDframe *, gpf, &gpl->frames) { if (gpf->flag & GP_FRAME_SELECT) { BKE_gpencil_layer_frame_delete(gpl, gpf); changed = true; } } return changed; } void ED_gpencil_layer_frames_duplicate(bGPDlayer *gpl) { /* error checking */ if (gpl == NULL) { return; } /* Duplicate selected frames. */ LISTBASE_FOREACH_MUTABLE (bGPDframe *, gpf, &gpl->frames) { /* duplicate this frame */ if (gpf->flag & GP_FRAME_SELECT) { bGPDframe *gpfd; /* duplicate frame, and deselect self */ gpfd = BKE_gpencil_frame_duplicate(gpf, true); gpf->flag &= ~GP_FRAME_SELECT; BLI_insertlinkafter(&gpl->frames, gpf, gpfd); } } } void ED_gpencil_layer_frames_keytype_set(bGPDlayer *gpl, short type) { if (gpl == NULL) { return; } LISTBASE_FOREACH (bGPDframe *, gpf, &gpl->frames) { if (gpf->flag & GP_FRAME_SELECT) { gpf->key_type = type; } } } /* -------------------------------------- */ /* 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 compatibility. * - 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) */ static ListBase gpencil_anim_copybuf = {NULL, NULL}; static int gpencil_anim_copy_firstframe = 999999999; static int gpencil_anim_copy_lastframe = -999999999; static int gpencil_anim_copy_cfra = 0; void ED_gpencil_anim_copybuf_free(void) { BKE_gpencil_free_layers(&gpencil_anim_copybuf); BLI_listbase_clear(&gpencil_anim_copybuf); gpencil_anim_copy_firstframe = 999999999; gpencil_anim_copy_lastframe = -999999999; gpencil_anim_copy_cfra = 0; } bool ED_gpencil_anim_copybuf_copy(bAnimContext *ac) { ListBase anim_data = {NULL, NULL}; bAnimListElem *ale; int filter; Scene *scene = ac->scene; /* clear buffer first */ ED_gpencil_anim_copybuf_free(); /* filter data */ filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE | ANIMFILTER_NODUPLIS); ANIM_animdata_filter(ac, &anim_data, filter, ac->data, ac->datatype); for (ale = anim_data.first; ale; ale = ale->next) { /* This function only deals with grease pencil layer frames. * This check is needed in the case of a call from the main dopesheet. */ if (ale->type != ANIMTYPE_GPLAYER) { continue; } ListBase copied_frames = {NULL, NULL}; bGPDlayer *gpl = (bGPDlayer *)ale->data; /* loop over frames, and copy only selected frames */ LISTBASE_FOREACH (bGPDframe *, gpf, &gpl->frames) { /* if frame is selected, make duplicate it and its strokes */ if (gpf->flag & GP_FRAME_SELECT) { /* make a copy of this frame */ bGPDframe *new_frame = BKE_gpencil_frame_duplicate(gpf, true); BLI_addtail(&copied_frames, new_frame); /* extend extents for keyframes encountered */ if (gpf->framenum < gpencil_anim_copy_firstframe) { gpencil_anim_copy_firstframe = gpf->framenum; } if (gpf->framenum > gpencil_anim_copy_lastframe) { gpencil_anim_copy_lastframe = gpf->framenum; } } } /* create a new layer in buffer if there were keyframes here */ if (BLI_listbase_is_empty(&copied_frames) == false) { bGPDlayer *new_layer = MEM_callocN(sizeof(bGPDlayer), "GPCopyPasteLayer"); BLI_addtail(&gpencil_anim_copybuf, new_layer); /* move over copied frames */ BLI_movelisttolist(&new_layer->frames, &copied_frames); BLI_assert(copied_frames.first == NULL); /* make a copy of the layer's name - for name-based matching later... */ BLI_strncpy(new_layer->info, gpl->info, sizeof(new_layer->info)); } } /* in case 'relative' paste method is used */ gpencil_anim_copy_cfra = scene->r.cfra; /* clean up */ ANIM_animdata_freelist(&anim_data); /* report success */ return !BLI_listbase_is_empty(&gpencil_anim_copybuf); } bool ED_gpencil_anim_copybuf_paste(bAnimContext *ac, const short offset_mode) { ListBase anim_data = {NULL, NULL}; bAnimListElem *ale; int filter; Scene *scene = ac->scene; bool no_name = false; int offset = 0; /* check if buffer is empty */ if (BLI_listbase_is_empty(&gpencil_anim_copybuf)) { return false; } /* Check if single channel in buffer (disregard names if so). */ if (gpencil_anim_copybuf.first == gpencil_anim_copybuf.last) { no_name = true; } /* methods of offset (eKeyPasteOffset) */ switch (offset_mode) { case KEYFRAME_PASTE_OFFSET_CFRA_START: offset = (scene->r.cfra - gpencil_anim_copy_firstframe); break; case KEYFRAME_PASTE_OFFSET_CFRA_END: offset = (scene->r.cfra - gpencil_anim_copy_lastframe); break; case KEYFRAME_PASTE_OFFSET_CFRA_RELATIVE: offset = (scene->r.cfra - gpencil_anim_copy_cfra); break; case KEYFRAME_PASTE_OFFSET_NONE: offset = 0; break; } /* filter data */ /* TODO: try doing it with selection, then without selection limits. */ filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE | ANIMFILTER_SEL | ANIMFILTER_FOREDIT | ANIMFILTER_NODUPLIS); ANIM_animdata_filter(ac, &anim_data, filter, ac->data, ac->datatype); /* from selected channels */ for (ale = anim_data.first; ale; ale = ale->next) { /* only deal with GPlayers (case of calls from general dopesheet) */ if (ale->type != ANIMTYPE_GPLAYER) { continue; } bGPDlayer *gpld = (bGPDlayer *)ale->data; bGPDlayer *gpls = NULL; bGPDframe *gpfs, *gpf; /* find suitable layer from buffer to use to paste from */ for (gpls = gpencil_anim_copybuf.first; gpls; gpls = gpls->next) { /* check if layer name matches */ if ((no_name) || STREQ(gpls->info, gpld->info)) { 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 = BKE_gpencil_layer_frame_get(gpld, gpfs->framenum, GP_GETFRAME_ADD_NEW); if (gpf) { /* Ensure to use same keyframe type. */ gpf->key_type = gpfs->key_type; bGPDstroke *gps, *gpsn; /* 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 * - For now, we don't check if the types will be compatible since we * don't have enough info to do so. Instead, we simply just paste, * if it works, it will show up. */ for (gps = gpfs->strokes.first; gps; gps = gps->next) { /* make a copy of stroke, then of its points array */ gpsn = BKE_gpencil_stroke_duplicate(gps, true, true); /* append stroke to frame */ BLI_addtail(&gpf->strokes, gpsn); } /* if no strokes (i.e. new frame) added, free gpf */ if (BLI_listbase_is_empty(&gpf->strokes)) { BKE_gpencil_layer_frame_delete(gpld, gpf); } } /* unapply offset from buffer-frame */ gpfs->framenum -= offset; } /* Tag destination datablock. */ DEG_id_tag_update(ale->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY); } /* clean up */ ANIM_animdata_freelist(&anim_data); return true; } /* -------------------------------------- */ /* Snap Tools */ static bool gpencil_frame_snap_nearest(bGPDframe *UNUSED(gpf), Scene *UNUSED(scene)) { #if 0 /* NOTE: gpf->framenum is already an int! */ if (gpf->flag & GP_FRAME_SELECT) { gpf->framenum = (int)(floor(gpf->framenum + 0.5)); } #endif return false; } static bool gpencil_frame_snap_nearestsec(bGPDframe *gpf, Scene *scene) { float secf = (float)FPS; if (gpf->flag & GP_FRAME_SELECT) { gpf->framenum = (int)(floorf(gpf->framenum / secf + 0.5f) * secf); } return false; } static bool gpencil_frame_snap_cframe(bGPDframe *gpf, Scene *scene) { if (gpf->flag & GP_FRAME_SELECT) { gpf->framenum = (int)scene->r.cfra; } return false; } static bool gpencil_frame_snap_nearmarker(bGPDframe *gpf, Scene *scene) { if (gpf->flag & GP_FRAME_SELECT) { gpf->framenum = (int)ED_markers_find_nearest_marker_time(&scene->markers, (float)gpf->framenum); } return false; } void ED_gpencil_layer_snap_frames(bGPDlayer *gpl, Scene *scene, short mode) { switch (mode) { case SNAP_KEYS_NEARFRAME: /* snap to nearest frame */ ED_gpencil_layer_frames_looper(gpl, scene, gpencil_frame_snap_nearest); break; case SNAP_KEYS_CURFRAME: /* snap to current frame */ ED_gpencil_layer_frames_looper(gpl, scene, gpencil_frame_snap_cframe); break; case SNAP_KEYS_NEARMARKER: /* snap to nearest marker */ ED_gpencil_layer_frames_looper(gpl, scene, gpencil_frame_snap_nearmarker); break; case SNAP_KEYS_NEARSEC: /* snap to nearest second */ ED_gpencil_layer_frames_looper(gpl, scene, gpencil_frame_snap_nearestsec); break; default: /* just in case */ break; } } /* -------------------------------------- */ /* Mirror Tools */ static bool gpencil_frame_mirror_cframe(bGPDframe *gpf, Scene *scene) { int diff; if (gpf->flag & GP_FRAME_SELECT) { diff = scene->r.cfra - gpf->framenum; gpf->framenum = scene->r.cfra + diff; } return false; } static bool gpencil_frame_mirror_yaxis(bGPDframe *gpf, Scene *UNUSED(scene)) { int diff; if (gpf->flag & GP_FRAME_SELECT) { diff = -gpf->framenum; gpf->framenum = diff; } return false; } static bool gpencil_frame_mirror_xaxis(bGPDframe *gpf, Scene *UNUSED(scene)) { int diff; /* NOTE: since we can't really do this, we just do the same as for yaxis... */ if (gpf->flag & GP_FRAME_SELECT) { diff = -gpf->framenum; gpf->framenum = diff; } return false; } static bool gpencil_frame_mirror_marker(bGPDframe *gpf, Scene *scene) { static TimeMarker *marker; static short initialized = 0; int diff; /* In order for this mirror function to work without * any extra arguments being added, we use the case * of gpf==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 != NULL) { /* mirroring time */ if ((gpf->flag & GP_FRAME_SELECT) && (marker)) { diff = (marker->frame - gpf->framenum); gpf->framenum = (marker->frame + diff); } } else { /* initialization time */ if (initialized) { /* reset everything for safety */ marker = NULL; initialized = 0; } else { /* try to find a marker */ marker = ED_markers_get_first_selected(&scene->markers); if (marker) { initialized = 1; } } } return false; } void ED_gpencil_layer_mirror_frames(bGPDlayer *gpl, Scene *scene, short mode) { switch (mode) { case MIRROR_KEYS_CURFRAME: /* mirror over current frame */ ED_gpencil_layer_frames_looper(gpl, scene, gpencil_frame_mirror_cframe); break; case MIRROR_KEYS_YAXIS: /* mirror over frame 0 */ ED_gpencil_layer_frames_looper(gpl, scene, gpencil_frame_mirror_yaxis); break; case MIRROR_KEYS_XAXIS: /* mirror over value 0 */ ED_gpencil_layer_frames_looper(gpl, scene, gpencil_frame_mirror_xaxis); break; case MIRROR_KEYS_MARKER: /* mirror over marker */ gpencil_frame_mirror_marker(NULL, scene); ED_gpencil_layer_frames_looper(gpl, scene, gpencil_frame_mirror_marker); gpencil_frame_mirror_marker(NULL, scene); break; default: /* just in case */ ED_gpencil_layer_frames_looper(gpl, scene, gpencil_frame_mirror_yaxis); break; } } /* ***************************************** */