/** * $Id: editaction_gpencil.c 14881 2008-05-18 10:41:42Z aligorith $ * * ***** 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 #include #include #include #include #ifdef HAVE_CONFIG_H #include #endif #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 "DNA_view2d_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 "BIF_butspace.h" #include "BIF_graphics.h" #include "BIF_interface.h" #include "BIF_mywindow.h" #include "BIF_resources.h" #include "BIF_space.h" #include "BIF_screen.h" #include "BIF_toolbox.h" #include "BIF_toets.h" #include "BIF_editaction.h" #include "BSE_editaction_types.h" #include "BDR_gpencil.h" #include "BIF_drawgpencil.h" #include "BSE_drawipo.h" #include "BSE_headerbuttons.h" #include "BSE_time.h" #include "BSE_view.h" #include "blendef.h" #include "butspace.h" #include "PIL_time.h" /* sleep */ #include "mydevice.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 */ /* Loops over the gp-frames for a gp-layer, and applies the given callback */ short gplayer_frames_looper (bGPDlayer *gpl, short (*gpf_cb)(bGPDframe *)) { 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)) 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= 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); } 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) { if (gpf->framenum == selx) gpframe_select(gpf, select_mode); } } 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 (bGPdata *gpd, short mode) { ListBase act_data = {NULL, NULL}; bActListElem *ale; int filter, sel=1; /* filter data */ filter= ACTFILTER_VISIBLE; actdata_filter(&act_data, filter, gpd, 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 */ void delete_gpencil_layers (void) { ListBase act_data = {NULL, NULL}; bActListElem *ale, *next; bGPdata *gpd; 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; gpd= (bGPdata *)data; /* 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) { 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"); allspace(REDRAWVIEW3D, 0); allqueue(REDRAWACTION, 0); } /* 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; bGPDstroke *gps; /* duplicate frame, and deselect self */ gpfd= MEM_dupallocN(gpf); gpf->flag &= ~GP_FRAME_SELECT; /* duplicate list of strokes too */ duplicatelist(&gpfd->strokes, &gpf->strokes); /* dupalloc only makes another copy of mem, but doesn't adjust pointers */ for (gps= gpfd->strokes.first; gps; gps= gps->next) { gps->points= MEM_dupallocN(gps->points); } BLI_insertlinkafter(&gpl->frames, gpf, gpfd); } } } /* -------------------------------------- */ /* Snap Tools */ static short snap_gpf_nearest (bGPDframe *gpf) { if (gpf->flag & GP_FRAME_SELECT) gpf->framenum= (int)(floor(gpf->framenum+0.5)); return 0; } static short snap_gpf_nearestsec (bGPDframe *gpf) { float secf = 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) { if (gpf->flag & GP_FRAME_SELECT) gpf->framenum= (int)CFRA; return 0; } static short snap_gpf_nearmarker (bGPDframe *gpf) { if (gpf->flag & GP_FRAME_SELECT) gpf->framenum= (int)find_nearest_marker_time(gpf->framenum); return 0; } /* snap selected frames to ... */ void snap_gplayer_frames (bGPDlayer *gpl, short mode) { switch (mode) { case 1: /* snap to nearest frame */ gplayer_frames_looper(gpl, snap_gpf_nearest); break; case 2: /* snap to current frame */ gplayer_frames_looper(gpl, snap_gpf_cframe); break; case 3: /* snap to nearest marker */ gplayer_frames_looper(gpl, snap_gpf_nearmarker); break; case 4: /* snap to nearest second */ gplayer_frames_looper(gpl, snap_gpf_nearestsec); break; default: /* just in case */ gplayer_frames_looper(gpl, snap_gpf_nearest); break; } } /* -------------------------------------- */ /* Mirror Tools */ static short mirror_gpf_cframe (bGPDframe *gpf) { float diff; if (gpf->flag & GP_FRAME_SELECT) { diff= ((float)CFRA - gpf->framenum); gpf->framenum= ((float)CFRA + diff); } return 0; } static short mirror_gpf_yaxis (bGPDframe *gpf) { float diff; if (gpf->flag & GP_FRAME_SELECT) { diff= (0.0f - gpf->framenum); gpf->framenum= (0.0f + diff); } return 0; } static short mirror_gpf_xaxis (bGPDframe *gpf) { float diff; if (gpf->flag & GP_FRAME_SELECT) { diff= (0.0f - gpf->framenum); gpf->framenum= (0.0f + diff); } return 0; } static short mirror_gpf_marker (bGPDframe *gpf) { static TimeMarker *marker; static short initialised = 0; float 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= G.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, short mode) { switch (mode) { case 1: /* mirror over current frame */ gplayer_frames_looper(gpl, mirror_gpf_cframe); break; case 2: /* mirror over frame 0 */ gplayer_frames_looper(gpl, mirror_gpf_yaxis); break; case 3: /* mirror over value 0 */ gplayer_frames_looper(gpl, mirror_gpf_xaxis); break; case 4: /* mirror over marker */ mirror_gpf_marker(NULL); gplayer_frames_looper(gpl, mirror_gpf_marker); mirror_gpf_marker(NULL); break; default: /* just in case */ gplayer_frames_looper(gpl, mirror_gpf_yaxis); break; } } /* ***************************************** */