From a1f87064c4c0cd9e8eafc19c599c4ad4ccdba49f Mon Sep 17 00:00:00 2001 From: Joshua Leung Date: Sun, 13 Dec 2015 21:03:13 +1300 Subject: Grease Pencil: Merge GPencil_Editing_Stage3 branch into master This commit merges all the work done in the GPencil_Editing_Stage3 branch as of ef2aecf2db981b5344e0d14e7f074f1742b0b2f7 into master. For more details about the changes that this brings, see the WIP release notes: http://wiki.blender.org/index.php/Dev:Ref/Release_Notes/2.77/GPencil --- source/blender/blenkernel/BKE_gpencil.h | 18 +- source/blender/blenkernel/intern/gpencil.c | 70 +- source/blender/blenkernel/intern/scene.c | 47 + source/blender/blenloader/intern/readfile.c | 1 + source/blender/blenloader/intern/versioning_270.c | 79 +- .../blenloader/intern/versioning_defaults.c | 45 + source/blender/editors/animation/anim_draw.c | 8 +- source/blender/editors/animation/anim_filter.c | 146 +- .../blender/editors/animation/keyframes_general.c | 2 +- source/blender/editors/gpencil/CMakeLists.txt | 1 + source/blender/editors/gpencil/drawgpencil.c | 25 +- .../blender/editors/gpencil/editaction_gpencil.c | 239 +-- source/blender/editors/gpencil/gpencil_brush.c | 1691 ++++++++++++++++++++ source/blender/editors/gpencil/gpencil_convert.c | 7 +- source/blender/editors/gpencil/gpencil_data.c | 218 +++ source/blender/editors/gpencil/gpencil_edit.c | 546 ++++++- source/blender/editors/gpencil/gpencil_intern.h | 49 + source/blender/editors/gpencil/gpencil_ops.c | 93 +- source/blender/editors/gpencil/gpencil_paint.c | 423 ++--- source/blender/editors/gpencil/gpencil_select.c | 11 +- source/blender/editors/gpencil/gpencil_utils.c | 162 +- source/blender/editors/include/ED_gpencil.h | 14 +- source/blender/editors/object/object_edit.c | 50 +- source/blender/editors/screen/screen_ops.c | 8 +- source/blender/editors/space_action/action_edit.c | 15 +- source/blender/editors/space_time/space_time.c | 12 +- source/blender/editors/space_view3d/view3d_draw.c | 18 +- .../blender/editors/space_view3d/view3d_header.c | 7 +- .../editors/transform/transform_conversions.c | 43 +- .../blender/editors/transform/transform_generics.c | 7 + .../editors/transform/transform_manipulator.c | 41 +- source/blender/makesdna/DNA_action_types.h | 3 + source/blender/makesdna/DNA_gpencil_types.h | 12 +- source/blender/makesdna/DNA_object_types.h | 1 + source/blender/makesdna/DNA_scene_types.h | 96 +- source/blender/makesrna/RNA_access.h | 2 + source/blender/makesrna/RNA_enum_types.h | 2 + source/blender/makesrna/intern/rna_action.c | 8 + source/blender/makesrna/intern/rna_gpencil.c | 91 +- source/blender/makesrna/intern/rna_object.c | 1 + source/blender/makesrna/intern/rna_scene.c | 64 +- source/blender/makesrna/intern/rna_sculpt_paint.c | 105 ++ source/blender/makesrna/intern/rna_wm.c | 6 + .../blender/windowmanager/intern/wm_event_system.c | 20 +- source/blender/windowmanager/intern/wm_init_exit.c | 1 + source/blender/windowmanager/intern/wm_keymap.c | 3 + source/blender/windowmanager/wm_event_types.h | 4 + 47 files changed, 3950 insertions(+), 565 deletions(-) create mode 100644 source/blender/editors/gpencil/gpencil_brush.c (limited to 'source/blender') diff --git a/source/blender/blenkernel/BKE_gpencil.h b/source/blender/blenkernel/BKE_gpencil.h index 084c5527f21..99fc195ed57 100644 --- a/source/blender/blenkernel/BKE_gpencil.h +++ b/source/blender/blenkernel/BKE_gpencil.h @@ -47,6 +47,7 @@ void BKE_gpencil_free(struct bGPdata *gpd); void gpencil_stroke_sync_selection(struct bGPDstroke *gps); struct bGPDframe *gpencil_frame_addnew(struct bGPDlayer *gpl, int cframe); +struct bGPDframe *gpencil_frame_addcopy(struct bGPDlayer *gpl, int cframe); struct bGPDlayer *gpencil_layer_addnew(struct bGPdata *gpd, const char *name, int setactive); struct bGPdata *gpencil_data_addnew(const char name[]); @@ -56,9 +57,24 @@ struct bGPdata *gpencil_data_duplicate(struct bGPdata *gpd, bool internal_copy); void gpencil_frame_delete_laststroke(struct bGPDlayer *gpl, struct bGPDframe *gpf); + +/* How gpencil_layer_getframe() should behave when there + * is no existing GP-Frame on the frame requested. + */ +typedef enum eGP_GetFrame_Mode { + /* Use the preceeding gp-frame (i.e. don't add anything) */ + GP_GETFRAME_USE_PREV = 0, + + /* Add a new empty/blank frame */ + GP_GETFRAME_ADD_NEW = 1, + /* Make a copy of the active frame */ + GP_GETFRAME_ADD_COPY = 2 +} eGP_GetFrame_Mode; + +struct bGPDframe *gpencil_layer_getframe(struct bGPDlayer *gpl, int cframe, eGP_GetFrame_Mode addnew); struct bGPDframe *BKE_gpencil_layer_find_frame(struct bGPDlayer *gpl, int cframe); -struct bGPDframe *gpencil_layer_getframe(struct bGPDlayer *gpl, int cframe, short addnew); bool gpencil_layer_delframe(struct bGPDlayer *gpl, struct bGPDframe *gpf); + struct bGPDlayer *gpencil_layer_getactive(struct bGPdata *gpd); void gpencil_layer_setactive(struct bGPdata *gpd, struct bGPDlayer *active); void gpencil_layer_delete(struct bGPdata *gpd, struct bGPDlayer *gpl); diff --git a/source/blender/blenkernel/intern/gpencil.c b/source/blender/blenkernel/intern/gpencil.c index ee5c9192371..e629a0791c9 100644 --- a/source/blender/blenkernel/intern/gpencil.c +++ b/source/blender/blenkernel/intern/gpencil.c @@ -132,7 +132,7 @@ bGPDframe *gpencil_frame_addnew(bGPDlayer *gpl, int cframe) bGPDframe *gpf = NULL, *gf = NULL; short state = 0; - /* error checking (neg frame only if they are not allowed in Blender!) */ + /* error checking */ if (gpl == NULL) return NULL; @@ -178,6 +178,61 @@ bGPDframe *gpencil_frame_addnew(bGPDlayer *gpl, int cframe) return gpf; } +/* add a copy of the active gp-frame to the given layer */ +bGPDframe *gpencil_frame_addcopy(bGPDlayer *gpl, int cframe) +{ + bGPDframe *new_frame, *gpf; + bool found = false; + + /* Error checking/handling */ + if (gpl == NULL) { + /* no layer */ + return NULL; + } + else if (gpl->actframe == NULL) { + /* no active frame, so just create a new one from scratch */ + return gpencil_frame_addnew(gpl, cframe); + } + + /* Create a copy of the frame */ + new_frame = gpencil_frame_duplicate(gpl->actframe); + + /* Find frame to insert it before */ + for (gpf = gpl->frames.first; gpf; gpf = gpf->next) { + if (gpf->framenum > cframe) { + /* Add it here */ + BLI_insertlinkbefore(&gpl->frames, gpf, new_frame); + + found = true; + break; + } + else if (gpf->framenum == cframe) { + /* This only happens when we're editing with framelock on... + * - Delete the new frame and don't do anything else here... + */ + free_gpencil_strokes(new_frame); + MEM_freeN(new_frame); + new_frame = NULL; + + found = true; + break; + } + } + + if (found == false) { + /* Add new frame to the end */ + BLI_addtail(&gpl->frames, new_frame); + } + + /* Ensure that frame is set up correctly, and return it */ + if (new_frame) { + new_frame->framenum = cframe; + gpl->actframe = new_frame; + } + + return new_frame; +} + /* add a new gp-layer and make it the active layer */ bGPDlayer *gpencil_layer_addnew(bGPdata *gpd, const char *name, int setactive) { @@ -197,6 +252,13 @@ bGPDlayer *gpencil_layer_addnew(bGPdata *gpd, const char *name, int setactive) copy_v4_v4(gpl->color, U.gpencil_new_layer_col); gpl->thickness = 3; + /* onion-skinning settings */ + gpl->flag |= (GP_LAYER_GHOST_PREVCOL | GP_LAYER_GHOST_NEXTCOL); + + ARRAY_SET_ITEMS(gpl->gcolor_prev, 0.145098f, 0.419608f, 0.137255f); /* green */ + ARRAY_SET_ITEMS(gpl->gcolor_next, 0.125490f, 0.082353f, 0.529412f); /* blue */ + + /* auto-name */ BLI_strncpy(gpl->info, name, sizeof(gpl->info)); BLI_uniquename(&gpd->layers, gpl, DATA_("GP_Layer"), '.', offsetof(bGPDlayer, info), sizeof(gpl->info)); @@ -387,7 +449,7 @@ bGPDframe *BKE_gpencil_layer_find_frame(bGPDlayer *gpl, int cframe) * - this sets the layer's actframe var (if allowed to) * - extension beyond range (if first gp-frame is after all frame in interest and cannot add) */ -bGPDframe *gpencil_layer_getframe(bGPDlayer *gpl, int cframe, short addnew) +bGPDframe *gpencil_layer_getframe(bGPDlayer *gpl, int cframe, eGP_GetFrame_Mode addnew) { bGPDframe *gpf = NULL; short found = 0; @@ -425,6 +487,8 @@ bGPDframe *gpencil_layer_getframe(bGPDlayer *gpl, int cframe, short addnew) if (addnew) { if ((found) && (gpf->framenum == cframe)) gpl->actframe = gpf; + else if (addnew == GP_GETFRAME_ADD_COPY) + gpl->actframe = gpencil_frame_addcopy(gpl, cframe); else gpl->actframe = gpencil_frame_addnew(gpl, cframe); } @@ -445,6 +509,8 @@ bGPDframe *gpencil_layer_getframe(bGPDlayer *gpl, int cframe, short addnew) if (addnew) { if ((found) && (gpf->framenum == cframe)) gpl->actframe = gpf; + else if (addnew == GP_GETFRAME_ADD_COPY) + gpl->actframe = gpencil_frame_addcopy(gpl, cframe); else gpl->actframe = gpencil_frame_addnew(gpl, cframe); } diff --git a/source/blender/blenkernel/intern/scene.c b/source/blender/blenkernel/intern/scene.c index 82a040f4ca0..b2ef693c7eb 100644 --- a/source/blender/blenkernel/intern/scene.c +++ b/source/blender/blenkernel/intern/scene.c @@ -750,6 +750,53 @@ void BKE_scene_init(Scene *sce) copy_v2_fl2(sce->safe_areas.action_center, 15.0f / 100.0f, 5.0f / 100.0f); sce->preview = NULL; + + /* GP Sculpt brushes */ + { + GP_BrushEdit_Settings *gset = &sce->toolsettings->gp_sculpt; + GP_EditBrush_Data *gp_brush; + + gp_brush = &gset->brush[GP_EDITBRUSH_TYPE_SMOOTH]; + gp_brush->size = 25; + gp_brush->strength = 0.3f; + gp_brush->flag = GP_EDITBRUSH_FLAG_USE_FALLOFF | GP_EDITBRUSH_FLAG_SMOOTH_PRESSURE; + + gp_brush = &gset->brush[GP_EDITBRUSH_TYPE_THICKNESS]; + gp_brush->size = 25; + gp_brush->strength = 0.5f; + gp_brush->flag = GP_EDITBRUSH_FLAG_USE_FALLOFF; + + gp_brush = &gset->brush[GP_EDITBRUSH_TYPE_GRAB]; + gp_brush->size = 50; + gp_brush->strength = 0.3f; + gp_brush->flag = GP_EDITBRUSH_FLAG_USE_FALLOFF; + + gp_brush = &gset->brush[GP_EDITBRUSH_TYPE_PUSH]; + gp_brush->size = 25; + gp_brush->strength = 0.3f; + gp_brush->flag = GP_EDITBRUSH_FLAG_USE_FALLOFF; + + gp_brush = &gset->brush[GP_EDITBRUSH_TYPE_TWIST]; + gp_brush->size = 50; + gp_brush->strength = 0.3f; // XXX? + gp_brush->flag = GP_EDITBRUSH_FLAG_USE_FALLOFF; + + gp_brush = &gset->brush[GP_EDITBRUSH_TYPE_PINCH]; + gp_brush->size = 50; + gp_brush->strength = 0.5f; // XXX? + gp_brush->flag = GP_EDITBRUSH_FLAG_USE_FALLOFF; + + gp_brush = &gset->brush[GP_EDITBRUSH_TYPE_RANDOMISE]; + gp_brush->size = 25; + gp_brush->strength = 0.5f; + gp_brush->flag = GP_EDITBRUSH_FLAG_USE_FALLOFF; + } + + /* GP Stroke Placement */ + sce->toolsettings->gpencil_v3d_align = GP_PROJECT_VIEWSPACE; + sce->toolsettings->gpencil_v2d_align = GP_PROJECT_VIEWSPACE; + sce->toolsettings->gpencil_seq_align = GP_PROJECT_VIEWSPACE; + sce->toolsettings->gpencil_ima_align = GP_PROJECT_VIEWSPACE; } Scene *BKE_scene_add(Main *bmain, const char *name) diff --git a/source/blender/blenloader/intern/readfile.c b/source/blender/blenloader/intern/readfile.c index 9bc0e241722..177055e7206 100644 --- a/source/blender/blenloader/intern/readfile.c +++ b/source/blender/blenloader/intern/readfile.c @@ -5822,6 +5822,7 @@ static void direct_link_scene(FileData *fd, Scene *sce) sce->toolsettings->particle.paintcursor = NULL; sce->toolsettings->particle.scene = NULL; sce->toolsettings->particle.object = NULL; + sce->toolsettings->gp_sculpt.paintcursor = NULL; /* in rare cases this is needed, see [#33806] */ if (sce->toolsettings->vpaint) { diff --git a/source/blender/blenloader/intern/versioning_270.c b/source/blender/blenloader/intern/versioning_270.c index a0248d5a97c..8a2045ddacf 100644 --- a/source/blender/blenloader/intern/versioning_270.c +++ b/source/blender/blenloader/intern/versioning_270.c @@ -38,6 +38,7 @@ #include "DNA_camera_types.h" #include "DNA_cloth_types.h" #include "DNA_constraint_types.h" +#include "DNA_gpencil_types.h" #include "DNA_sdna_types.h" #include "DNA_sequence_types.h" #include "DNA_space_types.h" @@ -935,5 +936,81 @@ void blo_do_versions_270(FileData *fd, Library *UNUSED(lib), Main *main) } } - + { + Scene *scene; + for (scene = main->scene.first; scene; scene = scene->id.next) { + ToolSettings *ts = scene->toolsettings; + + if (ts->gp_sculpt.brush[0].size == 0) { + GP_BrushEdit_Settings *gset = &ts->gp_sculpt; + GP_EditBrush_Data *brush; + + brush = &gset->brush[GP_EDITBRUSH_TYPE_SMOOTH]; + brush->size = 25; + brush->strength = 0.3f; + brush->flag = GP_EDITBRUSH_FLAG_USE_FALLOFF | GP_EDITBRUSH_FLAG_SMOOTH_PRESSURE; + + brush = &gset->brush[GP_EDITBRUSH_TYPE_THICKNESS]; + brush->size = 25; + brush->strength = 0.5f; + brush->flag = GP_EDITBRUSH_FLAG_USE_FALLOFF; + + brush = &gset->brush[GP_EDITBRUSH_TYPE_GRAB]; + brush->size = 50; + brush->strength = 0.3f; + brush->flag = GP_EDITBRUSH_FLAG_USE_FALLOFF; + + brush = &gset->brush[GP_EDITBRUSH_TYPE_PUSH]; + brush->size = 25; + brush->strength = 0.3f; + brush->flag = GP_EDITBRUSH_FLAG_USE_FALLOFF; + + brush = &gset->brush[GP_EDITBRUSH_TYPE_TWIST]; + brush->size = 50; + brush->strength = 0.3f; // XXX? + brush->flag = GP_EDITBRUSH_FLAG_USE_FALLOFF; + + brush = &gset->brush[GP_EDITBRUSH_TYPE_PINCH]; + brush->size = 50; + brush->strength = 0.5f; // XXX? + brush->flag = GP_EDITBRUSH_FLAG_USE_FALLOFF; + + brush = &gset->brush[GP_EDITBRUSH_TYPE_RANDOMISE]; + brush->size = 25; + brush->strength = 0.5f; + brush->flag = GP_EDITBRUSH_FLAG_USE_FALLOFF; + + brush = &gset->brush[GP_EDITBRUSH_TYPE_CLONE]; + brush->size = 50; + brush->strength = 1.0f; + } + + if (!DNA_struct_elem_find(fd->filesdna, "ToolSettings", "char", "gpencil_v3d_align")) { +#if 0 /* XXX: Cannot do this, as we get random crashes... */ + if (scene->gpd) { + bGPdata *gpd = scene->gpd; + + /* Copy over the settings stored in the GP datablock linked to the scene, for minimal disruption */ + ts->gpencil_v3d_align = 0; + + if (gpd->flag & GP_DATA_VIEWALIGN) ts->gpencil_v3d_align |= GP_PROJECT_VIEWSPACE; + if (gpd->flag & GP_DATA_DEPTH_VIEW) ts->gpencil_v3d_align |= GP_PROJECT_DEPTH_VIEW; + if (gpd->flag & GP_DATA_DEPTH_STROKE) ts->gpencil_v3d_align |= GP_PROJECT_DEPTH_STROKE; + + if (gpd->flag & GP_DATA_DEPTH_STROKE_ENDPOINTS) + ts->gpencil_v3d_align |= GP_PROJECT_DEPTH_STROKE_ENDPOINTS; + } + else { + /* Default to cursor for all standard 3D views */ + ts->gpencil_v3d_align = GP_PROJECT_VIEWSPACE; + } +#endif + + ts->gpencil_v3d_align = GP_PROJECT_VIEWSPACE; + ts->gpencil_v2d_align = GP_PROJECT_VIEWSPACE; + ts->gpencil_seq_align = GP_PROJECT_VIEWSPACE; + ts->gpencil_ima_align = GP_PROJECT_VIEWSPACE; + } + } + } } diff --git a/source/blender/blenloader/intern/versioning_defaults.c b/source/blender/blenloader/intern/versioning_defaults.c index 01af11e78d1..eb0d392aa92 100644 --- a/source/blender/blenloader/intern/versioning_defaults.c +++ b/source/blender/blenloader/intern/versioning_defaults.c @@ -91,6 +91,51 @@ void BLO_update_defaults_startup_blend(Main *bmain) sculpt->flags |= SCULPT_DYNTOPO_COLLAPSE; sculpt->detail_size = 12; } + + if (ts->gp_sculpt.brush[0].size == 0) { + GP_BrushEdit_Settings *gset = &ts->gp_sculpt; + GP_EditBrush_Data *brush; + + brush = &gset->brush[GP_EDITBRUSH_TYPE_SMOOTH]; + brush->size = 25; + brush->strength = 0.3f; + brush->flag = GP_EDITBRUSH_FLAG_USE_FALLOFF | GP_EDITBRUSH_FLAG_SMOOTH_PRESSURE; + + brush = &gset->brush[GP_EDITBRUSH_TYPE_THICKNESS]; + brush->size = 25; + brush->strength = 0.5f; + brush->flag = GP_EDITBRUSH_FLAG_USE_FALLOFF; + + brush = &gset->brush[GP_EDITBRUSH_TYPE_GRAB]; + brush->size = 50; + brush->strength = 0.3f; + brush->flag = GP_EDITBRUSH_FLAG_USE_FALLOFF; + + brush = &gset->brush[GP_EDITBRUSH_TYPE_PUSH]; + brush->size = 25; + brush->strength = 0.3f; + brush->flag = GP_EDITBRUSH_FLAG_USE_FALLOFF; + + brush = &gset->brush[GP_EDITBRUSH_TYPE_TWIST]; + brush->size = 50; + brush->strength = 0.3f; // XXX? + brush->flag = GP_EDITBRUSH_FLAG_USE_FALLOFF; + + brush = &gset->brush[GP_EDITBRUSH_TYPE_PINCH]; + brush->size = 50; + brush->strength = 0.5f; // XXX? + brush->flag = GP_EDITBRUSH_FLAG_USE_FALLOFF; + + brush = &gset->brush[GP_EDITBRUSH_TYPE_RANDOMISE]; + brush->size = 25; + brush->strength = 0.5f; + brush->flag = GP_EDITBRUSH_FLAG_USE_FALLOFF; + } + + ts->gpencil_v3d_align = GP_PROJECT_VIEWSPACE; + ts->gpencil_v2d_align = GP_PROJECT_VIEWSPACE; + ts->gpencil_seq_align = GP_PROJECT_VIEWSPACE; + ts->gpencil_ima_align = GP_PROJECT_VIEWSPACE; } scene->gm.lodflag |= SCE_LOD_USE_HYST; diff --git a/source/blender/editors/animation/anim_draw.c b/source/blender/editors/animation/anim_draw.c index ed3d228a93e..f41420ad231 100644 --- a/source/blender/editors/animation/anim_draw.c +++ b/source/blender/editors/animation/anim_draw.c @@ -403,7 +403,6 @@ static bool find_prev_next_keyframes(struct bContext *C, int *nextfra, int *prev { Scene *scene = CTX_data_scene(C); Object *ob = CTX_data_active_object(C); - bGPdata *gpd = CTX_data_gpencil_data(C); Mask *mask = CTX_data_edit_mask(C); bDopeSheet ads = {NULL}; DLRBT_Tree keys; @@ -425,11 +424,12 @@ static bool find_prev_next_keyframes(struct bContext *C, int *nextfra, int *prev /* populate tree with keyframe nodes */ scene_to_keylist(&ads, scene, &keys, NULL); + gpencil_to_keylist(&ads, scene->gpd, &keys); - if (ob) + if (ob) { ob_to_keylist(&ads, ob, &keys, NULL); - - gpencil_to_keylist(&ads, gpd, &keys); + gpencil_to_keylist(&ads, ob->gpd, &keys); + } if (mask) { MaskLayer *masklay = BKE_mask_layer_active(mask); diff --git a/source/blender/editors/animation/anim_filter.c b/source/blender/editors/animation/anim_filter.c index bdf5291dcc7..51b5a366861 100644 --- a/source/blender/editors/animation/anim_filter.c +++ b/source/blender/editors/animation/anim_filter.c @@ -1505,7 +1505,7 @@ static size_t animdata_filter_shapekey(bAnimContext *ac, ListBase *anim_data, Ke } /* Helper for Grease Pencil - layers within a datablock */ -static size_t animdata_filter_gpencil_data(ListBase *anim_data, bGPdata *gpd, int filter_mode) +static size_t animdata_filter_gpencil_layers_data(ListBase *anim_data, bDopeSheet *ads, bGPdata *gpd, int filter_mode) { bGPDlayer *gpl; size_t items = 0; @@ -1518,6 +1518,13 @@ static size_t animdata_filter_gpencil_data(ListBase *anim_data, bGPdata *gpd, in if (!(filter_mode & ANIMFILTER_FOREDIT) || EDITABLE_GPL(gpl)) { /* active... */ if (!(filter_mode & ANIMFILTER_ACTIVE) || (gpl->flag & GP_LAYER_ACTIVE)) { + /* skip layer if the name doesn't match the filter string */ + if ((ads) && (ads->filterflag & ADS_FILTER_BY_FCU_NAME)) { + if (BLI_strcasestr(gpl->info, ads->searchstr) == NULL) + continue; + } + + /* add to list */ ANIMCHANNEL_NEW_CHANNEL(gpl, ANIMTYPE_GPLAYER, gpd); } @@ -1528,54 +1535,121 @@ static size_t animdata_filter_gpencil_data(ListBase *anim_data, bGPdata *gpd, in return items; } -/* Grab all Grease Pencil datablocks in file */ -// TODO: should this be amalgamated with the dopesheet filtering code? -static size_t animdata_filter_gpencil(ListBase *anim_data, void *UNUSED(data), int filter_mode) +/* Helper for Grease Pencil - Grease Pencil datablock - GP Frames */ +static size_t animdata_filter_gpencil_data(ListBase *anim_data, bDopeSheet *ads, bGPdata *gpd, int filter_mode) { - bGPdata *gpd; size_t items = 0; - /* for now, grab grease pencil datablocks directly from main */ - // XXX: this is not good... - for (gpd = G.main->gpencil.first; gpd; gpd = gpd->id.next) { + /* When asked from "AnimData" blocks (i.e. the top-level containers for normal animation), + * for convenience, this will return GP Datablocks instead. This may cause issues down + * the track, but for now, this will do... + */ + if (filter_mode & ANIMFILTER_ANIMDATA) { + /* just add GPD as a channel - this will add everything needed */ + ANIMCHANNEL_NEW_CHANNEL(gpd, ANIMTYPE_GPDATABLOCK, NULL); + } + else { ListBase tmp_data = {NULL, NULL}; size_t tmp_items = 0; - /* only show if gpd is used by something... */ - if (ID_REAL_USERS(gpd) < 1) - continue; - - /* When asked from "AnimData" blocks (i.e. the top-level containers for normal animation), - * for convenience, this will return GP Datablocks instead. This may cause issues down - * the track, but for now, this will do... - */ - if (filter_mode & ANIMFILTER_ANIMDATA) { - /* just add GPD as a channel - this will add everything needed */ - ANIMCHANNEL_NEW_CHANNEL(gpd, ANIMTYPE_GPDATABLOCK, NULL); + /* add gpencil animation channels */ + BEGIN_ANIMFILTER_SUBCHANNELS(EXPANDED_GPD(gpd)) + { + tmp_items += animdata_filter_gpencil_layers_data(&tmp_data, ads, gpd, filter_mode); } - else { - /* add gpencil animation channels */ - BEGIN_ANIMFILTER_SUBCHANNELS(EXPANDED_GPD(gpd)) - { - tmp_items += animdata_filter_gpencil_data(&tmp_data, gpd, filter_mode); + END_ANIMFILTER_SUBCHANNELS; + + /* did we find anything? */ + if (tmp_items) { + /* include data-expand widget first */ + if (filter_mode & ANIMFILTER_LIST_CHANNELS) { + /* add gpd as channel too (if for drawing, and it has layers) */ + ANIMCHANNEL_NEW_CHANNEL(gpd, ANIMTYPE_GPDATABLOCK, NULL); } - END_ANIMFILTER_SUBCHANNELS; - /* did we find anything? */ - if (tmp_items) { - /* include data-expand widget first */ - if (filter_mode & ANIMFILTER_LIST_CHANNELS) { - /* add gpd as channel too (if for drawing, and it has layers) */ - ANIMCHANNEL_NEW_CHANNEL(gpd, ANIMTYPE_GPDATABLOCK, NULL); + /* now add the list of collected channels */ + BLI_movelisttolist(anim_data, &tmp_data); + BLI_assert(BLI_listbase_is_empty(&tmp_data)); + items += tmp_items; + } + } + + return items; +} + +/* Grab all Grease Pencil datablocks in file */ +// TODO: should this be amalgamated with the dopesheet filtering code? +static size_t animdata_filter_gpencil(bAnimContext *ac, ListBase *anim_data, void *UNUSED(data), int filter_mode) +{ + bDopeSheet *ads = ac->ads; + size_t items = 0; + + if (ads->filterflag & ADS_FILTER_GP_3DONLY) { + Scene *scene = (Scene *)ads->source; + Base *base; + + /* Active scene's GPencil block first - No parent item needed... */ + if (scene->gpd) { + items += animdata_filter_gpencil_data(anim_data, ads, scene->gpd, filter_mode); + } + + /* Objects in the scene */ + for (base = scene->base.first; base; base = base->next) { + /* Only consider this object if it has got some GP data (saving on all the other tests) */ + if (base->object && base->object->gpd) { + Object *ob = base->object; + + /* firstly, check if object can be included, by the following factors: + * - if only visible, must check for layer and also viewport visibility + * --> while tools may demand only visible, user setting takes priority + * as user option controls whether sets of channels get included while + * tool-flag takes into account collapsed/open channels too + * - if only selected, must check if object is selected + * - there must be animation data to edit (this is done recursively as we + * try to add the channels) + */ + if ((filter_mode & ANIMFILTER_DATA_VISIBLE) && !(ads->filterflag & ADS_FILTER_INCL_HIDDEN)) { + /* layer visibility - we check both object and base, since these may not be in sync yet */ + if ((scene->lay & (ob->lay | base->lay)) == 0) continue; + + /* outliner restrict-flag */ + if (ob->restrictflag & OB_RESTRICT_VIEW) continue; } - /* now add the list of collected channels */ - BLI_movelisttolist(anim_data, &tmp_data); - BLI_assert(BLI_listbase_is_empty(&tmp_data)); - items += tmp_items; + /* check selection and object type filters */ + if ( (ads->filterflag & ADS_FILTER_ONLYSEL) && !((base->flag & SELECT) /*|| (base == scene->basact)*/) ) { + /* only selected should be shown */ + continue; + } + + /* check if object belongs to the filtering group if option to filter + * objects by the grouped status is on + * - used to ease the process of doing multiple-character choreographies + */ + if (ads->filterflag & ADS_FILTER_ONLYOBGROUP) { + if (BKE_group_object_exists(ads->filter_grp, ob) == 0) + continue; + } + + /* finally, include this object's grease pencil datablock */ + /* XXX: Should we store these under expanders per item? */ + items += animdata_filter_gpencil_data(anim_data, ads, ob->gpd, filter_mode); } } } + else { + bGPdata *gpd; + + /* Grab all Grease Pencil datablocks directly from main, but only those that seem to be useful somewhere */ + for (gpd = G.main->gpencil.first; gpd; gpd = gpd->id.next) { + /* only show if gpd is used by something... */ + if (ID_REAL_USERS(gpd) < 1) + continue; + + /* add GP frames from this datablock */ + items += animdata_filter_gpencil_data(anim_data, ads, gpd, filter_mode); + } + } /* return the number of items added to the list */ return items; @@ -2880,7 +2954,7 @@ size_t ANIM_animdata_filter(bAnimContext *ac, ListBase *anim_data, eAnimFilter_F case ANIMCONT_GPENCIL: { if (animdata_filter_dopesheet_summary(ac, anim_data, filter_mode, &items)) - items = animdata_filter_gpencil(anim_data, data, filter_mode); + items = animdata_filter_gpencil(ac, anim_data, data, filter_mode); break; } case ANIMCONT_MASK: diff --git a/source/blender/editors/animation/keyframes_general.c b/source/blender/editors/animation/keyframes_general.c index be58c75e202..0e9e46bced0 100644 --- a/source/blender/editors/animation/keyframes_general.c +++ b/source/blender/editors/animation/keyframes_general.c @@ -929,7 +929,7 @@ short paste_animedit_keys(bAnimContext *ac, ListBase *anim_data, return -1; } - /* mathods of offset */ + /* methods of offset */ switch (offset_mode) { case KEYFRAME_PASTE_OFFSET_CFRA_START: offset = (float)(CFRA - animcopy_firstframe); diff --git a/source/blender/editors/gpencil/CMakeLists.txt b/source/blender/editors/gpencil/CMakeLists.txt index 83a13abb33f..6604d595573 100644 --- a/source/blender/editors/gpencil/CMakeLists.txt +++ b/source/blender/editors/gpencil/CMakeLists.txt @@ -40,6 +40,7 @@ set(INC_SYS set(SRC drawgpencil.c editaction_gpencil.c + gpencil_brush.c gpencil_convert.c gpencil_data.c gpencil_edit.c diff --git a/source/blender/editors/gpencil/drawgpencil.c b/source/blender/editors/gpencil/drawgpencil.c index d17ed22b1ec..f207c71474c 100644 --- a/source/blender/editors/gpencil/drawgpencil.c +++ b/source/blender/editors/gpencil/drawgpencil.c @@ -1166,6 +1166,7 @@ static void gp_draw_data_all(Scene *scene, bGPdata *gpd, int offsx, int offsy, i /* draw grease-pencil sketches to specified 2d-view that uses ibuf corrections */ void ED_gpencil_draw_2dimage(const bContext *C) { + wmWindowManager *wm = CTX_wm_manager(C); ScrArea *sa = CTX_wm_area(C); ARegion *ar = CTX_wm_region(C); Scene *scene = CTX_data_scene(C); @@ -1218,6 +1219,13 @@ void ED_gpencil_draw_2dimage(const bContext *C) break; } + if (ED_screen_animation_playing(wm)) { + /* don't show onionskins during animation playback/scrub (i.e. it obscures the poses) + * OpenGL Renders (i.e. final output), or depth buffer (i.e. not real strokes) + */ + dflag |= GP_DRAWDATA_NO_ONIONS; + } + /* draw it! */ gp_draw_data_all(scene, gpd, offsx, offsy, sizex, sizey, CFRA, dflag, sa->spacetype); @@ -1228,6 +1236,7 @@ void ED_gpencil_draw_2dimage(const bContext *C) * second time with onlyv2d=0 for screen-aligned strokes */ void ED_gpencil_draw_view2d(const bContext *C, bool onlyv2d) { + wmWindowManager *wm = CTX_wm_manager(C); ScrArea *sa = CTX_wm_area(C); ARegion *ar = CTX_wm_region(C); Scene *scene = CTX_data_scene(C); @@ -1246,6 +1255,8 @@ void ED_gpencil_draw_view2d(const bContext *C, bool onlyv2d) /* draw it! */ if (onlyv2d) dflag |= (GP_DRAWDATA_ONLYV2D | GP_DRAWDATA_NOSTATUS); + if (ED_screen_animation_playing(wm)) dflag |= GP_DRAWDATA_NO_ONIONS; + gp_draw_data_all(scene, gpd, 0, 0, ar->winx, ar->winy, CFRA, dflag, sa->spacetype); /* draw status text (if in screen/pixel-space) */ @@ -1257,7 +1268,7 @@ void ED_gpencil_draw_view2d(const bContext *C, bool onlyv2d) /* draw grease-pencil sketches to specified 3d-view assuming that matrices are already set correctly * Note: this gets called twice - first time with only3d=1 to draw 3d-strokes, * second time with only3d=0 for screen-aligned strokes */ -void ED_gpencil_draw_view3d(Scene *scene, View3D *v3d, ARegion *ar, bool only3d) +void ED_gpencil_draw_view3d(wmWindowManager *wm, Scene *scene, View3D *v3d, ARegion *ar, bool only3d) { bGPdata *gpd; int dflag = 0; @@ -1300,13 +1311,15 @@ void ED_gpencil_draw_view3d(Scene *scene, View3D *v3d, ARegion *ar, bool only3d) dflag |= GP_DRAWDATA_NOSTATUS; } + if ((wm == NULL) || ED_screen_animation_playing(wm)) { + /* don't show onionskins during animation playback/scrub (i.e. it obscures the poses) + * OpenGL Renders (i.e. final output), or depth buffer (i.e. not real strokes) + */ + dflag |= GP_DRAWDATA_NO_ONIONS; + } + /* draw it! */ gp_draw_data_all(scene, gpd, offsx, offsy, winx, winy, CFRA, dflag, v3d->spacetype); - - /* draw status text (if in screen/pixel-space) */ - if (only3d == false) { - gp_draw_status_text(gpd, ar); - } } void ED_gpencil_draw_ex(Scene *scene, bGPdata *gpd, int winx, int winy, const int cfra, const char spacetype) diff --git a/source/blender/editors/gpencil/editaction_gpencil.c b/source/blender/editors/gpencil/editaction_gpencil.c index a2ba6216f9c..9f96ac6122f 100644 --- a/source/blender/editors/gpencil/editaction_gpencil.c +++ b/source/blender/editors/gpencil/editaction_gpencil.c @@ -44,11 +44,15 @@ #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" + /* ***************************************** */ /* NOTE ABOUT THIS FILE: * This file contains code for editing Grease Pencil data in the Action Editor @@ -268,7 +272,7 @@ void ED_gplayer_frames_keytype_set(bGPDlayer *gpl, short type) } } -#if 0 // XXX disabled until grease pencil code stabilises again + /* -------------------------------------- */ /* Copy and Paste Tools */ /* - The copy/paste buffer currently stores a set of GP_Layers, with temporary @@ -280,118 +284,155 @@ void ED_gplayer_frames_keytype_set(bGPDlayer *gpl, short type) */ /* globals for copy/paste data (like for other copy/paste buffers) */ -ListBase gpcopybuf = {NULL, NULL}; -static int gpcopy_firstframe = 999999999; +ListBase gp_anim_copybuf = {NULL, NULL}; +static int gp_anim_copy_firstframe = 999999999; +static int gp_anim_copy_lastframe = -999999999; +static int gp_anim_copy_cfra = 0; + /* This function frees any MEM_calloc'ed copy/paste buffer data */ -void free_gpcopybuf() +void ED_gpencil_anim_copybuf_free(void) { - free_gpencil_layers(&gpcopybuf); + free_gpencil_layers(&gp_anim_copybuf); + BLI_listbase_clear(&gp_anim_copybuf); - BLI_listbase_clear(&gpcopybuf); - gpcopy_firstframe = 999999999; + gp_anim_copy_firstframe = 999999999; + gp_anim_copy_lastframe = -999999999; + gp_anim_copy_cfra = 0; } + /* This function adds data to the copy/paste buffer, freeing existing data first * Only the selected GP-layers get their selected keyframes copied. + * + * Returns whether the copy operation was successful or not */ -void copy_gpdata() +bool ED_gpencil_anim_copybuf_copy(bAnimContext *ac) { - ListBase act_data = {NULL, NULL}; - bActListElem *ale; + ListBase anim_data = {NULL, NULL}; + bAnimListElem *ale; int filter; - void *data; - short datatype; - /* clear buffer first */ - free_gpcopybuf(); + Scene *scene = ac->scene; - /* 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); + /* clear buffer first */ + ED_gpencil_anim_copybuf_free(); - /* 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"); - - BLI_listbase_clear(&gpln->frames); - BLI_strncpy(gpln->info, gpls->info, sizeof(gpln->info)); - - BLI_addtail(&gpcopybuf, gpln); + /* filter data */ + filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE | ANIMFILTER_NODUPLIS); + ANIM_animdata_filter(ac, &anim_data, filter, ac->data, ac->datatype); + + /* assume that each of these is a GP layer */ + for (ale = anim_data.first; ale; ale = ale->next) { + ListBase copied_frames = {NULL, NULL}; + bGPDlayer *gpl = (bGPDlayer *)ale->data; + bGPDframe *gpf; /* loop over frames, and copy only selected frames */ - for (gpf = gpls->frames.first; gpf; gpf = gpf->next) { + for (gpf = gpl->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); + /* make a copy of this frame */ + bGPDframe *new_frame = gpencil_frame_duplicate(gpf); + BLI_addtail(&copied_frames, new_frame); - /* check if this is the earliest frame encountered so far */ - if (gpf->framenum < gpcopy_firstframe) - gpcopy_firstframe = gpf->framenum; + /* extend extents for keyframes encountered */ + if (gpf->framenum < gp_anim_copy_firstframe) + gp_anim_copy_firstframe = gpf->framenum; + if (gpf->framenum > gp_anim_copy_lastframe) + gp_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(&gp_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 */ + gp_anim_copy_cfra = CFRA; + + /* clean up */ + ANIM_animdata_freelist(&anim_data); + /* check if anything ended up in the buffer */ - if (ELEM(NULL, gpcopybuf.first, gpcopybuf.last)) - error("Nothing copied to buffer"); + if (ELEM(NULL, gp_anim_copybuf.first, gp_anim_copybuf.last)) { + BKE_report(ac->reports, RPT_ERROR, "No keyframes copied to keyframes copy/paste buffer"); + return false; + } - /* free temp memory */ - BLI_freelistN(&act_data); + /* report success */ + return true; } -void paste_gpdata(Scene *scene) + +/* Pastes keyframes from buffer, and reports success */ +bool ED_gpencil_anim_copybuf_paste(bAnimContext *ac, const short offset_mode) { - ListBase act_data = {NULL, NULL}; - bActListElem *ale; + ListBase anim_data = {NULL, NULL}; + bAnimListElem *ale; int filter; - void *data; - short datatype; - const int offset = (CFRA - gpcopy_firstframe); - short no_name = 0; + Scene *scene = ac->scene; + bool no_name = false; + int offset = 0; /* check if buffer is empty */ - if (ELEM(NULL, gpcopybuf.first, gpcopybuf.last)) { - error("No data in buffer to paste"); - return; + if (BLI_listbase_is_empty(&gp_anim_copybuf)) { + BKE_report(ac->reports, RPT_ERROR, "No data in buffer to paste"); + return false; } + /* check if single channel in buffer (disregard names if so) */ - if (gpcopybuf.first == gpcopybuf.last) - no_name = 1; + if (gp_anim_copybuf.first == gp_anim_copybuf.last) { + no_name = true; + } - /* get data */ - data = get_action_context(&datatype); - if (data == NULL) return; - if (datatype != ACTCONT_GPENCIL) return; + /* methods of offset (eKeyPasteOffset) */ + switch (offset_mode) { + case KEYFRAME_PASTE_OFFSET_CFRA_START: + offset = (CFRA - gp_anim_copy_firstframe); + break; + case KEYFRAME_PASTE_OFFSET_CFRA_END: + offset = (CFRA - gp_anim_copy_lastframe); + break; + case KEYFRAME_PASTE_OFFSET_CFRA_RELATIVE: + offset = (CFRA - gp_anim_copy_cfra); + break; + case KEYFRAME_PASTE_OFFSET_NONE: + offset = 0; + break; + } + /* filter data */ - filter = (ACTFILTER_VISIBLE | ACTFILTER_SEL | ACTFILTER_FOREDIT); - actdata_filter(&act_data, filter, data, datatype); + // TODO: try doing it with selection, then without selection imits + 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 = act_data.first; ale; ale = ale->next) { + for (ale = anim_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) { + for (gpls = gp_anim_copybuf.first; gpls; gpls = gpls->next) { /* check if layer name matches */ - if ((no_name) || STREQ(gpls->info, gpld->info)) + if ((no_name) || STREQ(gpls->info, gpld->info)) { break; + } } /* this situation might occur! */ @@ -407,58 +448,21 @@ void paste_gpdata(Scene *scene) 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); - sa = NULL; - /* this should be the right frame... as it may be a pre-existing frame, + /* 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 minimize memory usage (copying 'junk' 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, + * af it works, it will show up. */ for (gps = gpfs->strokes.first; gps; gps = gps->next) { - short stroke_ok; + /* make a copy of stroke, then of its points array */ + gpsn = MEM_dupallocN(gps); + gpsn->points = MEM_dupallocN(gps->points); - /* 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 */ - case SPACE_CLIP: /* 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); - } + /* append stroke to frame */ + BLI_addtail(&gpf->strokes, gpsn); } /* if no strokes (i.e. new frame) added, free gpf */ @@ -471,13 +475,10 @@ void paste_gpdata(Scene *scene) } } - /* free temp memory */ - BLI_freelistN(&act_data); - - /* undo and redraw stuff */ - BIF_undo_push("Paste Grease Pencil Frames"); + /* clean up */ + ANIM_animdata_freelist(&anim_data); + return true; } -#endif /* XXX disabled until Grease Pencil code stabilises again... */ /* -------------------------------------- */ /* Snap Tools */ diff --git a/source/blender/editors/gpencil/gpencil_brush.c b/source/blender/editors/gpencil/gpencil_brush.c new file mode 100644 index 00000000000..ecc78dfc6b4 --- /dev/null +++ b/source/blender/editors/gpencil/gpencil_brush.c @@ -0,0 +1,1691 @@ +/* + * ***** 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * The Original Code is Copyright (C) 2015, Blender Foundation + * This is a new part of Blender + * + * Contributor(s): Joshua Leung + * + * ***** END GPL LICENSE BLOCK ***** + * + * Brush based operators for editing Grease Pencil strokes + */ + +/** \file blender/editors/gpencil/gpencil_edit.c + * \ingroup edgpencil + */ + + +#include +#include +#include +#include +#include + +#include "MEM_guardedalloc.h" + +#include "BLI_blenlib.h" +#include "BLI_ghash.h" +#include "BLI_math.h" +#include "BLI_rand.h" +#include "BLI_utildefines.h" + +#include "BLT_translation.h" + +#include "DNA_scene_types.h" +#include "DNA_screen_types.h" +#include "DNA_space_types.h" +#include "DNA_view3d_types.h" +#include "DNA_gpencil_types.h" + +#include "BKE_context.h" +#include "BKE_global.h" +#include "BKE_gpencil.h" +#include "BKE_library.h" +#include "BKE_report.h" +#include "BKE_screen.h" + +#include "UI_interface.h" + +#include "WM_api.h" +#include "WM_types.h" + +#include "RNA_access.h" +#include "RNA_define.h" +#include "RNA_enum_types.h" + +#include "UI_view2d.h" + +#include "ED_gpencil.h" +#include "ED_screen.h" +#include "ED_view3d.h" + +#include "BIF_gl.h" +#include "BIF_glutil.h" + +#include "gpencil_intern.h" + +/* ************************************************ */ +/* General Brush Editing Context */ + +/* Context for brush operators */ +typedef struct tGP_BrushEditData { + /* Current editor/region/etc. */ + /* NOTE: This stuff is mainly needed to handle 3D view projection stuff... */ + Scene *scene; + + ScrArea *sa; + ARegion *ar; + + /* Current GPencil datablock */ + bGPdata *gpd; + + /* Brush Settings */ + GP_BrushEdit_Settings *settings; + GP_EditBrush_Data *brush; + + eGP_EditBrush_Types brush_type; + eGP_EditBrush_Flag flag; + + /* Space Conversion Data */ + GP_SpaceConversion gsc; + + + /* Is the brush currently painting? */ + bool is_painting; + + /* Start of new sculpt stroke */ + bool first; + + /* Current frame */ + int cfra; + + + /* Brush Runtime Data: */ + /* - position and pressure + * - the *_prev variants are the previous values + */ + int mval[2], mval_prev[2]; + float pressure, pressure_prev; + + /* - effect vector (e.g. 2D/3D translation for grab brush) */ + float dvec[3]; + + /* brush geometry (bounding box) */ + rcti brush_rect; + + /* Custom data for certain brushes */ + /* - map from bGPDstroke's to structs containing custom data about those strokes */ + GHash *stroke_customdata; + /* - general customdata */ + void *customdata; + + + /* Timer for in-place accumulation of brush effect */ + wmTimer *timer; + bool timerTick; /* is this event from a timer */ +} tGP_BrushEditData; + + +/* Callback for performing some brush operation on a single point */ +typedef bool (*GP_BrushApplyCb)(tGP_BrushEditData *gso, bGPDstroke *gps, int i, + const int radius, const int co[2]); + +/* ************************************************ */ +/* Utility Functions */ + +/* Context ---------------------------------------- */ + +/* Get the sculpting settings */ +static GP_BrushEdit_Settings *gpsculpt_get_settings(Scene *scene) +{ + return &scene->toolsettings->gp_sculpt; +} + +/* Get the active brush */ +static GP_EditBrush_Data *gpsculpt_get_brush(Scene *scene) +{ + GP_BrushEdit_Settings *gset = &scene->toolsettings->gp_sculpt; + return &gset->brush[gset->brushtype]; +} + +/* Brush Operations ------------------------------- */ + +/* Invert behaviour of brush? */ +static bool gp_brush_invert_check(tGP_BrushEditData *gso) +{ + /* The basic setting is the brush's setting (from the panel) */ + bool invert = ((gso->brush->flag & GP_EDITBRUSH_FLAG_INVERT) != 0); + + /* During runtime, the user can hold down the Ctrl key to invert the basic behaviour */ + if (gso->flag & GP_EDITBRUSH_FLAG_INVERT) { + invert ^= true; + } + + return invert; +} + +/* Compute strength of effect */ +static float gp_brush_influence_calc(tGP_BrushEditData *gso, const int radius, const int co[2]) +{ + GP_EditBrush_Data *brush = gso->brush; + + /* basic strength factor from brush settings */ + float influence = brush->strength; + + /* use pressure? */ + if (brush->flag & GP_EDITBRUSH_FLAG_USE_PRESSURE) { + influence *= gso->pressure; + } + + /* distance fading */ + if (brush->flag & GP_EDITBRUSH_FLAG_USE_FALLOFF) { + float distance = (float)len_v2v2_int(gso->mval, co); + float fac; + + CLAMP(distance, 0.0f, (float)radius); + fac = 1.0f - (distance / (float)radius); + + influence *= fac; + } + + /* return influence */ + return influence; +} + +/* ************************************************ */ +/* Brush Callbacks */ +/* This section defines the callbacks used by each brush to perform their magic. + * These are called on each point within the brush's radius. + */ + +/* ----------------------------------------------- */ +/* Smooth Brush */ + +/* A simple (but slower + inaccurate) smooth-brush implementation to test the algorithm for stroke smoothing */ +static bool gp_brush_smooth_apply(tGP_BrushEditData *gso, bGPDstroke *gps, int i, + const int radius, const int co[2]) +{ + GP_EditBrush_Data *brush = gso->brush; + bGPDspoint *pt = &gps->points[i]; + float inf = gp_brush_influence_calc(gso, radius, co); + float pressure = 0.0f; + float sco[3] = {0.0f}; + + /* Do nothing if not enough points to smooth out */ + if (gps->totpoints <= 2) { + return false; + } + + /* Only affect endpoints by a fraction of the normal strength, + * to prevent the stroke from shrinking too much + */ + if ((i == 0) || (i == gps->totpoints - 1)) { + inf *= 0.1f; + } + + /* Compute smoothed coordinate by taking the ones nearby */ + /* XXX: This is potentially slow, and suffers from accumulation error as earlier points are handled before later ones */ + { + // XXX: this is hardcoded to look at 2 points on either side of the current one (i.e. 5 items total) + const int steps = 2; + const float average_fac = 1.0f / (float)(steps * 2 + 1); + int step; + + /* add the point itself */ + madd_v3_v3fl(sco, &pt->x, average_fac); + + if (brush->flag & GP_EDITBRUSH_FLAG_SMOOTH_PRESSURE) { + pressure += pt->pressure * average_fac; + } + + /* n-steps before/after current point */ + // XXX: review how the endpoints are treated by this algorithm + // XXX: falloff measures should also introduce some weighting variations, so that further-out points get less weight + for (step = 1; step <= steps; step++) { + bGPDspoint *pt1, *pt2; + int before = i - step; + int after = i + step; + + CLAMP_MIN(before, 0); + CLAMP_MAX(after, gps->totpoints - 1); + + pt1 = &gps->points[before]; + pt2 = &gps->points[after]; + + /* add both these points to the average-sum (s += p[i]/n) */ + madd_v3_v3fl(sco, &pt1->x, average_fac); + madd_v3_v3fl(sco, &pt2->x, average_fac); + + /* do pressure too? */ + if (brush->flag & GP_EDITBRUSH_FLAG_SMOOTH_PRESSURE) { + pressure += pt1->pressure * average_fac; + pressure += pt2->pressure * average_fac; + } + } + } + + /* Based on influence factor, blend between original and optimal smoothed coordinate */ + interp_v3_v3v3(&pt->x, &pt->x, sco, inf); + + if (brush->flag & GP_EDITBRUSH_FLAG_SMOOTH_PRESSURE) { + pt->pressure = pressure; + } + + return true; +} + +/* ----------------------------------------------- */ +/* Line Thickness Brush */ + +/* Make lines thicker or thinner by the specified amounts */ +static bool gp_brush_thickness_apply(tGP_BrushEditData *gso, bGPDstroke *gps, int i, + const int radius, const int co[2]) +{ + bGPDspoint *pt = gps->points + i; + float inf; + + /* Compute strength of effect + * - We divide the strength by 10, so that users can set "sane" values. + * Otherwise, good default values are in the range of 0.093 + */ + inf = gp_brush_influence_calc(gso, radius, co) / 10.0f; + + /* apply */ + // XXX: this is much too strong, and it should probably do some smoothing with the surrounding stuff + if (gp_brush_invert_check(gso)) { + /* make line thinner - reduce stroke pressure */ + pt->pressure -= inf; + } + else { + /* make line thicker - increase stroke pressure */ + pt->pressure += inf; + } + + /* Pressure should stay within [0.0, 1.0] + * However, it is nice for volumetric strokes to be able to exceed + * the upper end of this range. Therefore, we don't actually clamp + * down on the upper end. + */ + if (pt->pressure < 0.0f) + pt->pressure = 0.0f; + + return true; +} + + +/* ----------------------------------------------- */ +/* Grab Brush */ + +/* Custom data per stroke for the Grab Brush + * + * This basically defines the strength of the effect for each + * affected stroke point that was within the initial range of + * the brush region. + */ +typedef struct tGPSB_Grab_StrokeData { + /* array of indices to corresponding points in the stroke */ + int *points; + /* array of influence weights for each of the included points */ + float *weights; + + /* capacity of the arrays */ + int capacity; + /* actual number of items currently stored */ + int size; +} tGPSB_Grab_StrokeData; + +/* initialise custom data for handling this stroke */ +static void gp_brush_grab_stroke_init(tGP_BrushEditData *gso, bGPDstroke *gps) +{ + tGPSB_Grab_StrokeData *data = NULL; + + BLI_assert(gps->totpoints > 0); + + /* Check if there are buffers already (from a prior run) */ + if (BLI_ghash_haskey(gso->stroke_customdata, gps)) { + /* Ensure that the caches are empty + * - Since we reuse these between different strokes, we don't + * want the previous invocation's data polluting the arrays + */ + data = BLI_ghash_lookup(gso->stroke_customdata, gps); + BLI_assert(data != NULL); + + data->size = 0; /* minimum requirement - so that we can repopulate again */ + + memset(data->points, 0, sizeof(int) * data->capacity); + memset(data->weights, 0, sizeof(float) * data->capacity); + } + else { + /* Create new instance */ + data = MEM_callocN(sizeof(tGPSB_Grab_StrokeData), "GP Stroke Grab Data"); + + data->capacity = gps->totpoints; + data->size = 0; + + data->points = MEM_callocN(sizeof(int) * data->capacity, "GP Stroke Grab Indices"); + data->weights = MEM_callocN(sizeof(float) * data->capacity, "GP Stroke Grab Weights"); + + /* hook up to the cache */ + BLI_ghash_insert(gso->stroke_customdata, gps, data); + } +} + +/* store references to stroke points in the initial stage */ +static bool gp_brush_grab_store_points(tGP_BrushEditData *gso, bGPDstroke *gps, int i, + const int radius, const int co[2]) +{ + tGPSB_Grab_StrokeData *data = BLI_ghash_lookup(gso->stroke_customdata, gps); + float inf = gp_brush_influence_calc(gso, radius, co); + + BLI_assert(data != NULL); + BLI_assert(data->size < data->capacity); + + /* insert this point into the set of affected points */ + data->points[data->size] = i; + data->weights[data->size] = inf; + data->size++; + + /* done */ + return true; +} + +/* Compute effect vector for grab brush */ +static void gp_brush_grab_calc_dvec(tGP_BrushEditData *gso) +{ + /* Convert mouse-movements to movement vector */ + // TODO: incorporate pressure into this? + // XXX: screen-space strokes in 3D space will suffer! + if (gso->sa->spacetype == SPACE_VIEW3D) { + View3D *v3d = gso->sa->spacedata.first; + RegionView3D *rv3d = gso->ar->regiondata; + float *rvec = ED_view3d_cursor3d_get(gso->scene, v3d); + float zfac = ED_view3d_calc_zfac(rv3d, rvec, NULL); + + float mval_f[2]; + + /* convert from 2D screenspace to 3D... */ + mval_f[0] = (float)(gso->mval[0] - gso->mval_prev[0]); + mval_f[1] = (float)(gso->mval[1] - gso->mval_prev[1]); + + ED_view3d_win_to_delta(gso->ar, mval_f, gso->dvec, zfac); + } + else { + /* 2D - just copy */ + // XXX: view2d? + gso->dvec[0] = (float)(gso->mval[0] - gso->mval_prev[0]); + gso->dvec[1] = (float)(gso->mval[1] - gso->mval_prev[1]); + gso->dvec[2] = 0.0f; /* unused */ + } +} + +/* Apply grab transform to all relevant points of the affected strokes */ +static void gp_brush_grab_apply_cached(tGP_BrushEditData *gso, bGPDstroke *gps) +{ + tGPSB_Grab_StrokeData *data = BLI_ghash_lookup(gso->stroke_customdata, gps); + int i; + + /* Apply dvec to all of the stored points */ + for (i = 0; i < data->size; i++) { + bGPDspoint *pt = &gps->points[data->points[i]]; + float delta[3] = {0.0f}; + + /* adjust the amount of displacement to apply */ + mul_v3_v3fl(delta, gso->dvec, data->weights[i]); + + /* apply */ + add_v3_v3(&pt->x, delta); + } +} + +/* free customdata used for handling this stroke */ +static void gp_brush_grab_stroke_free(void *ptr) +{ + tGPSB_Grab_StrokeData *data = (tGPSB_Grab_StrokeData *)ptr; + + /* free arrays */ + MEM_freeN(data->points); + MEM_freeN(data->weights); + + /* ... and this item itself, since it was also allocated */ + MEM_freeN(data); +} + +/* ----------------------------------------------- */ +/* Push Brush */ +/* NOTE: Depends on gp_brush_grab_calc_dvec() */ + +static bool gp_brush_push_apply(tGP_BrushEditData *gso, bGPDstroke *gps, int i, + const int radius, const int co[2]) +{ + bGPDspoint *pt = gps->points + i; + float inf = gp_brush_influence_calc(gso, radius, co); + float delta[3] = {0.0f}; + + /* adjust the amount of displacement to apply */ + mul_v3_v3fl(delta, gso->dvec, inf); + + /* apply */ + add_v3_v3(&pt->x, delta); + + /* done */ + return true; +} + +/* ----------------------------------------------- */ +/* Pinch Brush */ + +/* Compute reference midpoint for the brush - this is what we'll be moving towards */ +static void gp_brush_calc_midpoint(tGP_BrushEditData *gso) +{ + if (gso->sa->spacetype == SPACE_VIEW3D) { + /* Convert mouse position to 3D space + * See: gpencil_paint.c :: gp_stroke_convertcoords() + */ + View3D *v3d = gso->sa->spacedata.first; + RegionView3D *rv3d = gso->ar->regiondata; + float *rvec = ED_view3d_cursor3d_get(gso->scene, v3d); + float zfac = ED_view3d_calc_zfac(rv3d, rvec, NULL); + + float mval_f[2] = {UNPACK2(gso->mval)}; + float mval_prj[2]; + float dvec[3]; + + + if (ED_view3d_project_float_global(gso->ar, rvec, mval_prj, V3D_PROJ_TEST_NOP) == V3D_PROJ_RET_OK) { + sub_v2_v2v2(mval_f, mval_prj, mval_f); + ED_view3d_win_to_delta(gso->ar, mval_f, dvec, zfac); + sub_v3_v3v3(gso->dvec, rvec, dvec); + } + else { + zero_v3(gso->dvec); + } + } + else { + /* Just 2D coordinates */ + // XXX: fix View2D offsets later + gso->dvec[0] = (float)gso->mval[0]; + gso->dvec[1] = (float)gso->mval[1]; + gso->dvec[2] = 0.0f; + } +} + +/* Shrink distance between midpoint and this point... */ +static bool gp_brush_pinch_apply(tGP_BrushEditData *gso, bGPDstroke *gps, int i, + const int radius, const int co[2]) +{ + bGPDspoint *pt = gps->points + i; + float fac, inf; + float vec[3]; + + /* Scale down standard influence value to get it more manageable... + * - No damping = Unmanageable at > 0.5 strength + * - Div 10 = Not enough effect + * - Div 5 = Happy medium... (by trial and error) + */ + inf = gp_brush_influence_calc(gso, radius, co) / 5.0f; + + /* 1) Make this point relative to the cursor/midpoint (dvec) */ + sub_v3_v3v3(vec, &pt->x, gso->dvec); + + /* 2) Shrink the distance by pulling the point towards the midpoint + * (0.0 = at midpoint, 1 = at edge of brush region) + * OR + * Increase the distance (if inverting the brush action!) + */ + if (gp_brush_invert_check(gso)) { + /* Inflate (inverse) */ + fac = 1.0f + (inf * inf); /* squared to temper the effect... */ + } + else { + /* Shrink (default) */ + fac = 1.0f - (inf * inf); /* squared to temper the effect... */ + } + mul_v3_fl(vec, fac); + + /* 3) Translate back to original space, with the shrinkage applied */ + add_v3_v3v3(&pt->x, gso->dvec, vec); + + /* done */ + return true; +} + +/* ----------------------------------------------- */ +/* Twist Brush - Rotate Around midpoint */ + +/* Take the screenspace coordinates of the point, rotate this around the brush midpoint, + * convert the rotated point and convert it into "data" space + */ + +static bool gp_brush_twist_apply(tGP_BrushEditData *gso, bGPDstroke *gps, int i, + const int radius, const int co[2]) +{ + bGPDspoint *pt = gps->points + i; + float tco[2], rco[2], nco[2]; + float rmat[2][2]; + float angle, inf; + + /* Angle to rotate by */ + inf = gp_brush_influence_calc(gso, radius, co); + angle = DEG2RADF(1.0f) * inf; + + if (gp_brush_invert_check(gso)) { + /* invert angle that we rotate by */ + angle *= -1; + } + + /* Express position of point relative to cursor, ready to rotate */ + tco[0] = (float)(co[0] - gso->mval[0]); + tco[1] = (float)(co[1] - gso->mval[1]); + + /* Rotate point in 2D */ + angle_to_mat2(rmat, angle); + mul_v2_m2v2(rco, rmat, tco); + + /* Convert back to screen-coordinates */ + nco[0] = rco[0] + (float)gso->mval[0]; + nco[1] = rco[1] + (float)gso->mval[1]; + +#if 0 + printf("C: %d %d | P: %d %d -> t: %f %f -> r: %f %f x %f -> %f %f\n", + gso->mval[0], gso->mval[1], co[0], co[1], + tco[0], tco[1], + rco[0], rco[1], angle, + nco[0], nco[1]); +#endif + + /* convert to dataspace */ + if (gps->flag & GP_STROKE_3DSPACE) { + /* 3D: Project to 3D space */ + if (gso->sa->spacetype == SPACE_VIEW3D) { + // XXX: this conversion process sometimes introduces noise to the data -> some parts don't seem to move at all, while others get random offsets + gp_point_xy_to_3d(&gso->gsc, gso->scene, nco, &pt->x); + } + else { + /* ERROR */ + BLI_assert("3D stroke being sculpted in non-3D view"); + } + } + else { + /* 2D: As-is */ + // XXX: v2d scaling/offset? + copy_v2_v2(&pt->x, nco); + } + + /* done */ + return true; +} + + +/* ----------------------------------------------- */ +/* Randomise Brush */ + +/* Apply some random jitter to the point */ +static bool gp_brush_randomise_apply(tGP_BrushEditData *gso, bGPDstroke *gps, int i, + const int radius, const int co[2]) +{ + bGPDspoint *pt = gps->points + i; + + /* Amount of jitter to apply depends on the distance of the point to the cursor, + * as well as the strength of the brush + */ + const float inf = gp_brush_influence_calc(gso, radius, co) / 2.0f; + const float fac = BLI_frand() * inf; + + /* Jitter is applied perpendicular to the mouse movement vector + * - We compute all effects in screenspace (since it's easier) + * and then project these to get the points/distances in + * viewspace as needed + */ + float mvec[2], svec[2], nco[2]; + + /* mouse movement in ints -> floats */ + mvec[0] = (float)(gso->mval[0] - gso->mval_prev[0]); + mvec[1] = (float)(gso->mval[1] - gso->mval_prev[1]); + + /* rotate mvec by 90 degrees... */ + svec[0] = -mvec[1]; + svec[1] = mvec[0]; + + //printf("svec = %f %f, ", svec[0], svec[1]); + + /* scale the displacement by the random displacement, and apply */ + if (BLI_frand() > 0.5f) { + mul_v2_fl(svec, -fac); + } + else { + mul_v2_fl(svec, fac); + } + + nco[0] = (float)co[0] + svec[0]; + nco[1] = (float)co[1] + svec[1]; + + //printf("%f %f (%f), nco = {%f %f}, co = %d %d\n", svec[0], svec[1], fac, nco[0], nco[1], co[0], co[1]); + + /* convert to dataspace */ + if (gps->flag & GP_STROKE_3DSPACE) { + /* 3D: Project to 3D space */ + if (gso->sa->spacetype == SPACE_VIEW3D) { + gp_point_xy_to_3d(&gso->gsc, gso->scene, nco, &pt->x); + } + else { + /* ERROR */ + BLI_assert("3D stroke being sculpted in non-3D view"); + } + } + else { + /* 2D: As-is */ + // XXX: v2d scaling/offset? + copy_v2_v2(&pt->x, nco); + } + + /* done */ + return true; +} + +/* ************************************************ */ +/* Non Callback-Based Brushes */ + +/* Clone Brush ------------------------------------- */ +/* How this brush currently works: + * - If this is start of the brush stroke, paste immediately under the cursor + * by placing the midpoint of the buffer strokes under the cursor now + * + * - Otherwise, in: + * "Stamp Mode" - Move the newly pasted strokes so that their center + * "Continuous" - Repeatedly just paste new copies for where the brush is now + */ + +/* Custom state data for clone brush */ +typedef struct tGPSB_CloneBrushData { + /* midpoint of the strokes on the clipboard */ + float buffer_midpoint[3]; + + /* number of strokes in the paste buffer (and/or to be created each time) */ + size_t totitems; + + /* for "stamp" mode, the currently pasted brushes */ + bGPDstroke **new_strokes; +} tGPSB_CloneBrushData; + +/* Initialise "clone" brush data */ +static void gp_brush_clone_init(bContext *C, tGP_BrushEditData *gso) +{ + tGPSB_CloneBrushData *data; + bGPDstroke *gps; + + /* init custom data */ + gso->customdata = data = MEM_callocN(sizeof(tGPSB_CloneBrushData), "CloneBrushData"); + + /* compute midpoint of strokes on clipboard */ + for (gps = gp_strokes_copypastebuf.first; gps; gps = gps->next) { + if (ED_gpencil_stroke_can_use(C, gps)) { + const float dfac = 1.0f / ((float)gps->totpoints); + float mid[3] = {0.0f}; + + bGPDspoint *pt; + int i; + + /* compute midpoint of this stroke */ + for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) { + float co[3]; + + mul_v3_v3fl(co, &pt->x, dfac); + add_v3_v3(mid, co); + } + + /* combine this stroke's data with the main data */ + add_v3_v3(data->buffer_midpoint, mid); + data->totitems++; + } + } + + /* Divide the midpoint by the number of strokes, to finish averaging it */ + if (data->totitems > 1) { + mul_v3_fl(data->buffer_midpoint, 1.0f / (float)data->totitems); + } + + /* Create a buffer for storing the current strokes */ + if (1 /*gso->brush->mode == GP_EDITBRUSH_CLONE_MODE_STAMP*/) { + data->new_strokes = MEM_callocN(sizeof(bGPDstroke *) * data->totitems, "cloned strokes ptr array"); + } +} + +/* Free custom data used for "clone" brush */ +static void gp_brush_clone_free(tGP_BrushEditData *gso) +{ + tGPSB_CloneBrushData *data = gso->customdata; + + /* free strokes array */ + if (data->new_strokes) { + MEM_freeN(data->new_strokes); + data->new_strokes = NULL; + } + + /* free the customdata itself */ + MEM_freeN(data); + gso->customdata = NULL; +} + +/* Create new copies of the strokes on the clipboard */ +static void gp_brush_clone_add(bContext *C, tGP_BrushEditData *gso) +{ + tGPSB_CloneBrushData *data = gso->customdata; + + Scene *scene = gso->scene; + bGPDlayer *gpl = CTX_data_active_gpencil_layer(C); + bGPDframe *gpf = gpencil_layer_getframe(gpl, CFRA, true); + bGPDstroke *gps; + + float delta[3]; + size_t strokes_added = 0; + + /* Compute amount to offset the points by */ + /* NOTE: This assumes that screenspace strokes are NOT used in the 3D view... */ + + gp_brush_calc_midpoint(gso); /* this puts the cursor location into gso->dvec */ + sub_v3_v3v3(delta, gso->dvec, data->buffer_midpoint); + + /* Copy each stroke into the layer */ + for (gps = gp_strokes_copypastebuf.first; gps; gps = gps->next) { + if (ED_gpencil_stroke_can_use(C, gps)) { + bGPDstroke *new_stroke; + bGPDspoint *pt; + int i; + + /* Make a new stroke */ + new_stroke = MEM_dupallocN(gps); + + new_stroke->points = MEM_dupallocN(gps->points); + new_stroke->next = new_stroke->prev = NULL; + + BLI_addtail(&gpf->strokes, new_stroke); + + /* Adjust all the stroke's points, so that the strokes + * get pasted relative to where the cursor is now + */ + for (i = 0, pt = new_stroke->points; i < new_stroke->totpoints; i++, pt++) { + /* assume that the delta can just be applied, and then everything works */ + add_v3_v3(&pt->x, delta); + } + + /* Store ref for later */ + if ((data->new_strokes) && (strokes_added < data->totitems)) { + data->new_strokes[strokes_added] = new_stroke; + strokes_added++; + } + } + } +} + +/* Move newly-added strokes around - "Stamp" mode of the Clone brush */ +static void gp_brush_clone_adjust(bContext *C, tGP_BrushEditData *gso) +{ + tGPSB_CloneBrushData *data = gso->customdata; + size_t snum; + + /* Compute the amount of movement to apply (overwrites dvec) */ + gp_brush_grab_calc_dvec(gso); + + /* For each of the stored strokes, apply the offset to each point */ + /* NOTE: Again this assumes that in the 3D view, we only have 3d space and not screenspace strokes... */ + for (snum = 0; snum < data->totitems; snum++) { + bGPDstroke *gps = data->new_strokes[snum]; + bGPDspoint *pt; + int i; + + for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) { + if (gso->brush->flag & GP_EDITBRUSH_FLAG_USE_FALLOFF) { + /* "Smudge" Effect when falloff is enabled */ + float delta[3] = {0.0f}; + int sco[2] = {0}; + float influence; + + /* compute influence on point */ + gp_point_to_xy(&gso->gsc, gps, pt, &sco[0], &sco[1]); + influence = gp_brush_influence_calc(gso, gso->brush->size, sco); + + /* adjust the amount of displacement to apply */ + mul_v3_v3fl(delta, gso->dvec, influence); + + /* apply */ + add_v3_v3(&pt->x, delta); + } + else { + /* Just apply the offset - All points move perfectly in sync with the cursor */ + add_v3_v3(&pt->x, gso->dvec); + } + } + } +} + +/* Entrypoint for applying "clone" brush */ +static bool gpsculpt_brush_apply_clone(bContext *C, tGP_BrushEditData *gso) +{ + /* Which "mode" are we operating in? */ + if (gso->first) { + /* Create initial clones */ + gp_brush_clone_add(C, gso); + } + else { + /* Stamp or Continous Mode */ + if (1 /*gso->brush->mode == GP_EDITBRUSH_CLONE_MODE_STAMP*/) { + /* Stamp - Proceed to translate the newly added strokes */ + gp_brush_clone_adjust(C, gso); + } + else { + /* Continuous - Just keep pasting everytime we move */ + /* TODO: The spacing of repeat should be controlled using a "stepsize" or similar property? */ + gp_brush_clone_add(C, gso); + } + } + + return true; +} + +/* ************************************************ */ +/* Cursor drawing */ + +/* Helper callback for drawing the cursor itself */ +static void gp_brush_drawcursor(bContext *C, int x, int y, void *UNUSED(customdata)) +{ + GP_EditBrush_Data *brush = gpsculpt_get_brush(CTX_data_scene(C)); + + if (brush) { + glPushMatrix(); + + glTranslatef((float)x, (float)y, 0.0f); + + /* TODO: toggle between add and remove? */ + glColor4ub(255, 255, 255, 128); + + glEnable(GL_LINE_SMOOTH); + glEnable(GL_BLEND); + + glutil_draw_lined_arc(0.0, M_PI * 2.0, brush->size, 40); + + glDisable(GL_BLEND); + glDisable(GL_LINE_SMOOTH); + + glPopMatrix(); + } +} + +/* Turn brush cursor in on/off */ +static void gpencil_toggle_brush_cursor(bContext *C, bool enable) +{ + GP_BrushEdit_Settings *gset = gpsculpt_get_settings(CTX_data_scene(C)); + + if (gset->paintcursor && !enable) { + /* clear cursor */ + WM_paint_cursor_end(CTX_wm_manager(C), gset->paintcursor); + gset->paintcursor = NULL; + } + else if (enable) { + /* enable cursor */ + gset->paintcursor = WM_paint_cursor_activate(CTX_wm_manager(C), + NULL, + gp_brush_drawcursor, NULL); + } +} + + +/* ************************************************ */ +/* Header Info for GPencil Sculpt */ + +static void gpsculpt_brush_header_set(bContext *C, tGP_BrushEditData *gso) +{ + const char *brush_name = NULL; + char str[256] = ""; + + RNA_enum_name(rna_enum_gpencil_sculpt_brush_items, gso->brush_type, &brush_name); + + BLI_snprintf(str, sizeof(str), + IFACE_("GPencil Sculpt: %s Stroke | LMB to paint | RMB/Escape to Exit" + " | Ctrl to Invert Action | Wheel Up/Down for Size " + " | Shift-Wheel Up/Down for Strength"), + (brush_name) ? brush_name : ""); + + ED_area_headerprint(CTX_wm_area(C), str); +} + +/* ************************************************ */ +/* Grease Pencil Sculpting Operator */ + +/* Init/Exit ----------------------------------------------- */ + +static bool gpsculpt_brush_init(bContext *C, wmOperator *op) +{ + Scene *scene = CTX_data_scene(C); + tGP_BrushEditData *gso; + + /* setup operator data */ + gso = MEM_callocN(sizeof(tGP_BrushEditData), "tGP_BrushEditData"); + op->customdata = gso; + + /* store state */ + gso->settings = gpsculpt_get_settings(scene); + gso->brush = gpsculpt_get_brush(scene); + + gso->brush_type = gso->settings->brushtype; + + + gso->is_painting = false; + gso->first = true; + + gso->gpd = ED_gpencil_data_get_active(C); + gso->cfra = INT_MAX; /* NOTE: So that first stroke will get handled in init_stroke() */ + + gso->scene = scene; + + gso->sa = CTX_wm_area(C); + gso->ar = CTX_wm_region(C); + + /* initialise custom data for brushes */ + switch (gso->brush_type) { + case GP_EDITBRUSH_TYPE_CLONE: + { + bGPDstroke *gps; + bool found = false; + + /* check that there are some usable strokes in the buffer */ + for (gps = gp_strokes_copypastebuf.first; gps; gps = gps->next) { + if (ED_gpencil_stroke_can_use(C, gps)) { + found = true; + break; + } + } + + if (found == false) { + /* STOP HERE! Nothing to paste! */ + BKE_report(op->reports, RPT_ERROR, + "Copy some strokes to the clipboard before using the Clone brush to paste copies of them"); + + MEM_freeN(gso); + op->customdata = NULL; + return false; + } + else { + /* initialise customdata */ + gp_brush_clone_init(C, gso); + } + break; + } + + case GP_EDITBRUSH_TYPE_GRAB: + { + /* initialise the cache needed for this brush */ + gso->stroke_customdata = BLI_ghash_ptr_new("GP Grab Brush - Strokes Hash"); + break; + } + + /* Others - No customdata needed */ + default: + break; + } + + + /* setup space conversions */ + gp_point_conversion_init(C, &gso->gsc); + + /* update header */ + gpsculpt_brush_header_set(C, gso); + + /* setup cursor drawing */ + WM_cursor_modal_set(CTX_wm_window(C), BC_CROSSCURSOR); + gpencil_toggle_brush_cursor(C, true); + + return true; +} + +static void gpsculpt_brush_exit(bContext *C, wmOperator *op) +{ + tGP_BrushEditData *gso = op->customdata; + wmWindow *win = CTX_wm_window(C); + + /* free brush-specific data */ + switch (gso->brush_type) { + case GP_EDITBRUSH_TYPE_GRAB: + { + /* Free per-stroke customdata + * - Keys don't need to be freed, as those are the strokes + * - Values assigned to those keys do, as they are custom structs + */ + BLI_ghash_free(gso->stroke_customdata, NULL, gp_brush_grab_stroke_free); + break; + } + + case GP_EDITBRUSH_TYPE_CLONE: + { + /* Free customdata */ + gp_brush_clone_free(gso); + break; + } + + default: + break; + } + + /* unregister timer (only used for realtime) */ + if (gso->timer) { + WM_event_remove_timer(CTX_wm_manager(C), win, gso->timer); + } + + /* disable cursor and headerprints */ + ED_area_headerprint(CTX_wm_area(C), NULL); + WM_cursor_modal_restore(win); + gpencil_toggle_brush_cursor(C, false); + + /* free operator data */ + MEM_freeN(gso); + op->customdata = NULL; +} + +/* poll callback for stroke sculpting operator(s) */ +static int gpsculpt_brush_poll(bContext *C) +{ + /* NOTE: this is a bit slower, but is the most accurate... */ + return CTX_DATA_COUNT(C, editable_gpencil_strokes) != 0; +} + +/* Init Sculpt Stroke ---------------------------------- */ + +static void gpsculpt_brush_init_stroke(tGP_BrushEditData *gso) +{ + Scene *scene = gso->scene; + bGPdata *gpd = gso->gpd; + bGPDlayer *gpl; + int cfra = CFRA; + + /* only try to add a new frame if this is the first stroke, or the frame has changed */ + if ((gpd == NULL) || (cfra == gso->cfra)) + return; + + /* go through each layer, and ensure that we've got a valid frame to use */ + for (gpl = gpd->layers.first; gpl; gpl = gpl->next) { + /* only editable and visible layers are considered */ + if ((gpl->flag & (GP_LAYER_HIDE | GP_LAYER_LOCKED)) == 0 && + (gpl->actframe != NULL)) + { + bGPDframe *gpf = gpl->actframe; + + /* Make a new frame to work on if the layer's frame and the current scene frame don't match up + * - This is useful when animating as it saves that "uh-oh" moment when you realize you've + * spent too much time editing the wrong frame... + */ + // XXX: should this be allowed when framelock is enabled? + if (gpf->framenum != cfra) { + gpencil_frame_addcopy(gpl, cfra); + } + } + } + + /* save off new current frame, so that next update works fine */ + gso->cfra = cfra; +} + +/* Apply ----------------------------------------------- */ + +/* Apply brush operation to points in this stroke */ +static bool gpsculpt_brush_do_stroke(tGP_BrushEditData *gso, bGPDstroke *gps, GP_BrushApplyCb apply) +{ + GP_SpaceConversion *gsc = &gso->gsc; + rcti *rect = &gso->brush_rect; + const int radius = gso->brush->size; + + bGPDspoint *pt1, *pt2; + int pc1[2] = {0}; + int pc2[2] = {0}; + int i; + bool include_last = false; + bool changed = false; + + if (gps->totpoints == 1) { + gp_point_to_xy(gsc, gps, gps->points, &pc1[0], &pc1[1]); + + /* do boundbox check first */ + if ((!ELEM(V2D_IS_CLIPPED, pc1[0], pc1[1])) && BLI_rcti_isect_pt(rect, pc1[0], pc1[1])) { + /* only check if point is inside */ + if (len_v2v2_int(gso->mval, pc1) <= radius) { + /* apply operation to this point */ + changed = apply(gso, gps, 0, radius, pc1); + } + } + } + else { + /* Loop over the points in the stroke, checking for intersections + * - an intersection means that we touched the stroke + */ + for (i = 0; (i + 1) < gps->totpoints; i++) { + /* Get points to work with */ + pt1 = gps->points + i; + pt2 = gps->points + i + 1; + + /* Skip if neither one is selected (and we are only allowed to edit/consider selected points) */ + if (gso->settings->flag & GP_BRUSHEDIT_FLAG_SELECT_MASK) { + if (!(pt1->flag & GP_SPOINT_SELECT) && !(pt2->flag & GP_SPOINT_SELECT)) { + include_last = false; + continue; + } + } + + gp_point_to_xy(gsc, gps, pt1, &pc1[0], &pc1[1]); + gp_point_to_xy(gsc, gps, pt2, &pc2[0], &pc2[1]); + + /* Check that point segment of the boundbox of the selection stroke */ + if (((!ELEM(V2D_IS_CLIPPED, pc1[0], pc1[1])) && BLI_rcti_isect_pt(rect, pc1[0], pc1[1])) || + ((!ELEM(V2D_IS_CLIPPED, pc2[0], pc2[1])) && BLI_rcti_isect_pt(rect, pc2[0], pc2[1]))) + { + /* Check if point segment of stroke had anything to do with + * eraser region (either within stroke painted, or on its lines) + * - this assumes that linewidth is irrelevant + */ + if (gp_stroke_inside_circle(gso->mval, gso->mval_prev, radius, pc1[0], pc1[1], pc2[0], pc2[1])) { + /* Apply operation to these points */ + bool ok = false; + + /* To each point individually... */ + ok = apply(gso, gps, i, radius, pc1); + + /* Only do the second point if this is the last segment, + * and it is unlikely that the point will get handled + * otherwise. + * + * NOTE: There is a small risk here that the second point wasn't really + * actually in-range. In that case, it only got in because + * the line linking the points was! + */ + if (i + 1 == gps->totpoints - 1) { + ok |= apply(gso, gps, i + 1, radius, pc2); + include_last = false; + } + else { + include_last = true; + } + + changed |= ok; + } + else if (include_last) { + /* This case is for cases where for whatever reason the second vert (1st here) doesn't get included + * because the whole edge isn't in bounds, but it would've qualified since it did with the + * previous step (but wasn't added then, to avoid double-ups) + */ + changed |= apply(gso, gps, i, radius, pc1); + include_last = false; + } + } + } + } + + return changed; +} + +/* Perform two-pass brushes which modify the existing strokes */ +static bool gpsculpt_brush_apply_standard(bContext *C, tGP_BrushEditData *gso) +{ + bool changed = false; + + /* Calculate brush-specific data which applies equally to all points */ + switch (gso->brush_type) { + case GP_EDITBRUSH_TYPE_GRAB: /* Grab points */ + case GP_EDITBRUSH_TYPE_PUSH: /* Push points */ + { + /* calculate amount of displacement to apply */ + gp_brush_grab_calc_dvec(gso); + break; + } + + case GP_EDITBRUSH_TYPE_PINCH: /* Pinch points */ + //case GP_EDITBRUSH_TYPE_TWIST: /* Twist points around midpoint */ + { + /* calculate midpoint of the brush (in data space) */ + gp_brush_calc_midpoint(gso); + break; + } + + case GP_EDITBRUSH_TYPE_RANDOMISE: /* Random jitter */ + { + /* compute the displacement vector for the cursor (in data space) */ + gp_brush_grab_calc_dvec(gso); + break; + } + + default: + break; + } + + + /* Find visible strokes, and perform operations on those if hit */ + CTX_DATA_BEGIN(C, bGPDstroke *, gps, editable_gpencil_strokes) + { + switch (gso->brush_type) { + case GP_EDITBRUSH_TYPE_SMOOTH: /* Smooth strokes */ + { + changed |= gpsculpt_brush_do_stroke(gso, gps, gp_brush_smooth_apply); + break; + } + + case GP_EDITBRUSH_TYPE_THICKNESS: /* Adjust stroke thickness */ + { + changed |= gpsculpt_brush_do_stroke(gso, gps, gp_brush_thickness_apply); + break; + } + + case GP_EDITBRUSH_TYPE_GRAB: /* Grab points */ + { + if (gso->first) { + /* First time this brush stroke is being applied: + * 1) Prepare data buffers (init/clear) for this stroke + * 2) Use the points now under the cursor + */ + gp_brush_grab_stroke_init(gso, gps); + changed |= gpsculpt_brush_do_stroke(gso, gps, gp_brush_grab_store_points); + } + else { + /* Apply effect to the stored points */ + gp_brush_grab_apply_cached(gso, gps); + changed |= true; + } + break; + } + + case GP_EDITBRUSH_TYPE_PUSH: /* Push points */ + { + changed |= gpsculpt_brush_do_stroke(gso, gps, gp_brush_push_apply); + break; + } + + case GP_EDITBRUSH_TYPE_PINCH: /* Pinch points */ + { + changed |= gpsculpt_brush_do_stroke(gso, gps, gp_brush_pinch_apply); + break; + } + + case GP_EDITBRUSH_TYPE_TWIST: /* Twist points around midpoint */ + { + changed |= gpsculpt_brush_do_stroke(gso, gps, gp_brush_twist_apply); + break; + } + + case GP_EDITBRUSH_TYPE_RANDOMISE: /* Apply jitter */ + { + changed |= gpsculpt_brush_do_stroke(gso, gps, gp_brush_randomise_apply); + break; + } + + default: + printf("ERROR: Unknown type of GPencil Sculpt brush - %d\n", gso->brush_type); + break; + } + } + CTX_DATA_END; + + return changed; +} + +/* Calculate settings for applying brush */ +static void gpsculpt_brush_apply(bContext *C, wmOperator *op, PointerRNA *itemptr) +{ + tGP_BrushEditData *gso = op->customdata; + const int radius = gso->brush->size; + float mousef[2]; + int mouse[2]; + bool changed = false; + + /* Get latest mouse coordinates */ + RNA_float_get_array(itemptr, "mouse", mousef); + gso->mval[0] = mouse[0] = (int)(mousef[0]); + gso->mval[1] = mouse[1] = (int)(mousef[1]); + + gso->pressure = RNA_float_get(itemptr, "pressure"); + + if (RNA_boolean_get(itemptr, "pen_flip")) + gso->flag |= GP_EDITBRUSH_FLAG_INVERT; + else + gso->flag &= ~GP_EDITBRUSH_FLAG_INVERT; + + + /* Store coordinates as reference, if operator just started running */ + if (gso->first) { + gso->mval_prev[0] = gso->mval[0]; + gso->mval_prev[1] = gso->mval[1]; + gso->pressure_prev = gso->pressure; + } + + /* Update brush_rect, so that it represents the bounding rectangle of brush */ + gso->brush_rect.xmin = mouse[0] - radius; + gso->brush_rect.ymin = mouse[1] - radius; + gso->brush_rect.xmax = mouse[0] + radius; + gso->brush_rect.ymax = mouse[1] + radius; + + + /* Apply brush */ + if (gso->brush_type == GP_EDITBRUSH_TYPE_CLONE) { + changed = gpsculpt_brush_apply_clone(C, gso); + } + else { + changed = gpsculpt_brush_apply_standard(C, gso); + } + + + /* Updates */ + if (changed) { + WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL); + } + + /* Store values for next step */ + gso->mval_prev[0] = gso->mval[0]; + gso->mval_prev[1] = gso->mval[1]; + gso->pressure_prev = gso->pressure; + gso->first = false; +} + +/* Running --------------------------------------------- */ + +/* helper - a record stroke, and apply paint event */ +static void gpsculpt_brush_apply_event(bContext *C, wmOperator *op, const wmEvent *event) +{ + tGP_BrushEditData *gso = op->customdata; + PointerRNA itemptr; + float mouse[2]; + int tablet = 0; + + mouse[0] = event->mval[0] + 1; + mouse[1] = event->mval[1] + 1; + + /* fill in stroke */ + RNA_collection_add(op->ptr, "stroke", &itemptr); + + RNA_float_set_array(&itemptr, "mouse", mouse); + RNA_boolean_set(&itemptr, "pen_flip", event->ctrl != false); + RNA_boolean_set(&itemptr, "is_start", gso->first); + + /* handle pressure sensitivity (which is supplied by tablets) */ + if (event->tablet_data) { + const wmTabletData *wmtab = event->tablet_data; + float pressure = wmtab->Pressure; + + tablet = (wmtab->Active != EVT_TABLET_NONE); + + /* special exception here for too high pressure values on first touch in + * windows for some tablets: clamp the values to be sane + */ + if (tablet && (pressure >= 0.99f)) { + pressure = 1.0f; + } + RNA_float_set(&itemptr, "pressure", pressure); + } + else { + RNA_float_set(&itemptr, "pressure", 1.0f); + } + + /* apply */ + gpsculpt_brush_apply(C, op, &itemptr); +} + +/* reapply */ +static int gpsculpt_brush_exec(bContext *C, wmOperator *op) +{ + if (!gpsculpt_brush_init(C, op)) + return OPERATOR_CANCELLED; + + RNA_BEGIN(op->ptr, itemptr, "stroke") + { + gpsculpt_brush_apply(C, op, &itemptr); + } + RNA_END; + + gpsculpt_brush_exit(C, op); + + return OPERATOR_FINISHED; +} + + +/* start modal painting */ +static int gpsculpt_brush_invoke(bContext *C, wmOperator *op, const wmEvent *event) +{ + tGP_BrushEditData *gso = NULL; + const bool is_modal = RNA_boolean_get(op->ptr, "wait_for_input"); + bool needs_timer = false; + float brush_rate = 0.0f; + + /* init painting data */ + if (!gpsculpt_brush_init(C, op)) + return OPERATOR_CANCELLED; + + gso = op->customdata; + + /* initialise type-specific data (used for the entire session) */ + switch (gso->brush_type) { + /* Brushes requiring timer... */ + case GP_EDITBRUSH_TYPE_THICKNESS: + brush_rate = 0.01f; // XXX: hardcoded + needs_timer = true; + break; + + case GP_EDITBRUSH_TYPE_PINCH: + brush_rate = 0.001f; // XXX: hardcoded + needs_timer = true; + break; + + case GP_EDITBRUSH_TYPE_TWIST: + brush_rate = 0.01f; // XXX: hardcoded + needs_timer = true; + break; + + default: + break; + } + + /* register timer for increasing influence by hovering over an area */ + if (needs_timer) { + gso->timer = WM_event_add_timer(CTX_wm_manager(C), CTX_wm_window(C), TIMER, brush_rate); + } + + /* register modal handler */ + WM_event_add_modal_handler(C, op); + + /* start drawing immediately? */ + if (is_modal == false) { + ARegion *ar = CTX_wm_region(C); + + /* ensure that we'll have a new frame to draw on */ + gpsculpt_brush_init_stroke(gso); + + /* apply first dab... */ + gso->is_painting = true; + gpsculpt_brush_apply_event(C, op, event); + + /* redraw view with feedback */ + ED_region_tag_redraw(ar); + } + + return OPERATOR_RUNNING_MODAL; +} + +/* painting - handle events */ +static int gpsculpt_brush_modal(bContext *C, wmOperator *op, const wmEvent *event) +{ + tGP_BrushEditData *gso = op->customdata; + const bool is_modal = RNA_boolean_get(op->ptr, "wait_for_input"); + bool redraw_region = false; + + /* The operator can be in 2 states: Painting and Idling */ + if (gso->is_painting) { + /* Painting */ + switch (event->type) { + /* Mouse Move = Apply somewhere else */ + case MOUSEMOVE: + case INBETWEEN_MOUSEMOVE: + /* apply brush effect at new position */ + gpsculpt_brush_apply_event(C, op, event); + + /* force redraw, so that the cursor will at least be valid */ + redraw_region = true; + break; + + /* Timer Tick - Only if this was our own timer */ + case TIMER: + if (event->customdata == gso->timer) { + gso->timerTick = true; + gpsculpt_brush_apply_event(C, op, event); + gso->timerTick = false; + } + break; + + /* Painting mbut release = Stop painting (back to idle) */ + case LEFTMOUSE: + //BLI_assert(event->val == KM_RELEASE); + if (is_modal) { + /* go back to idling... */ + gso->is_painting = false; + } + else { + /* end sculpt session, since we're not modal */ + gso->is_painting = false; + + gpsculpt_brush_exit(C, op); + return OPERATOR_FINISHED; + } + break; + + /* Abort painting if any of the usual things are tried */ + case MIDDLEMOUSE: + case RIGHTMOUSE: + case ESCKEY: + gpsculpt_brush_exit(C, op); + return OPERATOR_FINISHED; + } + } + else { + /* Idling */ + BLI_assert(is_modal == true); + + switch (event->type) { + /* Painting mbut press = Start painting (switch to painting state) */ + case LEFTMOUSE: + /* do initial "click" apply */ + gso->is_painting = true; + gso->first = true; + + gpsculpt_brush_init_stroke(gso); + gpsculpt_brush_apply_event(C, op, event); + break; + + /* Exit modal operator, based on the "standard" ops */ + case RIGHTMOUSE: + case ESCKEY: + gpsculpt_brush_exit(C, op); + return OPERATOR_FINISHED; + + /* MMB is often used for view manipulations */ + case MIDDLEMOUSE: + return OPERATOR_PASS_THROUGH; + + /* Mouse movements should update the brush cursor - Just redraw the active region */ + case MOUSEMOVE: + case INBETWEEN_MOUSEMOVE: + redraw_region = true; + break; + + /* Adjust brush settings */ + /* FIXME: Step increments and modifier keys are hardcoded here! */ + case WHEELUPMOUSE: + case PADPLUSKEY: + if (event->shift) { + /* increase strength */ + gso->brush->strength += 0.05f; + CLAMP_MAX(gso->brush->strength, 1.0f); + } + else { + /* increase brush size */ + gso->brush->size += 3; + CLAMP_MAX(gso->brush->size, 300); + } + + redraw_region = true; + break; + + case WHEELDOWNMOUSE: + case PADMINUS: + if (event->shift) { + /* decrease strength */ + gso->brush->strength -= 0.05f; + CLAMP_MIN(gso->brush->strength, 0.0f); + } + else { + /* decrease brush size */ + gso->brush->size -= 3; + CLAMP_MIN(gso->brush->size, 1); + } + + redraw_region = true; + break; + + /* Change Frame - Allowed */ + case LEFTARROWKEY: + case RIGHTARROWKEY: + case UPARROWKEY: + case DOWNARROWKEY: + return OPERATOR_PASS_THROUGH; + + /* Unhandled event */ + default: + break; + } + } + + /* Redraw region? */ + if (redraw_region) { + ARegion *ar = CTX_wm_region(C); + ED_region_tag_redraw(ar); + } + + return OPERATOR_RUNNING_MODAL; +} + + +/* Operator --------------------------------------------- */ + +void GPENCIL_OT_brush_paint(wmOperatorType *ot) +{ + PropertyRNA *prop; + + /* identifiers */ + ot->name = "Stroke Sculpt"; + ot->idname = "GPENCIL_OT_brush_paint"; + ot->description = "Apply tweaks to strokes by painting over the strokes"; // XXX + + /* api callbacks */ + ot->exec = gpsculpt_brush_exec; + ot->invoke = gpsculpt_brush_invoke; + ot->modal = gpsculpt_brush_modal; + ot->cancel = gpsculpt_brush_exit; + ot->poll = gpsculpt_brush_poll; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO | OPTYPE_BLOCKING; + + /* properties */ + RNA_def_collection_runtime(ot->srna, "stroke", &RNA_OperatorStrokeElement, "Stroke", ""); + + prop = RNA_def_boolean(ot->srna, "wait_for_input", true, "Wait for Input", + "Enter a mini 'sculpt-mode' if enabled, otherwise, exit after drawing a single stroke"); + RNA_def_property_flag(prop, PROP_SKIP_SAVE); +} + +/* ************************************************ */ diff --git a/source/blender/editors/gpencil/gpencil_convert.c b/source/blender/editors/gpencil/gpencil_convert.c index e8d73eaffdf..ab02000a200 100644 --- a/source/blender/editors/gpencil/gpencil_convert.c +++ b/source/blender/editors/gpencil/gpencil_convert.c @@ -77,6 +77,7 @@ #include "RNA_access.h" #include "RNA_define.h" +#include "UI_resources.h" #include "UI_view2d.h" #include "ED_gpencil.h" @@ -106,9 +107,9 @@ enum { /* RNA enum define */ static EnumPropertyItem prop_gpencil_convertmodes[] = { - {GP_STROKECONVERT_PATH, "PATH", 0, "Path", ""}, - {GP_STROKECONVERT_CURVE, "CURVE", 0, "Bezier Curve", ""}, - {GP_STROKECONVERT_POLY, "POLY", 0, "Polygon Curve", ""}, + {GP_STROKECONVERT_PATH, "PATH", ICON_CURVE_PATH, "Path", "Animation path"}, + {GP_STROKECONVERT_CURVE, "CURVE", ICON_CURVE_BEZCURVE, "Bezier Curve", "Smooth Bezier curve"}, + {GP_STROKECONVERT_POLY, "POLY", ICON_MESH_DATA, "Polygon Curve", "Bezier curve with straight-line segments (vector handles)"}, {0, NULL, 0, NULL, NULL} }; diff --git a/source/blender/editors/gpencil/gpencil_data.c b/source/blender/editors/gpencil/gpencil_data.c index de966776645..4f03a53e736 100644 --- a/source/blender/editors/gpencil/gpencil_data.c +++ b/source/blender/editors/gpencil/gpencil_data.c @@ -59,12 +59,14 @@ #include "BKE_screen.h" #include "UI_interface.h" +#include "UI_resources.h" #include "WM_api.h" #include "WM_types.h" #include "RNA_access.h" #include "RNA_define.h" +#include "RNA_enum_types.h" #include "ED_gpencil.h" @@ -444,4 +446,220 @@ void GPENCIL_OT_reveal(wmOperatorType *ot) ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; } +/* ***************** Lock/Unlock All Layers ************************ */ + +static int gp_lock_all_exec(bContext *C, wmOperator *UNUSED(op)) +{ + bGPdata *gpd = ED_gpencil_data_get_active(C); + bGPDlayer *gpl; + + /* sanity checks */ + if (gpd == NULL) + return OPERATOR_CANCELLED; + + /* make all layers non-editable */ + for (gpl = gpd->layers.first; gpl; gpl = gpl->next) { + gpl->flag |= GP_LAYER_LOCKED; + } + + /* notifiers */ + WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL); + + return OPERATOR_FINISHED; +} + +void GPENCIL_OT_lock_all(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Lock All Layers"; + ot->idname = "GPENCIL_OT_lock_all"; + ot->description = "Lock all Grease Pencil layers to prevent them from being accidentally modified"; + + /* callbacks */ + ot->exec = gp_lock_all_exec; + ot->poll = gp_reveal_poll; /* XXX: could use dedicated poll later */ + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; +} + +/* -------------------------- */ + +static int gp_unlock_all_exec(bContext *C, wmOperator *UNUSED(op)) +{ + bGPdata *gpd = ED_gpencil_data_get_active(C); + bGPDlayer *gpl; + + /* sanity checks */ + if (gpd == NULL) + return OPERATOR_CANCELLED; + + /* make all layers editable again */ + for (gpl = gpd->layers.first; gpl; gpl = gpl->next) { + gpl->flag &= ~GP_LAYER_LOCKED; + } + + /* notifiers */ + WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL); + + return OPERATOR_FINISHED; +} + +void GPENCIL_OT_unlock_all(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Unlock All Layers"; + ot->idname = "GPENCIL_OT_unlock_all"; + ot->description = "unlock all Grease Pencil layers so that they can be edited"; + + /* callbacks */ + ot->exec = gp_unlock_all_exec; + ot->poll = gp_reveal_poll; /* XXX: could use dedicated poll later */ + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; +} + +/* ********************** Isolate Layer **************************** */ + +static int gp_isolate_layer_exec(bContext *C, wmOperator *op) +{ + bGPdata *gpd = ED_gpencil_data_get_active(C); + bGPDlayer *layer = gpencil_layer_getactive(gpd); + bGPDlayer *gpl; + int flags = GP_LAYER_LOCKED; + bool isolate = false; + + if (RNA_boolean_get(op->ptr, "affect_visibility")) + flags |= GP_LAYER_HIDE; + + if (ELEM(NULL, gpd, layer)) { + BKE_report(op->reports, RPT_ERROR, "No active layer to isolate"); + return OPERATOR_CANCELLED; + } + + /* Test whether to isolate or clear all flags */ + for (gpl = gpd->layers.first; gpl; gpl = gpl->next) { + /* Skip if this is the active layer */ + if (gpl == layer) + continue; + + /* If the flags aren't set, that means that the layer is + * not alone, so we have some layers to isolate still + */ + if ((gpl->flag & flags) == 0) { + isolate = true; + break; + } + } + + /* Set/Clear flags as appropriate */ + /* TODO: Include onionskinning on this list? */ + if (isolate) { + /* Set flags on all "other" layers */ + for (gpl = gpd->layers.first; gpl; gpl = gpl->next) { + if (gpl == layer) + continue; + else + gpl->flag |= flags; + } + } + else { + /* Clear flags - Restore everything else */ + for (gpl = gpd->layers.first; gpl; gpl = gpl->next) { + gpl->flag &= ~flags; + } + } + + /* notifiers */ + WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL); + + return OPERATOR_FINISHED; +} + +void GPENCIL_OT_layer_isolate(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Isolate Layer"; + ot->idname = "GPENCIL_OT_layer_isolate"; + ot->description = "Toggle whether the active layer is the only one that can be edited and/or visible"; + + /* callbacks */ + ot->exec = gp_isolate_layer_exec; + ot->poll = gp_active_layer_poll; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + /* properties */ + RNA_def_boolean(ot->srna, "affect_visibility", false, "Affect Visibility", + "In addition to toggling the editability, also affect the visibility"); +} + +/* ********************** Change Layer ***************************** */ + +static int gp_layer_change_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(evt)) +{ + uiPopupMenu *pup; + uiLayout *layout; + + /* call the menu, which will call this operator again, hence the canceled */ + pup = UI_popup_menu_begin(C, op->type->name, ICON_NONE); + layout = UI_popup_menu_layout(pup); + uiItemsEnumO(layout, "GPENCIL_OT_layer_change", "layer"); + UI_popup_menu_end(C, pup); + + return OPERATOR_INTERFACE; +} + +static int gp_layer_change_exec(bContext *C, wmOperator *op) +{ + bGPdata *gpd = CTX_data_gpencil_data(C); + bGPDlayer *gpl = NULL; + int layer_num = RNA_enum_get(op->ptr, "layer"); + + /* Get layer or create new one */ + if (layer_num == -1) { + /* Create layer */ + gpl = gpencil_layer_addnew(gpd, DATA_("GP_Layer"), true); + } + else { + /* Try to get layer */ + gpl = BLI_findlink(&gpd->layers, layer_num); + + if (gpl == NULL) { + BKE_reportf(op->reports, RPT_ERROR, "Cannot change to non-existent layer (index = %d)", layer_num); + return OPERATOR_CANCELLED; + } + } + + /* Set active layer */ + gpencil_layer_setactive(gpd, gpl); + + /* updates */ + WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL); + + return OPERATOR_FINISHED; +} + +void GPENCIL_OT_layer_change(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Change Layer"; + ot->idname = "GPENCIL_OT_layer_change"; + ot->description = "Change active Grease Pencil layer"; + + /* callbacks */ + ot->invoke = gp_layer_change_invoke; + ot->exec = gp_layer_change_exec; + ot->poll = gp_active_layer_poll; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + /* gp layer to use (dynamic enum) */ + ot->prop = RNA_def_enum(ot->srna, "layer", DummyRNA_DEFAULT_items, 0, "Grease Pencil Layer", ""); + RNA_def_enum_funcs(ot->prop, ED_gpencil_layers_with_new_enum_itemf); +} + /* ************************************************ */ diff --git a/source/blender/editors/gpencil/gpencil_edit.c b/source/blender/editors/gpencil/gpencil_edit.c index 5c37a0a5b60..9c832ebe20c 100644 --- a/source/blender/editors/gpencil/gpencil_edit.c +++ b/source/blender/editors/gpencil/gpencil_edit.c @@ -44,6 +44,7 @@ #include "BLT_translation.h" +#include "DNA_object_types.h" #include "DNA_scene_types.h" #include "DNA_screen_types.h" #include "DNA_space_types.h" @@ -58,20 +59,62 @@ #include "BKE_screen.h" #include "UI_interface.h" +#include "UI_resources.h" #include "WM_api.h" #include "WM_types.h" #include "RNA_access.h" #include "RNA_define.h" +#include "RNA_enum_types.h" #include "UI_view2d.h" #include "ED_gpencil.h" +#include "ED_object.h" #include "ED_view3d.h" #include "gpencil_intern.h" +/* ************************************************ */ +/* Stroke Edit Mode Management */ + +static int gpencil_editmode_toggle_poll(bContext *C) +{ + return ED_gpencil_data_get_active(C) != NULL; +} + +static int gpencil_editmode_toggle_exec(bContext *C, wmOperator *op) +{ + bGPdata *gpd = ED_gpencil_data_get_active(C); + + if (gpd == NULL) + return OPERATOR_CANCELLED; + + /* Just toggle editmode flag... */ + gpd->flag ^= GP_DATA_STROKE_EDITMODE; + + WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | ND_GPENCIL_EDITMODE, NULL); + WM_event_add_notifier(C, NC_SCENE | ND_MODE, NULL); + + return OPERATOR_FINISHED; +} + +void GPENCIL_OT_editmode_toggle(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Strokes Edit Mode Toggle"; + ot->idname = "GPENCIL_OT_editmode_toggle"; + ot->description = "Enter/Exit edit mode for Grease Pencil strokes"; + + /* callbacks */ + ot->exec = gpencil_editmode_toggle_exec; + ot->poll = gpencil_editmode_toggle_poll; + + /* flags */ + ot->flag = OPTYPE_UNDO | OPTYPE_REGISTER; +} + /* ************************************************ */ /* Stroke Editing Operators */ @@ -233,7 +276,8 @@ void GPENCIL_OT_duplicate(wmOperatorType *ot) */ /* list of bGPDstroke instances */ -static ListBase gp_strokes_copypastebuf = {NULL, NULL}; +/* NOTE: is exposed within the editors/gpencil module so that other tools can use it too */ +ListBase gp_strokes_copypastebuf = {NULL, NULL}; /* Free copy/paste buffer data */ void ED_gpencil_strokes_copybuf_free(void) @@ -339,7 +383,7 @@ static int gp_strokes_paste_exec(bContext *C, wmOperator *op) BKE_report(op->reports, RPT_ERROR, "No Grease Pencil data"); return OPERATOR_CANCELLED; } - else if (gp_strokes_copypastebuf.first == NULL) { + else if (BLI_listbase_is_empty(&gp_strokes_copypastebuf)) { BKE_report(op->reports, RPT_ERROR, "No strokes to paste, select and copy some points before trying again"); return OPERATOR_CANCELLED; } @@ -434,6 +478,110 @@ void GPENCIL_OT_paste(wmOperatorType *ot) ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; } +/* ******************* Move To Layer ****************************** */ + +static int gp_move_to_layer_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(evt)) +{ + uiPopupMenu *pup; + uiLayout *layout; + + /* call the menu, which will call this operator again, hence the canceled */ + pup = UI_popup_menu_begin(C, op->type->name, ICON_NONE); + layout = UI_popup_menu_layout(pup); + uiItemsEnumO(layout, "GPENCIL_OT_move_to_layer", "layer"); + UI_popup_menu_end(C, pup); + + return OPERATOR_INTERFACE; +} + +// FIXME: allow moving partial strokes +static int gp_move_to_layer_exec(bContext *C, wmOperator *op) +{ + bGPdata *gpd = CTX_data_gpencil_data(C); + bGPDlayer *target_layer = NULL; + ListBase strokes = {NULL, NULL}; + int layer_num = RNA_enum_get(op->ptr, "layer"); + + /* Get layer or create new one */ + if (layer_num == -1) { + /* Create layer */ + target_layer = gpencil_layer_addnew(gpd, DATA_("GP_Layer"), true); + } + else { + /* Try to get layer */ + target_layer = BLI_findlink(&gpd->layers, layer_num); + + if (target_layer == NULL) { + BKE_reportf(op->reports, RPT_ERROR, "There is no layer number %d", layer_num); + return OPERATOR_CANCELLED; + } + } + + /* Extract all strokes to move to this layer + * NOTE: We need to do this in a two-pass system to avoid conflicts with strokes + * getting repeatedly moved + */ + CTX_DATA_BEGIN(C, bGPDlayer *, gpl, editable_gpencil_layers) + { + bGPDframe *gpf = gpl->actframe; + bGPDstroke *gps, *gpsn; + + /* skip if no frame with strokes, or if this is the layer we're moving strokes to */ + if ((gpl == target_layer) || (gpf == NULL)) + continue; + + /* make copies of selected strokes, and deselect these once we're done */ + for (gps = gpf->strokes.first; gps; gps = gpsn) { + gpsn = gps->next; + + /* skip strokes that are invalid for current view */ + if (ED_gpencil_stroke_can_use(C, gps) == false) + continue; + + /* TODO: Don't just move entire strokes - instead, only copy the selected portions... */ + if (gps->flag & GP_STROKE_SELECT) { + BLI_remlink(&gpf->strokes, gps); + BLI_addtail(&strokes, gps); + } + } + } + CTX_DATA_END; + + /* Paste them all in one go */ + if (strokes.first) { + Scene *scene = CTX_data_scene(C); + bGPDframe *gpf = gpencil_layer_getframe(target_layer, CFRA, true); + + BLI_movelisttolist(&gpf->strokes, &strokes); + BLI_assert((strokes.first == strokes.last) && (strokes.first == NULL)); + } + + /* updates */ + WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL); + + return OPERATOR_FINISHED; +} + +void GPENCIL_OT_move_to_layer(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Move Strokes to Layer"; + ot->idname = "GPENCIL_OT_move_to_layer"; + ot->description = "Move selected strokes to another layer"; // XXX: allow moving individual points too? + + /* callbacks */ + ot->invoke = gp_move_to_layer_invoke; + ot->exec = gp_move_to_layer_exec; + ot->poll = gp_stroke_edit_poll; // XXX? + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + /* gp layer to use (dynamic enum) */ + ot->prop = RNA_def_enum(ot->srna, "layer", DummyRNA_DEFAULT_items, 0, "Grease Pencil Layer", ""); + RNA_def_enum_funcs(ot->prop, ED_gpencil_layers_with_new_enum_itemf); +} + /* ******************* Delete Active Frame ************************ */ static int gp_actframe_delete_poll(bContext *C) @@ -497,6 +645,7 @@ typedef enum eGP_DeleteMode { GP_DELETEOP_FRAME = 2, } eGP_DeleteMode; +/* ----------------------------------- */ /* Delete selected strokes */ static int gp_delete_selected_strokes(bContext *C) @@ -540,6 +689,8 @@ static int gp_delete_selected_strokes(bContext *C) } } +/* ----------------------------------- */ + /* Delete selected points but keep the stroke */ static int gp_dissolve_selected_points(bContext *C) { @@ -621,6 +772,124 @@ static int gp_dissolve_selected_points(bContext *C) } } +/* ----------------------------------- */ + +/* Temp data for storing information about an "island" of points + * that should be kept when splitting up a stroke. Used in: + * gp_stroke_delete_tagged_points() + */ +typedef struct tGPDeleteIsland { + int start_idx; + int end_idx; +} tGPDeleteIsland; + + +/* Split the given stroke into several new strokes, partitioning + * it based on whether the stroke points have a particular flag + * is set (e.g. "GP_SPOINT_SELECT" in most cases, but not always) + * + * The algorithm used here is as follows: + * 1) We firstly identify the number of "islands" of non-tagged points + * which will all end up being in new strokes. + * - In the most extreme case (i.e. every other vert is a 1-vert island), + * we have at most n / 2 islands + * - Once we start having larger islands than that, the number required + * becomes much less + * 2) Each island gets converted to a new stroke + */ +void gp_stroke_delete_tagged_points(bGPDframe *gpf, bGPDstroke *gps, bGPDstroke *next_stroke, int tag_flags) +{ + tGPDeleteIsland *islands = MEM_callocN(sizeof(tGPDeleteIsland) * (gps->totpoints + 1) / 2, "gp_point_islands"); + bool in_island = false; + int num_islands = 0; + + bGPDspoint *pt; + int i; + + /* First Pass: Identify start/end of islands */ + for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) { + if (pt->flag & tag_flags) { + /* selected - stop accumulating to island */ + in_island = false; + } + else { + /* unselected - start of a new island? */ + int idx; + + if (in_island) { + /* extend existing island */ + idx = num_islands - 1; + islands[idx].end_idx = i; + } + else { + /* start of new island */ + in_island = true; + num_islands++; + + idx = num_islands - 1; + islands[idx].start_idx = islands[idx].end_idx = i; + } + } + } + + /* Watch out for special case where No islands = All points selected = Delete Stroke only */ + if (num_islands) { + /* there are islands, so create a series of new strokes, adding them before the "next" stroke */ + int idx; + + /* Create each new stroke... */ + for (idx = 0; idx < num_islands; idx++) { + tGPDeleteIsland *island = &islands[idx]; + bGPDstroke *new_stroke = MEM_dupallocN(gps); + + /* Compute new buffer size (+ 1 needed as the endpoint index is "inclusive") */ + new_stroke->totpoints = island->end_idx - island->start_idx + 1; + new_stroke->points = MEM_callocN(sizeof(bGPDspoint) * new_stroke->totpoints, "gp delete stroke fragment"); + + /* Copy over the relevant points */ + memcpy(new_stroke->points, gps->points + island->start_idx, sizeof(bGPDspoint) * new_stroke->totpoints); + + + /* Each island corresponds to a new stroke. We must adjust the + * timings of these new strokes: + * + * Each point's timing data is a delta from stroke's inittime, so as we erase some points from + * the start of the stroke, we have to offset this inittime and all remaing points' delta values. + * This way we get a new stroke with exactly the same timing as if user had started drawing from + * the first non-removed point... + */ + { + bGPDspoint *pts; + float delta = gps->points[island->start_idx].time; + int j; + + new_stroke->inittime += (double)delta; + + pts = new_stroke->points; + for (j = 0; j < new_stroke->totpoints; j++, pts++) { + pts->time -= delta; + } + } + + /* Add new stroke to the frame */ + if (next_stroke) { + BLI_insertlinkbefore(&gpf->strokes, next_stroke, new_stroke); + } + else { + BLI_addtail(&gpf->strokes, new_stroke); + } + } + } + + /* free islands */ + MEM_freeN(islands); + + /* Delete the old stroke */ + MEM_freeN(gps->points); + BLI_freelinkN(&gpf->strokes, gps); +} + + /* Split selected strokes into segments, splitting on selected points */ static int gp_delete_selected_points(bContext *C) { @@ -644,89 +913,11 @@ static int gp_delete_selected_points(bContext *C) if (gps->flag & GP_STROKE_SELECT) { - bGPDspoint *pt; - int i; - - /* The algorithm used here is as follows: - * 1) We firstly identify the number of "islands" of non-selected points - * which will all end up being in new strokes. - * - In the most extreme case (i.e. every other vert is a 1-vert island), - * we have at most n / 2 islands - * - Once we start having larger islands than that, the number required - * becomes much less - * 2) Each island gets converted to a new stroke - */ - typedef struct tGPDeleteIsland { - int start_idx; - int end_idx; - } tGPDeleteIsland; - - tGPDeleteIsland *islands = MEM_callocN(sizeof(tGPDeleteIsland) * (gps->totpoints + 1) / 2, "gp_point_islands"); - bool in_island = false; - int num_islands = 0; - - /* First Pass: Identify start/end of islands */ - for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) { - if (pt->flag & GP_SPOINT_SELECT) { - /* selected - stop accumulating to island */ - in_island = false; - } - else { - /* unselected - start of a new island? */ - int idx; - - if (in_island) { - /* extend existing island */ - idx = num_islands - 1; - islands[idx].end_idx = i; - } - else { - /* start of new island */ - in_island = true; - num_islands++; - - idx = num_islands - 1; - islands[idx].start_idx = islands[idx].end_idx = i; - } - } - } - - /* Watch out for special case where No islands = All points selected = Delete Stroke only */ - if (num_islands) { - /* there are islands, so create a series of new strokes, adding them before the "next" stroke */ - int idx; - - /* deselect old stroke, since it will be used as template for the new strokes */ - gps->flag &= ~GP_STROKE_SELECT; - - /* create each new stroke... */ - for (idx = 0; idx < num_islands; idx++) { - tGPDeleteIsland *island = &islands[idx]; - bGPDstroke *new_stroke = MEM_dupallocN(gps); - - /* compute new buffer size (+ 1 needed as the endpoint index is "inclusive") */ - new_stroke->totpoints = island->end_idx - island->start_idx + 1; - new_stroke->points = MEM_callocN(sizeof(bGPDspoint) * new_stroke->totpoints, "gp delete stroke fragment"); - - /* copy over the relevant points */ - memcpy(new_stroke->points, gps->points + island->start_idx, sizeof(bGPDspoint) * new_stroke->totpoints); - - /* add new stroke to the frame */ - if (gpsn) { - BLI_insertlinkbefore(&gpf->strokes, gpsn, new_stroke); - } - else { - BLI_addtail(&gpf->strokes, new_stroke); - } - } - } - - /* free islands */ - MEM_freeN(islands); + /* deselect old stroke, since it will be used as template for the new strokes */ + gps->flag &= ~GP_STROKE_SELECT; - /* Delete the old stroke */ - MEM_freeN(gps->points); - BLI_freelinkN(&gpf->strokes, gps); + /* delete unwanted points by splitting stroke into several smaller ones */ + gp_stroke_delete_tagged_points(gpf, gps, gpsn, GP_SPOINT_SELECT); changed = true; } @@ -743,6 +934,7 @@ static int gp_delete_selected_points(bContext *C) } } +/* ----------------------------------- */ static int gp_delete_exec(bContext *C, wmOperator *op) { @@ -812,4 +1004,190 @@ void GPENCIL_OT_dissolve(wmOperatorType *ot) ot->flag = OPTYPE_UNDO | OPTYPE_REGISTER; } +/* ****************** Snapping - Strokes <-> Cursor ************************ */ + +/* Poll callback for snap operators */ +/* NOTE: For now, we only allow these in the 3D view, as other editors do not + * define a cursor or gridstep which can be used + */ +static int gp_snap_poll(bContext *C) +{ + bGPdata *gpd = CTX_data_gpencil_data(C); + ScrArea *sa = CTX_wm_area(C); + + return (gpd != NULL) && ((sa != NULL) && (sa->spacetype == SPACE_VIEW3D)); +} + +/* --------------------------------- */ + +static int gp_snap_to_grid(bContext *C, wmOperator *op) +{ + RegionView3D *rv3d = CTX_wm_region_data(C); + float gridf = rv3d->gridview; + + CTX_DATA_BEGIN(C, bGPDstroke *, gps, editable_gpencil_strokes) + { + bGPDspoint *pt; + int i; + + // TOOD: if entire stroke is selected, offset entire stroke by same amount? + + for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) { + /* only if point is selected.. */ + if (pt->flag & GP_SPOINT_SELECT) { + pt->x = gridf * floorf(0.5f + pt->x / gridf); + pt->y = gridf * floorf(0.5f + pt->y / gridf); + pt->z = gridf * floorf(0.5f + pt->z / gridf); + } + } + } + CTX_DATA_END; + + WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL); + return OPERATOR_FINISHED; +} + +void GPENCIL_OT_snap_to_grid(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Snap Selection to Grid"; + ot->idname = "GPENCIL_OT_snap_to_grid"; + ot->description = "Snap selected points to the nearest grid points"; + + /* callbacks */ + ot->exec = gp_snap_to_grid; + ot->poll = gp_snap_poll; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; +} + +/* ------------------------------- */ + +static int gp_snap_to_cursor(bContext *C, wmOperator *op) +{ + Scene *scene = CTX_data_scene(C); + View3D *v3d = CTX_wm_view3d(C); + + const bool use_offset = RNA_boolean_get(op->ptr, "use_offset"); + const float *cursor_global = ED_view3d_cursor3d_get(scene, v3d); + + CTX_DATA_BEGIN(C, bGPDstroke *, gps, editable_gpencil_strokes) + { + bGPDspoint *pt; + int i; + + /* only continue if this stroke is selected (editable doesn't guarantee this)... */ + if ((gps->flag & GP_STROKE_SELECT) == 0) + continue; + + if (use_offset) { + float offset[3]; + + /* compute offset from first point of stroke to cursor */ + /* TODO: Allow using midpoint instead? */ + sub_v3_v3v3(offset, cursor_global, &gps->points->x); + + /* apply offset to all points in the stroke */ + for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) { + add_v3_v3(&pt->x, offset); + } + } + else { + /* affect each selected point */ + for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) { + if (pt->flag & GP_SPOINT_SELECT) { + copy_v3_v3(&pt->x, cursor_global); + } + } + } + } + CTX_DATA_END; + + WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL); + return OPERATOR_FINISHED; +} + +void GPENCIL_OT_snap_to_cursor(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Snap Selection to Cursor"; + ot->idname = "GPENCIL_OT_snap_to_cursor"; + ot->description = "Snap selected points/strokes to the cursor"; + + /* callbacks */ + ot->exec = gp_snap_to_cursor; + ot->poll = gp_snap_poll; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + /* props */ + ot->prop = RNA_def_boolean(ot->srna, "use_offset", true, "With Offset", + "Offset the entire stroke instead of selected points only"); +} + +/* ------------------------------- */ + +static int gp_snap_cursor_to_sel(bContext *C, wmOperator *op) +{ + Scene *scene = CTX_data_scene(C); + View3D *v3d = CTX_wm_view3d(C); + + float *cursor = ED_view3d_cursor3d_get(scene, v3d); + float centroid[3] = {0.0f}; + float min[3], max[3]; + size_t count = 0; + + INIT_MINMAX(min, max); + + /* calculate midpoints from selected points */ + CTX_DATA_BEGIN(C, bGPDstroke *, gps, editable_gpencil_strokes) + { + bGPDspoint *pt; + int i; + + /* only continue if this stroke is selected (editable doesn't guarantee this)... */ + if ((gps->flag & GP_STROKE_SELECT) == 0) + continue; + + for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) { + if (pt->flag & GP_SPOINT_SELECT) { + add_v3_v3(centroid, &pt->x); + minmax_v3v3_v3(min, max, &pt->x); + count++; + } + } + } + CTX_DATA_END; + + if (v3d->around == V3D_AROUND_CENTER_MEAN) { + mul_v3_fl(centroid, 1.0f / (float)count); + copy_v3_v3(cursor, centroid); + } + else { + mid_v3_v3v3(cursor, min, max); + } + + + WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL); + return OPERATOR_FINISHED; +} + +void GPENCIL_OT_snap_cursor_to_selected(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Snap Cursor to Selected Points"; + ot->idname = "GPENCIL_OT_snap_cursor_to_selected"; + ot->description = "Snap cursor to center of selected points"; + + /* callbacks */ + ot->exec = gp_snap_cursor_to_sel; + ot->poll = gp_snap_poll; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; +} + + /* ************************************************ */ diff --git a/source/blender/editors/gpencil/gpencil_intern.h b/source/blender/editors/gpencil/gpencil_intern.h index 9c0b6f785d1..d9a6441c00e 100644 --- a/source/blender/editors/gpencil/gpencil_intern.h +++ b/source/blender/editors/gpencil/gpencil_intern.h @@ -44,6 +44,10 @@ struct ARegion; struct View2D; struct wmOperatorType; +struct PointerRNA; +struct PropertyRNA; +struct EnumPropertyItem; + /* ***************************************************** */ /* Internal API */ @@ -96,12 +100,37 @@ void gp_point_conversion_init(struct bContext *C, GP_SpaceConversion *r_gsc); void gp_point_to_xy(GP_SpaceConversion *settings, struct bGPDstroke *gps, struct bGPDspoint *pt, int *r_x, int *r_y); +/** + * Convert a screenspace point to a 3D Grease Pencil coordinate. + * + * For use with editing tools where it is easier to perform the operations in 2D, + * and then later convert the transformed points back to 3D. + * + * \param screeN_co The screenspace 2D coordinates to convert to + * \param[out] r_out The resulting 3D coordinates of the input point + */ +bool gp_point_xy_to_3d(GP_SpaceConversion *gsc, struct Scene *scene, const float screen_co[2], float r_out[3]); + /* Poll Callbacks ------------------------------------ */ /* gpencil_utils.c */ int gp_add_poll(struct bContext *C); int gp_active_layer_poll(struct bContext *C); +/* Copy/Paste Buffer --------------------------------- */ +/* gpencil_edit.c */ + +extern ListBase gp_strokes_copypastebuf; + +/* Stroke Editing ------------------------------------ */ + +void gp_stroke_delete_tagged_points(bGPDframe *gpf, bGPDstroke *gps, bGPDstroke *next_stroke, int tag_flags); + +/* Layers Enums -------------------------------------- */ + +struct EnumPropertyItem *ED_gpencil_layers_enum_itemf(struct bContext *C, struct PointerRNA *ptr, struct PropertyRNA *prop, bool *r_free); +struct EnumPropertyItem *ED_gpencil_layers_with_new_enum_itemf(struct bContext *C, struct PointerRNA *ptr, struct PropertyRNA *prop, bool *r_free); + /* ***************************************************** */ /* Operator Defines */ @@ -119,6 +148,8 @@ typedef enum eGPencil_PaintModes { /* stroke editing ----- */ +void GPENCIL_OT_editmode_toggle(struct wmOperatorType *ot); + void GPENCIL_OT_select(struct wmOperatorType *ot); void GPENCIL_OT_select_all(struct wmOperatorType *ot); void GPENCIL_OT_select_circle(struct wmOperatorType *ot); @@ -135,6 +166,19 @@ void GPENCIL_OT_dissolve(struct wmOperatorType *ot); void GPENCIL_OT_copy(struct wmOperatorType *ot); void GPENCIL_OT_paste(struct wmOperatorType *ot); +void GPENCIL_OT_move_to_layer(struct wmOperatorType *ot); +void GPENCIL_OT_layer_change(struct wmOperatorType *ot); + +void GPENCIL_OT_snap_to_grid(struct wmOperatorType *ot); +void GPENCIL_OT_snap_to_cursor(struct wmOperatorType *ot); +void GPENCIL_OT_snap_cursor_to_selected(struct wmOperatorType *ot); +void GPENCIL_OT_snap_cursor_to_center(struct wmOperatorType *ot); + + +/* stroke sculpting -- */ + +void GPENCIL_OT_brush_paint(struct wmOperatorType *ot); + /* buttons editing --- */ void GPENCIL_OT_data_add(struct wmOperatorType *ot); @@ -148,6 +192,11 @@ void GPENCIL_OT_layer_duplicate(struct wmOperatorType *ot); void GPENCIL_OT_hide(struct wmOperatorType *ot); void GPENCIL_OT_reveal(struct wmOperatorType *ot); +void GPENCIL_OT_lock_all(struct wmOperatorType *ot); +void GPENCIL_OT_unlock_all(struct wmOperatorType *ot); + +void GPENCIL_OT_layer_isolate(struct wmOperatorType *ot); + void GPENCIL_OT_active_frame_delete(struct wmOperatorType *ot); void GPENCIL_OT_convert(struct wmOperatorType *ot); diff --git a/source/blender/editors/gpencil/gpencil_ops.c b/source/blender/editors/gpencil/gpencil_ops.c index ab56565f4ca..25012ab6d64 100644 --- a/source/blender/editors/gpencil/gpencil_ops.c +++ b/source/blender/editors/gpencil/gpencil_ops.c @@ -79,11 +79,25 @@ static void ed_keymap_gpencil_general(wmKeyConfig *keyconf) RNA_enum_set(kmi->ptr, "mode", GP_PAINTMODE_ERASER); RNA_boolean_set(kmi->ptr, "wait_for_input", false); + + /* Tablet Mappings for Drawing ------------------ */ + /* For now, only support direct drawing using the eraser, as most users using a tablet + * may still want to use that as their primary pointing device! + */ +#if 0 + kmi = WM_keymap_add_item(keymap, "GPENCIL_OT_draw", TABLET_STYLUS, KM_PRESS, 0, 0); + RNA_enum_set(kmi->ptr, "mode", GP_PAINTMODE_DRAW); + RNA_boolean_set(kmi->ptr, "wait_for_input", false); +#endif + + kmi = WM_keymap_add_item(keymap, "GPENCIL_OT_draw", TABLET_ERASER, KM_PRESS, 0, 0); + RNA_enum_set(kmi->ptr, "mode", GP_PAINTMODE_ERASER); + RNA_boolean_set(kmi->ptr, "wait_for_input", false); + /* Viewport Tools ------------------------------- */ /* Enter EditMode */ - kmi = WM_keymap_add_item(keymap, "WM_OT_context_toggle", TABKEY, KM_PRESS, 0, DKEY); - RNA_string_set(kmi->ptr, "data_path", "gpencil_data.use_stroke_edit_mode"); + WM_keymap_add_item(keymap, "GPENCIL_OT_editmode_toggle", TABKEY, KM_PRESS, 0, DKEY); /* Pie Menu - For standard tools */ WM_keymap_add_menu_pie(keymap, "GPENCIL_PIE_tool_palette", QKEY, KM_PRESS, 0, DKEY); @@ -111,8 +125,10 @@ static void ed_keymap_gpencil_editing(wmKeyConfig *keyconf) /* ----------------------------------------------- */ /* Exit EditMode */ - kmi = WM_keymap_add_item(keymap, "WM_OT_context_toggle", TABKEY, KM_PRESS, 0, 0); - RNA_string_set(kmi->ptr, "data_path", "gpencil_data.use_stroke_edit_mode"); + WM_keymap_add_item(keymap, "GPENCIL_OT_editmode_toggle", TABKEY, KM_PRESS, 0, 0); + + /* Pie Menu - For settings/tools easy access */ + WM_keymap_add_menu_pie(keymap, "GPENCIL_PIE_sculpt", EKEY, KM_PRESS, 0, DKEY); /* Brush Settings */ /* NOTE: We cannot expose these in the standard keymap, as they will interfere with regular hotkeys @@ -185,6 +201,14 @@ static void ed_keymap_gpencil_editing(wmKeyConfig *keyconf) WM_keymap_add_item(keymap, "GPENCIL_OT_copy", CKEY, KM_PRESS, KM_OSKEY, 0); WM_keymap_add_item(keymap, "GPENCIL_OT_paste", VKEY, KM_PRESS, KM_OSKEY, 0); #endif + + /* snap */ + WM_keymap_add_menu(keymap, "GPENCIL_MT_snap", SKEY, KM_PRESS, KM_SHIFT, 0); + + + /* convert to geometry */ + WM_keymap_add_item(keymap, "GPENCIL_OT_convert", CKEY, KM_PRESS, KM_ALT, 0); + /* Show/Hide */ /* NOTE: These are available only in EditMode now, since they clash with general-purpose hotkeys */ @@ -196,35 +220,62 @@ static void ed_keymap_gpencil_editing(wmKeyConfig *keyconf) kmi = WM_keymap_add_item(keymap, "GPENCIL_OT_hide", HKEY, KM_PRESS, KM_SHIFT, 0); RNA_boolean_set(kmi->ptr, "unselected", true); + /* Isolate Layer */ + WM_keymap_add_item(keymap, "GPENCIL_OT_layer_isolate", PADASTERKEY, KM_PRESS, 0, 0); + + /* Move to Layer */ + WM_keymap_add_item(keymap, "GPENCIL_OT_move_to_layer", MKEY, KM_PRESS, 0, 0); + + + + /* Brush-Based Editing: + * EKEY + LMB = Single stroke, draw immediately + * + Other Modifiers (Ctrl/Shift) = Invert, Smooth, etc. + * + * For the modal version, use D+E -> Sculpt + */ + kmi = WM_keymap_add_item(keymap, "GPENCIL_OT_brush_paint", LEFTMOUSE, KM_PRESS, 0, EKEY); + RNA_boolean_set(kmi->ptr, "wait_for_input", false); + + kmi = WM_keymap_add_item(keymap, "GPENCIL_OT_brush_paint", LEFTMOUSE, KM_PRESS, KM_CTRL, EKEY); + RNA_boolean_set(kmi->ptr, "wait_for_input", false); + /*RNA_boolean_set(kmi->ptr, "use_invert", true);*/ + + kmi = WM_keymap_add_item(keymap, "GPENCIL_OT_brush_paint", LEFTMOUSE, KM_PRESS, KM_SHIFT, EKEY); + RNA_boolean_set(kmi->ptr, "wait_for_input", false); + /*RNA_boolean_set(kmi->ptr, "use_smooth", true);*/ + + + /* Shift-FKEY = Sculpt Strength */ + kmi = WM_keymap_add_item(keymap, "WM_OT_radial_control", FKEY, KM_PRESS, KM_SHIFT, 0); + RNA_string_set(kmi->ptr, "data_path_primary", "tool_settings.gpencil_sculpt.brush.strength"); + + /* Ctrl-FKEY = Sculpt Brush Size */ + kmi = WM_keymap_add_item(keymap, "WM_OT_radial_control", FKEY, KM_PRESS, KM_CTRL, 0); + RNA_string_set(kmi->ptr, "data_path_primary", "tool_settings.gpencil_sculpt.brush.size"); + + + /* Transform Tools */ kmi = WM_keymap_add_item(keymap, "TRANSFORM_OT_translate", GKEY, KM_PRESS, 0, 0); - RNA_boolean_set(kmi->ptr, "gpencil_strokes", true); kmi = WM_keymap_add_item(keymap, "TRANSFORM_OT_translate", EVT_TWEAK_S, KM_ANY, 0, 0); - RNA_boolean_set(kmi->ptr, "gpencil_strokes", true); kmi = WM_keymap_add_item(keymap, "TRANSFORM_OT_rotate", RKEY, KM_PRESS, 0, 0); - RNA_boolean_set(kmi->ptr, "gpencil_strokes", true); kmi = WM_keymap_add_item(keymap, "TRANSFORM_OT_resize", SKEY, KM_PRESS, 0, 0); - RNA_boolean_set(kmi->ptr, "gpencil_strokes", true); kmi = WM_keymap_add_item(keymap, "TRANSFORM_OT_mirror", MKEY, KM_PRESS, KM_CTRL, 0); - RNA_boolean_set(kmi->ptr, "gpencil_strokes", true); kmi = WM_keymap_add_item(keymap, "TRANSFORM_OT_bend", WKEY, KM_PRESS, KM_SHIFT, 0); - RNA_boolean_set(kmi->ptr, "gpencil_strokes", true); WM_keymap_add_item(keymap, "TRANSFORM_OT_tosphere", SKEY, KM_PRESS, KM_ALT | KM_SHIFT, 0); - RNA_boolean_set(kmi->ptr, "gpencil_strokes", true); WM_keymap_add_item(keymap, "TRANSFORM_OT_shear", SKEY, KM_PRESS, KM_ALT | KM_CTRL | KM_SHIFT, 0); - RNA_boolean_set(kmi->ptr, "gpencil_strokes", true); kmi = WM_keymap_add_item(keymap, "TRANSFORM_OT_transform", SKEY, KM_PRESS, KM_ALT, 0); RNA_enum_set(kmi->ptr, "mode", TFM_GPENCIL_SHRINKFATTEN); - RNA_boolean_set(kmi->ptr, "gpencil_strokes", true); /* Proportional Editing */ ED_keymap_proportional_cycle(keyconf, keymap); @@ -249,6 +300,8 @@ void ED_operatortypes_gpencil(void) /* Editing (Strokes) ------------ */ + WM_operatortype_append(GPENCIL_OT_editmode_toggle); + WM_operatortype_append(GPENCIL_OT_select); WM_operatortype_append(GPENCIL_OT_select_all); WM_operatortype_append(GPENCIL_OT_select_circle); @@ -265,6 +318,15 @@ void ED_operatortypes_gpencil(void) WM_operatortype_append(GPENCIL_OT_copy); WM_operatortype_append(GPENCIL_OT_paste); + WM_operatortype_append(GPENCIL_OT_move_to_layer); + WM_operatortype_append(GPENCIL_OT_layer_change); + + WM_operatortype_append(GPENCIL_OT_snap_to_grid); + WM_operatortype_append(GPENCIL_OT_snap_to_cursor); + WM_operatortype_append(GPENCIL_OT_snap_cursor_to_selected); + + WM_operatortype_append(GPENCIL_OT_brush_paint); + /* Editing (Buttons) ------------ */ WM_operatortype_append(GPENCIL_OT_data_add); @@ -277,6 +339,9 @@ void ED_operatortypes_gpencil(void) WM_operatortype_append(GPENCIL_OT_hide); WM_operatortype_append(GPENCIL_OT_reveal); + WM_operatortype_append(GPENCIL_OT_lock_all); + WM_operatortype_append(GPENCIL_OT_unlock_all); + WM_operatortype_append(GPENCIL_OT_layer_isolate); WM_operatortype_append(GPENCIL_OT_active_frame_delete); @@ -290,12 +355,14 @@ void ED_operatormacros_gpencil(void) wmOperatorType *ot; wmOperatorTypeMacro *otmacro; + /* Duplicate + Move = Interactively place newly duplicated strokes */ ot = WM_operatortype_append_macro("GPENCIL_OT_duplicate_move", "Duplicate Strokes", "Make copies of the selected Grease Pencil strokes and move them", OPTYPE_UNDO | OPTYPE_REGISTER); WM_operatortype_macro_define(ot, "GPENCIL_OT_duplicate"); otmacro = WM_operatortype_macro_define(ot, "TRANSFORM_OT_translate"); RNA_boolean_set(otmacro->ptr, "gpencil_strokes", true); + } /* ****************************************** */ diff --git a/source/blender/editors/gpencil/gpencil_paint.c b/source/blender/editors/gpencil/gpencil_paint.c index df88da073ca..c5a92c4383d 100644 --- a/source/blender/editors/gpencil/gpencil_paint.c +++ b/source/blender/editors/gpencil/gpencil_paint.c @@ -77,7 +77,33 @@ /* ******************************************* */ /* 'Globals' and Defines */ -/* Temporary 'Stroke' Operation data */ +/* values for tGPsdata->status */ +typedef enum eGPencil_PaintStatus { + GP_STATUS_IDLING = 0, /* stroke isn't in progress yet */ + GP_STATUS_PAINTING, /* a stroke is in progress */ + GP_STATUS_ERROR, /* something wasn't correctly set up */ + GP_STATUS_DONE /* painting done */ +} eGPencil_PaintStatus; + +/* Return flags for adding points to stroke buffer */ +typedef enum eGP_StrokeAdd_Result { + GP_STROKEADD_INVALID = -2, /* error occurred - insufficient info to do so */ + GP_STROKEADD_OVERFLOW = -1, /* error occurred - cannot fit any more points */ + GP_STROKEADD_NORMAL, /* point was successfully added */ + GP_STROKEADD_FULL /* cannot add any more points to buffer */ +} eGP_StrokeAdd_Result; + +/* Runtime flags */ +typedef enum eGPencil_PaintFlags { + GP_PAINTFLAG_FIRSTRUN = (1 << 0), /* operator just started */ + GP_PAINTFLAG_STROKEADDED = (1 << 1), + GP_PAINTFLAG_V3D_ERASER_DEPTH = (1 << 2) +} eGPencil_PaintFlags; + + +/* Temporary 'Stroke' Operation data + * "p" = op->customdata + */ typedef struct tGPsdata { Scene *scene; /* current scene from context */ @@ -95,8 +121,13 @@ typedef struct tGPsdata { bGPDlayer *gpl; /* layer we're working on */ bGPDframe *gpf; /* frame we're working on */ - short status; /* current status of painting */ - short paintmode; /* mode for painting */ + char *align_flag; /* projection-mode flags (toolsettings - eGPencil_Placement_Flags) */ + + eGPencil_PaintStatus status; /* current status of painting */ + eGPencil_PaintModes paintmode; /* mode for painting */ + eGPencil_PaintFlags flags; /* flags that can get set during runtime (eGPencil_PaintFlags) */ + + short radius; /* radius of influence for eraser */ int mval[2]; /* current mouse-position */ int mvalo[2]; /* previous recorded mouse-position */ @@ -104,9 +135,6 @@ typedef struct tGPsdata { float pressure; /* current stylus pressure */ float opressure; /* previous stylus pressure */ - short radius; /* radius of influence for eraser */ - short flags; /* flags that can get set during runtime */ - /* These need to be doubles, as (at least under unix) they are in seconds since epoch, * float (and its 7 digits precision) is definitively not enough here! * double, with its 15 digits precision, ensures us millisecond precision for a few centuries at least. @@ -124,29 +152,6 @@ typedef struct tGPsdata { void *erasercursor; /* radial cursor data for drawing eraser */ } tGPsdata; -/* values for tGPsdata->status */ -enum { - GP_STATUS_IDLING = 0, /* stroke isn't in progress yet */ - GP_STATUS_PAINTING, /* a stroke is in progress */ - GP_STATUS_ERROR, /* something wasn't correctly set up */ - GP_STATUS_DONE /* painting done */ -}; - -/* Return flags for adding points to stroke buffer */ -enum { - GP_STROKEADD_INVALID = -2, /* error occurred - insufficient info to do so */ - GP_STROKEADD_OVERFLOW = -1, /* error occurred - cannot fit any more points */ - GP_STROKEADD_NORMAL, /* point was successfully added */ - GP_STROKEADD_FULL /* cannot add any more points to buffer */ -}; - -/* Runtime flags */ -enum { - GP_PAINTFLAG_FIRSTRUN = (1 << 0), /* operator just started */ - GP_PAINTFLAG_STROKEADDED = (1 << 1), - GP_PAINTFLAG_V3D_ERASER_DEPTH = (1 << 2) -}; - /* ------ */ /* maximum sizes of gp-session buffer */ @@ -204,7 +209,7 @@ static int gpencil_draw_poll(bContext *C) static bool gpencil_project_check(tGPsdata *p) { bGPdata *gpd = p->gpd; - return ((gpd->sbuffer_sflag & GP_STROKE_3DSPACE) && (p->gpd->flag & (GP_DATA_DEPTH_VIEW | GP_DATA_DEPTH_STROKE))); + return ((gpd->sbuffer_sflag & GP_STROKE_3DSPACE) && (*p->align_flag & (GP_PROJECT_DEPTH_VIEW | GP_PROJECT_DEPTH_STROKE))); } /* ******************************************* */ @@ -736,112 +741,6 @@ static void gp_stroke_newfrombuffer(tGPsdata *p) /* --- 'Eraser' for 'Paint' Tool ------ */ -/* eraser tool - remove segment from stroke/split stroke (after lasso inside) */ -static short gp_stroke_eraser_splitdel(bGPDframe *gpf, bGPDstroke *gps, int i) -{ - bGPDspoint *pt_tmp = gps->points; - bGPDstroke *gsn = NULL; - - /* if stroke only had two points, get rid of stroke */ - if (gps->totpoints == 2) { - /* free stroke points, then stroke */ - MEM_freeN(pt_tmp); - BLI_freelinkN(&gpf->strokes, gps); - - /* nothing left in stroke, so stop */ - return 1; - } - - /* if last segment, just remove segment from the stroke */ - else if (i == gps->totpoints - 2) { - /* allocate new points array, and assign most of the old stroke there */ - gps->totpoints--; - gps->points = MEM_mallocN(sizeof(bGPDspoint) * gps->totpoints, "gp_stroke_points"); - memcpy(gps->points, pt_tmp, sizeof(bGPDspoint) * gps->totpoints); - - /* free temp buffer */ - MEM_freeN(pt_tmp); - - /* nothing left in stroke, so stop */ - return 1; - } - - /* if first segment, just remove segment from the stroke */ - else if (i == 0) { - /* allocate new points array, and assign most of the old stroke there */ - gps->totpoints--; - gps->points = MEM_mallocN(sizeof(bGPDspoint) * gps->totpoints, "gp_stroke_points"); - memcpy(gps->points, pt_tmp + 1, sizeof(bGPDspoint) * gps->totpoints); - - /* We must adjust timings! - * Each point's timing data is a delta from stroke's inittime, so as we erase the first - * point of the stroke, we have to offset this inittime and all remaining points' delta values. - * This way we get a new stroke with exactly the same timing as if user had started drawing from - * the second point... - */ - { - bGPDspoint *pts; - float delta = pt_tmp[1].time; - int j; - - gps->inittime += (double)delta; - - pts = gps->points; - for (j = 0; j < gps->totpoints; j++, pts++) { - pts->time -= delta; - } - } - - /* free temp buffer */ - MEM_freeN(pt_tmp); - - /* no break here, as there might still be stuff to remove in this stroke */ - return 0; - } - - /* segment occurs in 'middle' of stroke, so split */ - else { - /* duplicate stroke, and assign 'later' data to that stroke */ - gsn = MEM_dupallocN(gps); - gsn->prev = gsn->next = NULL; - BLI_insertlinkafter(&gpf->strokes, gps, gsn); - - gsn->totpoints = gps->totpoints - i; - gsn->points = MEM_mallocN(sizeof(bGPDspoint) * gsn->totpoints, "gp_stroke_points"); - memcpy(gsn->points, pt_tmp + i, sizeof(bGPDspoint) * gsn->totpoints); - - /* We must adjust timings of this new stroke! - * Each point's timing data is a delta from stroke's inittime, so as we erase the first - * point of the stroke, we have to offset this inittime and all remaing points' delta values. - * This way we get a new stroke with exactly the same timing as if user had started drawing from - * the second point... - */ - { - bGPDspoint *pts; - float delta = pt_tmp[i].time; - int j; - - gsn->inittime += (double)delta; - - pts = gsn->points; - for (j = 0; j < gsn->totpoints; j++, pts++) { - pts->time -= delta; - } - } - - /* adjust existing stroke */ - gps->totpoints = i; - gps->points = MEM_mallocN(sizeof(bGPDspoint) * gps->totpoints, "gp_stroke_points"); - memcpy(gps->points, pt_tmp, sizeof(bGPDspoint) * gps->totpoints); - - /* free temp buffer */ - MEM_freeN(pt_tmp); - - /* nothing left in stroke, so stop */ - return 1; - } -} - /* which which point is infront (result should only be used for comparison) */ static float view3d_point_depth(const RegionView3D *rv3d, const float co[3]) { @@ -853,6 +752,7 @@ static float view3d_point_depth(const RegionView3D *rv3d, const float co[3]) } } +/* only erase stroke points that are visible */ static bool gp_stroke_eraser_is_occluded(tGPsdata *p, const bGPDspoint *pt, const int x, const int y) { if ((p->sa->spacetype == SPACE_VIEW3D) && @@ -874,15 +774,33 @@ static bool gp_stroke_eraser_is_occluded(tGPsdata *p, const bGPDspoint *pt, cons return false; } +/* apply a falloff effect to brush strength, based on distance */ +static float gp_stroke_eraser_calc_influence(tGPsdata *p, const int mval[2], const int radius, const int co[2]) +{ + /* Linear Falloff... */ + float distance = (float)len_v2v2_int(mval, co); + float fac; + + CLAMP(distance, 0.0f, (float)radius); + fac = 1.0f - (distance / (float)radius); + + /* Control this further using pen pressure */ + fac *= p->pressure; + + /* Return influence factor computed here */ + return fac; +} /* eraser tool - evaluation per stroke */ /* TODO: this could really do with some optimization (KD-Tree/BVH?) */ static void gp_stroke_eraser_dostroke(tGPsdata *p, + bGPDlayer *gpl, bGPDframe *gpf, bGPDstroke *gps, const int mval[2], const int mvalo[2], - short rad, const rcti *rect, bGPDframe *gpf, bGPDstroke *gps) + const int radius, const rcti *rect) { bGPDspoint *pt1, *pt2; - int x0 = 0, y0 = 0, x1 = 0, y1 = 0; + int pc1[2] = {0}; + int pc2[2] = {0}; int i; if (gps->totpoints == 0) { @@ -892,56 +810,101 @@ static void gp_stroke_eraser_dostroke(tGPsdata *p, BLI_freelinkN(&gpf->strokes, gps); } else if (gps->totpoints == 1) { - gp_point_to_xy(&p->gsc, gps, gps->points, &x0, &y0); + gp_point_to_xy(&p->gsc, gps, gps->points, &pc1[0], &pc1[1]); /* do boundbox check first */ - if ((!ELEM(V2D_IS_CLIPPED, x0, y0)) && BLI_rcti_isect_pt(rect, x0, y0)) { + if ((!ELEM(V2D_IS_CLIPPED, pc1[0], pc1[1])) && BLI_rcti_isect_pt(rect, pc1[0], pc1[1])) { /* only check if point is inside */ - if (((x0 - mval[0]) * (x0 - mval[0]) + (y0 - mval[1]) * (y0 - mval[1])) <= rad * rad) { + if (len_v2v2_int(mval, pc1) <= radius) { /* free stroke */ + // XXX: pressure sensitive eraser should apply here too? MEM_freeN(gps->points); BLI_freelinkN(&gpf->strokes, gps); } } } else { - /* loop over the points in the stroke, checking for intersections - * - an intersection will require the stroke to be split + /* Pressure threshold at which stroke should be culled: Calculated as pressure value + * below which we would have invisible strokes + */ + const float cull_thresh = (gpl->thickness) ? 1.0f / ((float)gpl->thickness) : 1.0f; + + /* Amount to decrease the pressure of each point with each stroke */ + // TODO: Fetch from toolsettings, or compute based on thickness instead? + const float strength = 0.1f; + + /* Perform culling? */ + bool do_cull = false; + + + /* Clear Tags + * + * Note: It's better this way, as we are sure that + * we don't miss anything, though things will be + * slightly slower as a result + */ + for (i = 0; i < gps->totpoints; i++) { + bGPDspoint *pt = &gps->points[i]; + pt->flag &= ~GP_SPOINT_TAG; + } + + /* First Pass: Loop over the points in the stroke + * 1) Thin out parts of the stroke under the brush + * 2) Tag "too thin" parts for removal (in second pass) */ for (i = 0; (i + 1) < gps->totpoints; i++) { /* get points to work with */ pt1 = gps->points + i; pt2 = gps->points + i + 1; - gp_point_to_xy(&p->gsc, gps, pt1, &x0, &y0); - gp_point_to_xy(&p->gsc, gps, pt2, &x1, &y1); + gp_point_to_xy(&p->gsc, gps, pt1, &pc1[0], &pc1[1]); + gp_point_to_xy(&p->gsc, gps, pt2, &pc2[0], &pc2[1]); - /* check that point segment of the boundbox of the eraser stroke */ - if (((!ELEM(V2D_IS_CLIPPED, x0, y0)) && BLI_rcti_isect_pt(rect, x0, y0)) || - ((!ELEM(V2D_IS_CLIPPED, x1, y1)) && BLI_rcti_isect_pt(rect, x1, y1))) + /* Check that point segment of the boundbox of the eraser stroke */ + if (((!ELEM(V2D_IS_CLIPPED, pc1[0], pc1[1])) && BLI_rcti_isect_pt(rect, pc1[0], pc1[1])) || + ((!ELEM(V2D_IS_CLIPPED, pc2[0], pc2[1])) && BLI_rcti_isect_pt(rect, pc2[0], pc2[1]))) { - /* check if point segment of stroke had anything to do with + /* Check if point segment of stroke had anything to do with * eraser region (either within stroke painted, or on its lines) * - this assumes that linewidth is irrelevant */ - if (gp_stroke_inside_circle(mval, mvalo, rad, x0, y0, x1, y1)) { - if ((gp_stroke_eraser_is_occluded(p, pt1, x0, y0) == false) || - (gp_stroke_eraser_is_occluded(p, pt2, x1, y1) == false)) + if (gp_stroke_inside_circle(mval, mvalo, radius, pc1[0], pc1[1], pc2[0], pc2[1])) { + if ((gp_stroke_eraser_is_occluded(p, pt1, pc1[0], pc1[1]) == false) || + (gp_stroke_eraser_is_occluded(p, pt2, pc2[0], pc2[1]) == false)) { - /* if function returns true, break this loop (as no more point to check) */ - if (gp_stroke_eraser_splitdel(gpf, gps, i)) - break; + /* Point is affected: */ + /* 1) Adjust thickness + * - Influence of eraser falls off with distance from the middle of the eraser + * - Second point gets less influence, as it might get hit again in the next segment + */ + pt1->pressure -= gp_stroke_eraser_calc_influence(p, mval, radius, pc1) * strength; + pt2->pressure -= gp_stroke_eraser_calc_influence(p, mval, radius, pc2) * strength / 2.0f; + + /* 2) Tag any point with overly low influence for removal in the next pass */ + if (pt1->pressure < cull_thresh) { + pt1->flag |= GP_SPOINT_TAG; + do_cull = true; + } + if (pt2->pressure < cull_thresh) { + pt2->flag |= GP_SPOINT_TAG; + do_cull = true; + } } } } } + + /* Second Pass: Remove any points that are tagged */ + if (do_cull) { + gp_stroke_delete_tagged_points(gpf, gps, gps->next, GP_SPOINT_TAG); + } } } /* erase strokes which fall under the eraser strokes */ static void gp_stroke_doeraser(tGPsdata *p) { - bGPDframe *gpf = p->gpf; + bGPDlayer *gpl; bGPDstroke *gps, *gpn; rcti rect; @@ -960,10 +923,32 @@ static void gp_stroke_doeraser(tGPsdata *p) } } - /* loop over strokes, checking segments for intersections */ - for (gps = gpf->strokes.first; gps; gps = gpn) { - gpn = gps->next; - gp_stroke_eraser_dostroke(p, p->mval, p->mvalo, p->radius, &rect, gpf, gps); + /* loop over all layers too, since while it's easy to restrict editing to + * only a subset of layers, it is harder to perform the same erase operation + * on multiple layers... + */ + for (gpl = p->gpd->layers.first; gpl; gpl = gpl->next) { + bGPDframe *gpf = gpl->actframe; + + /* only affect layer if it's editable (and visible) */ + if (gpl->flag & (GP_LAYER_HIDE | GP_LAYER_LOCKED)) { + continue; + } + else if (gpf == NULL) { + continue; + } + + /* loop over strokes, checking segments for intersections */ + for (gps = gpf->strokes.first; gps; gps = gpn) { + gpn = gps->next; + + /* Not all strokes in the datablock may be valid in the current editor/context + * (e.g. 2D space strokes in the 3D view, if the same datablock is shared) + */ + if (ED_gpencil_stroke_can_use_direct(p->sa, gps)) { + gp_stroke_eraser_dostroke(p, gpl, gpf, gps, p->mval, p->mvalo, p->radius, &rect); + } + } } } @@ -1001,6 +986,7 @@ static bool gp_session_initdata(bContext *C, tGPsdata *p) bGPdata **gpd_ptr = NULL; ScrArea *curarea = CTX_wm_area(C); ARegion *ar = CTX_wm_region(C); + ToolSettings *ts = CTX_data_tool_settings(C); /* make sure the active view (at the starting time) is a 3d-view */ if (curarea == NULL) { @@ -1030,6 +1016,7 @@ static bool gp_session_initdata(bContext *C, tGPsdata *p) /* CAUTION: If this is the "toolbar", then this will change on the first stroke */ p->sa = curarea; p->ar = ar; + p->align_flag = &ts->gpencil_v3d_align; if (ar->regiondata == NULL) { p->status = GP_STATUS_ERROR; @@ -1047,6 +1034,7 @@ static bool gp_session_initdata(bContext *C, tGPsdata *p) p->sa = curarea; p->ar = ar; p->v2d = &ar->v2d; + p->align_flag = &ts->gpencil_v2d_align; break; } case SPACE_SEQ: @@ -1057,6 +1045,7 @@ static bool gp_session_initdata(bContext *C, tGPsdata *p) p->sa = curarea; p->ar = ar; p->v2d = &ar->v2d; + p->align_flag = &ts->gpencil_seq_align; /* check that gpencil data is allowed to be drawn */ if (sseq->mainb == SEQ_DRAW_SEQUENCE) { @@ -1075,6 +1064,7 @@ static bool gp_session_initdata(bContext *C, tGPsdata *p) p->sa = curarea; p->ar = ar; p->v2d = &ar->v2d; + p->align_flag = &ts->gpencil_ima_align; break; } case SPACE_CLIP: @@ -1091,6 +1081,7 @@ static bool gp_session_initdata(bContext *C, tGPsdata *p) p->sa = curarea; p->ar = ar; p->v2d = &ar->v2d; + p->align_flag = &ts->gpencil_v2d_align; invert_m4_m4(p->imat, sc->unistabmat); @@ -1167,6 +1158,12 @@ static tGPsdata *gp_session_initpaint(bContext *C) gp_session_initdata(C, p); + /* radius for eraser circle is defined in userprefs now */ + /* NOTE: we do this here, so that if we exit immediately, + * erase size won't get lost + */ + p->radius = U.gp_eraser; + /* return context data for running paint operator */ return p; } @@ -1194,8 +1191,10 @@ static void gp_session_cleanup(tGPsdata *p) } /* init new stroke */ -static void gp_paint_initstroke(tGPsdata *p, short paintmode) +static void gp_paint_initstroke(tGPsdata *p, eGPencil_PaintModes paintmode) { + Scene *scene = p->scene; + /* get active layer (or add a new one if non-existent) */ p->gpl = gpencil_layer_getactive(p->gpd); if (p->gpl == NULL) { @@ -1212,15 +1211,61 @@ static void gp_paint_initstroke(tGPsdata *p, short paintmode) } /* get active frame (add a new one if not matching frame) */ - p->gpf = gpencil_layer_getframe(p->gpl, p->scene->r.cfra, 1); - if (p->gpf == NULL) { - p->status = GP_STATUS_ERROR; - if (G.debug & G_DEBUG) - printf("Error: No frame created (gpencil_paint_init)\n"); - return; + if (paintmode == GP_PAINTMODE_ERASER) { + /* Eraser mode: + * 1) Add new frames to all frames that we might touch, + * 2) Ensure that p->gpf refers to the frame used for the active layer + * (to avoid problems with other tools which expect it to exist) + */ + bGPDlayer *gpl; + for (gpl = p->gpd->layers.first; gpl; gpl = gpl->next) { + /* Skip if layer not editable */ + if (gpl->flag & (GP_LAYER_HIDE | GP_LAYER_LOCKED)) + continue; + + /* Add a new frame if needed (and based off the active frame, + * as we need some existing strokes to erase) + */ + gpl->actframe = gpencil_layer_getframe(gpl, CFRA, GP_GETFRAME_ADD_COPY); + + /* XXX: we omit GP_FRAME_PAINT here for now, + * as it is only really useful for doing + * paintbuffer drawing + */ + } + + /* Ensure this gets set... */ + p->gpf = p->gpl->actframe; + + if (p->gpf == NULL) { + p->status = GP_STATUS_ERROR; + //if (G.debug & G_DEBUG) + printf("Error: No frame created (gpencil_paint_init)\n"); + return; + } + } + else { + /* Drawing Modes - Add a new frame if needed on the active layer */ + ToolSettings *ts = p->scene->toolsettings; + short add_frame_mode; + + if (ts->gpencil_flags & GP_TOOL_FLAG_RETAIN_LAST) + add_frame_mode = GP_GETFRAME_ADD_COPY; + else + add_frame_mode = GP_GETFRAME_ADD_NEW; + + p->gpf = gpencil_layer_getframe(p->gpl, CFRA, add_frame_mode); + + if (p->gpf == NULL) { + p->status = GP_STATUS_ERROR; + if (G.debug & G_DEBUG) + printf("Error: No frame created (gpencil_paint_init)\n"); + return; + } + else { + p->gpf->flag |= GP_FRAME_PAINT; + } } - else - p->gpf->flag |= GP_FRAME_PAINT; /* set 'eraser' for this stroke if using eraser */ p->paintmode = paintmode; @@ -1251,7 +1296,7 @@ static void gp_paint_initstroke(tGPsdata *p, short paintmode) /* when drawing in the camera view, in 2D space, set the subrect */ p->subrect = NULL; - if (!(p->gpd->flag & GP_DATA_VIEWALIGN)) { + if ((*p->align_flag & GP_PROJECT_VIEWSPACE) == 0) { if (p->sa->spacetype == SPACE_VIEW3D) { View3D *v3d = p->sa->spacedata.first; RegionView3D *rv3d = p->ar->regiondata; @@ -1279,7 +1324,7 @@ static void gp_paint_initstroke(tGPsdata *p, short paintmode) /* check if points will need to be made in view-aligned space */ - if (p->gpd->flag & GP_DATA_VIEWALIGN) { + if (*p->align_flag & GP_PROJECT_VIEWSPACE) { switch (p->sa->spacetype) { case SPACE_VIEW3D: { @@ -1308,7 +1353,7 @@ static void gp_paint_initstroke(tGPsdata *p, short paintmode) if (ELEM(NULL, sima, sima->image)) { /* make strokes be drawn in screen space */ p->gpd->sbuffer_sflag &= ~GP_STROKE_2DSPACE; - p->gpd->flag &= ~GP_DATA_VIEWALIGN; + *(p->align_flag) &= ~GP_PROJECT_VIEWSPACE; } else { p->gpd->sbuffer_sflag |= GP_STROKE_2DSPACE; @@ -1417,6 +1462,17 @@ static void gpencil_draw_toggle_eraser_cursor(bContext *C, tGPsdata *p, short en } } +/* Check if tablet eraser is being used (when processing events) */ +static bool gpencil_is_tablet_eraser_active(const wmEvent *event) +{ + if (event->tablet_data) { + const wmTabletData *wmtab = event->tablet_data; + return (wmtab->Active == EVT_TABLET_ERASER); + } + + return false; +} + /* ------------------------------- */ @@ -1467,7 +1523,7 @@ static void gpencil_draw_cancel(bContext *C, wmOperator *op) static int gpencil_draw_init(bContext *C, wmOperator *op) { tGPsdata *p; - int paintmode = RNA_enum_get(op->ptr, "mode"); + eGPencil_PaintModes paintmode = RNA_enum_get(op->ptr, "mode"); /* check context */ p = op->customdata = gp_session_initpaint(C); @@ -1484,9 +1540,6 @@ static int gpencil_draw_init(bContext *C, wmOperator *op) return 0; } - /* radius for eraser circle is defined in userprefs now */ - p->radius = U.gp_eraser; - /* everything is now setup ok */ return 1; } @@ -1529,6 +1582,10 @@ static void gpencil_draw_status_indicators(tGPsdata *p) ED_area_headerprint(p->sa, IFACE_("Grease Pencil Freehand Session: Hold and drag LMB to draw | " "ESC/Enter to end")); break; + case GP_PAINTMODE_DRAW_POLY: + ED_area_headerprint(p->sa, IFACE_("Grease Pencil Poly Session: LMB click to place next stroke vertex | " + "ESC/Enter to end")); + break; default: /* unhandled future cases */ ED_area_headerprint(p->sa, IFACE_("Grease Pencil Session: ESC/Enter to end")); @@ -1622,9 +1679,6 @@ static void gpencil_draw_apply_event(wmOperator *op, const wmEvent *event) tablet = (wmtab->Active != EVT_TABLET_NONE); p->pressure = wmtab->Pressure; - - /* if (wmtab->Active == EVT_TABLET_ERASER) */ - /* TODO... this should get caught by the keymaps which call drawing in the first place */ } else p->pressure = 1.0f; @@ -1819,7 +1873,6 @@ static tGPsdata *gpencil_stroke_begin(bContext *C, wmOperator *op) /* we may need to set up paint env again if we're resuming */ /* XXX: watch it with the paintmode! in future, * it'd be nice to allow changing paint-mode when in sketching-sessions */ - /* XXX: with tablet events, we may event want to check for eraser here, for nicer tablet support */ if (gp_session_initdata(C, p)) gp_paint_initstroke(p, p->paintmode); @@ -2008,14 +2061,14 @@ static int gpencil_draw_modal(bContext *C, wmOperator *op, const wmEvent *event) /* Switch paintmode (temporarily if need be) based on which button was used * NOTE: This is to make it more convenient to erase strokes when using drawing sessions */ - if (event->type == LEFTMOUSE) { - /* restore drawmode to default */ - p->paintmode = RNA_enum_get(op->ptr, "mode"); - } - else if (event->type == RIGHTMOUSE) { + if ((event->type == RIGHTMOUSE) || gpencil_is_tablet_eraser_active(event)) { /* turn on eraser */ p->paintmode = GP_PAINTMODE_ERASER; } + else if (event->type == LEFTMOUSE) { + /* restore drawmode to default */ + p->paintmode = RNA_enum_get(op->ptr, "mode"); + } gpencil_draw_toggle_eraser_cursor(C, p, p->paintmode == GP_PAINTMODE_ERASER); diff --git a/source/blender/editors/gpencil/gpencil_select.c b/source/blender/editors/gpencil/gpencil_select.c index 0dd91019a8c..83a1f2458eb 100644 --- a/source/blender/editors/gpencil/gpencil_select.c +++ b/source/blender/editors/gpencil/gpencil_select.c @@ -67,10 +67,15 @@ static int gpencil_select_poll(bContext *C) { bGPdata *gpd = ED_gpencil_data_get_active(C); - bGPDlayer *gpl = gpencil_layer_getactive(gpd); - /* only if there's an active layer with an active frame */ - return (gpl && gpl->actframe); + /* we just need some visible strokes, and to be in editmode */ + if ((gpd) && (gpd->flag & GP_DATA_STROKE_EDITMODE)) { + /* TODO: include a check for visible strokes? */ + if (gpd->layers.first) + return true; + } + + return false; } /* ********************************************** */ diff --git a/source/blender/editors/gpencil/gpencil_utils.c b/source/blender/editors/gpencil/gpencil_utils.c index 0d7aac7f48f..36508751d08 100644 --- a/source/blender/editors/gpencil/gpencil_utils.c +++ b/source/blender/editors/gpencil/gpencil_utils.c @@ -51,7 +51,9 @@ #include "RNA_access.h" #include "RNA_define.h" +#include "RNA_enum_types.h" +#include "UI_resources.h" #include "UI_view2d.h" #include "ED_gpencil.h" @@ -76,8 +78,6 @@ bGPdata **ED_gpencil_data_get_pointers_direct(ID *screen_id, Scene *scene, ScrAr switch (sa->spacetype) { case SPACE_VIEW3D: /* 3D-View */ - case SPACE_TIME: /* Timeline - XXX: this is a hack to get it to show GP keyframes for 3D view */ - case SPACE_ACTION: /* DepeSheet - XXX: this is a hack to get the keyframe jump operator to take GP Keyframes into account */ { BLI_assert(scene && ELEM(scene->toolsettings->gpencil_src, GP_TOOL_SOURCE_SCENE, GP_TOOL_SOURCE_OBJECT)); @@ -211,6 +211,45 @@ bGPdata *ED_gpencil_data_get_active_v3d(Scene *scene, View3D *v3d) return gpd ? gpd : scene->gpd; } +/* ******************************************************** */ +/* Keyframe Indicator Checks */ + +/* Check whether there's an active GP keyframe on the current frame */ +bool ED_gpencil_has_keyframe_v3d(Scene *scene, Object *ob, int cfra) +{ + /* just check both for now... */ + // XXX: this could get confusing (e.g. if only on the object, but other places don't show this) + if (scene->gpd) { + bGPDlayer *gpl = gpencil_layer_getactive(scene->gpd); + if (gpl) { + if (gpl->actframe) { + // XXX: assumes that frame has been fetched already + return (gpl->actframe->framenum == cfra); + } + else { + /* XXX: disabled as could be too much of a penalty */ + /* return BKE_gpencil_layer_find_frame(gpl, cfra); */ + } + } + } + + if (ob && ob->gpd) { + bGPDlayer *gpl = gpencil_layer_getactive(ob->gpd); + if (gpl) { + if (gpl->actframe) { + // XXX: assumes that frame has been fetched already + return (gpl->actframe->framenum == cfra); + } + else { + /* XXX: disabled as could be too much of a penalty */ + /* return BKE_gpencil_layer_find_frame(gpl, cfra); */ + } + } + } + + return false; +} + /* ******************************************************** */ /* Poll Callbacks */ @@ -230,6 +269,92 @@ int gp_active_layer_poll(bContext *C) return (gpl != NULL); } +/* ******************************************************** */ +/* Dynamic Enums of GP Layers */ +/* NOTE: These include an option to create a new layer and use that... */ + +/* Just existing layers */ +EnumPropertyItem *ED_gpencil_layers_enum_itemf(bContext *C, PointerRNA *UNUSED(ptr), PropertyRNA *UNUSED(prop), bool *r_free) +{ + bGPdata *gpd = CTX_data_gpencil_data(C); + bGPDlayer *gpl; + EnumPropertyItem *item = NULL, item_tmp = {0}; + int totitem = 0; + int i = 0; + + if (ELEM(NULL, C, gpd)) { + return DummyRNA_DEFAULT_items; + } + + /* Existing layers */ + for (gpl = gpd->layers.first; gpl; gpl = gpl->next, i++) { + item_tmp.identifier = gpl->info; + item_tmp.name = gpl->info; + item_tmp.value = i; + + if (gpl->flag & GP_LAYER_ACTIVE) + item_tmp.icon = ICON_GREASEPENCIL; + else + item_tmp.icon = ICON_NONE; + + RNA_enum_item_add(&item, &totitem, &item_tmp); + } + + RNA_enum_item_end(&item, &totitem); + *r_free = true; + + return item; +} + +/* Existing + Option to add/use new layer */ +EnumPropertyItem *ED_gpencil_layers_with_new_enum_itemf(bContext *C, PointerRNA *UNUSED(ptr), PropertyRNA *UNUSED(prop), bool *r_free) +{ + bGPdata *gpd = CTX_data_gpencil_data(C); + bGPDlayer *gpl; + EnumPropertyItem *item = NULL, item_tmp = {0}; + int totitem = 0; + int i = 0; + + if (ELEM(NULL, C, gpd)) { + return DummyRNA_DEFAULT_items; + } + + /* Create new layer */ + /* TODO: have some way of specifying that we don't want this? */ + { + /* active Keying Set */ + item_tmp.identifier = "__CREATE__"; + item_tmp.name = "New Layer"; + item_tmp.value = -1; + item_tmp.icon = ICON_ZOOMIN; + RNA_enum_item_add(&item, &totitem, &item_tmp); + + /* separator */ + RNA_enum_item_add_separator(&item, &totitem); + } + + /* Existing layers */ + for (gpl = gpd->layers.first, i = 0; gpl; gpl = gpl->next, i++) { + item_tmp.identifier = gpl->info; + item_tmp.name = gpl->info; + item_tmp.value = i; + + if (gpl->flag & GP_LAYER_ACTIVE) + item_tmp.icon = ICON_GREASEPENCIL; + else + item_tmp.icon = ICON_NONE; + + RNA_enum_item_add(&item, &totitem, &item_tmp); + } + + RNA_enum_item_end(&item, &totitem); + *r_free = true; + + return item; +} + + + /* ******************************************************** */ /* Brush Tool Core */ @@ -372,4 +497,37 @@ void gp_point_to_xy(GP_SpaceConversion *gsc, bGPDstroke *gps, bGPDspoint *pt, } } +/* Project screenspace coordinates to 3D-space + * NOTE: We include this as a utility function, since the standard method + * involves quite a few steps, which are invariably always the same + * for all GPencil operations. So, it's nicer to just centralise these. + * WARNING: Assumes that it is getting called in a 3D view only + */ +bool gp_point_xy_to_3d(GP_SpaceConversion *gsc, Scene *scene, const float screen_co[2], float r_out[3]) +{ + View3D *v3d = gsc->sa->spacedata.first; + RegionView3D *rv3d = gsc->ar->regiondata; + float *rvec = ED_view3d_cursor3d_get(scene, v3d); + float ref[3] = {rvec[0], rvec[1], rvec[2]}; + float zfac = ED_view3d_calc_zfac(rv3d, rvec, NULL); + + float mval_f[2], mval_prj[2]; + float dvec[3]; + + copy_v2_v2(mval_f, screen_co); + + if (ED_view3d_project_float_global(gsc->ar, ref, mval_prj, V3D_PROJ_TEST_NOP) == V3D_PROJ_RET_OK) { + sub_v2_v2v2(mval_f, mval_prj, mval_f); + ED_view3d_win_to_delta(gsc->ar, mval_f, dvec, zfac); + sub_v3_v3v3(r_out, rvec, dvec); + + return true; + } + else { + zero_v3(r_out); + + return false; + } +} + /* ******************************************************** */ diff --git a/source/blender/editors/include/ED_gpencil.h b/source/blender/editors/include/ED_gpencil.h index 448f2c83aad..47619031501 100644 --- a/source/blender/editors/include/ED_gpencil.h +++ b/source/blender/editors/include/ED_gpencil.h @@ -41,7 +41,9 @@ struct bGPdata; struct bGPDlayer; struct bGPDframe; struct bGPDstroke; +struct bAnimContext; struct PointerRNA; +struct wmWindowManager; struct wmKeyConfig; @@ -77,6 +79,8 @@ struct bGPdata *ED_gpencil_data_get_active_direct(struct ID *screen_id, struct S /* 3D View */ struct bGPdata *ED_gpencil_data_get_active_v3d(struct Scene *scene, struct View3D *v3d); +bool ED_gpencil_has_keyframe_v3d(struct Scene *scene, struct Object *ob, int cfra); + /* ----------- Stroke Editing Utilities ---------------- */ bool ED_gpencil_stroke_can_use_direct(const struct ScrArea *sa, const struct bGPDstroke *gps); @@ -100,7 +104,7 @@ void ED_gpencil_strokes_copybuf_free(void); void ED_gpencil_draw_2dimage(const struct bContext *C); void ED_gpencil_draw_view2d(const struct bContext *C, bool onlyv2d); -void ED_gpencil_draw_view3d(struct Scene *scene, struct View3D *v3d, struct ARegion *ar, bool only3d); +void ED_gpencil_draw_view3d(struct wmWindowManager *wm, struct Scene *scene, struct View3D *v3d, struct ARegion *ar, bool only3d); void ED_gpencil_draw_ex(struct Scene *scene, struct bGPdata *gpd, int winx, int winy, const int cfra, const char spacetype); @@ -122,11 +126,11 @@ void ED_gplayer_frames_keytype_set(struct bGPDlayer *gpl, short type); void ED_gplayer_snap_frames(struct bGPDlayer *gpl, struct Scene *scene, short mode); -#if 0 -void free_gpcopybuf(void); -void copy_gpdata(void); -void paste_gpdata(void); +void ED_gpencil_anim_copybuf_free(void); +bool ED_gpencil_anim_copybuf_copy(struct bAnimContext *ac); +bool ED_gpencil_anim_copybuf_paste(struct bAnimContext *ac, const short copy_mode); +#if 0 void mirror_gplayer_frames(struct bGPDlayer *gpl, short mode); #endif diff --git a/source/blender/editors/object/object_edit.c b/source/blender/editors/object/object_edit.c index fb4065bf5de..64a69542e38 100644 --- a/source/blender/editors/object/object_edit.c +++ b/source/blender/editors/object/object_edit.c @@ -46,6 +46,7 @@ #include "DNA_armature_types.h" #include "DNA_curve_types.h" +#include "DNA_gpencil_types.h" #include "DNA_group_types.h" #include "DNA_material_types.h" #include "DNA_meta_types.h" @@ -1509,6 +1510,7 @@ static EnumPropertyItem *object_mode_set_itemsf(bContext *C, PointerRNA *UNUSED( EnumPropertyItem *input = rna_enum_object_mode_items; EnumPropertyItem *item = NULL; Object *ob; + bGPdata *gpd; int totitem = 0; if (!C) /* needed for docs */ @@ -1536,6 +1538,14 @@ static EnumPropertyItem *object_mode_set_itemsf(bContext *C, PointerRNA *UNUSED( /* We need at least this one! */ RNA_enum_items_add_value(&item, &totitem, input, OB_MODE_OBJECT); } + + /* On top of all the rest, GPencil Stroke Edit Mode + * is available if there's a valid gp datablock... + */ + gpd = CTX_data_gpencil_data(C); + if (gpd) { + RNA_enum_items_add_value(&item, &totitem, rna_enum_object_mode_items, OB_MODE_GPENCIL); + } RNA_enum_item_end(&item, &totitem); @@ -1560,6 +1570,8 @@ static const char *object_mode_op_string(int mode) return "PARTICLE_OT_particle_edit_toggle"; if (mode == OB_MODE_POSE) return "OBJECT_OT_posemode_toggle"; + if (mode == OB_MODE_GPENCIL) + return "GPENCIL_OT_editmode_toggle"; return NULL; } @@ -1571,6 +1583,8 @@ static bool object_mode_compat_test(Object *ob, ObjectMode mode) if (ob) { if (mode == OB_MODE_OBJECT) return true; + else if (mode == OB_MODE_GPENCIL) + return true; /* XXX: assume this is the case for now... */ switch (ob->type) { case OB_MESH: @@ -1625,13 +1639,45 @@ bool ED_object_mode_compat_set(bContext *C, Object *ob, int mode, ReportList *re return ok; } +static int object_mode_set_poll(bContext *C) +{ + /* Since Grease Pencil editmode is also handled here, + * we have a special exception for allowing this operator + * to still work in that case when there's no active object + * so that users can exit editmode this way as per normal. + */ + if (ED_operator_object_active_editable(C)) + return true; + else + return (CTX_data_gpencil_data(C) != NULL); +} + static int object_mode_set_exec(bContext *C, wmOperator *op) { Object *ob = CTX_data_active_object(C); + bGPdata *gpd = CTX_data_gpencil_data(C); ObjectMode mode = RNA_enum_get(op->ptr, "mode"); ObjectMode restore_mode = (ob) ? ob->mode : OB_MODE_OBJECT; const bool toggle = RNA_boolean_get(op->ptr, "toggle"); - + + if (gpd) { + /* GP Mode is not bound to a specific object. Therefore, + * we don't want it to be actually saved on any objects, + * as weirdness can happen if you select other objects, + * or load old files. + * + * Instead, we use the following 2 rules to ensure that + * the mode selector works as expected: + * 1) If there's no object, we want to enter editmode. + * (i.e. with no object, we're in object mode) + * 2) Otherwise, exit stroke editmode, so that we can + * enter another mode... + */ + if (!ob || (gpd->flag & GP_DATA_STROKE_EDITMODE)) { + WM_operator_name_call(C, "GPENCIL_OT_editmode_toggle", WM_OP_EXEC_REGION_WIN, NULL); + } + } + if (!ob || !object_mode_compat_test(ob, mode)) return OPERATOR_PASS_THROUGH; @@ -1675,7 +1721,7 @@ void OBJECT_OT_mode_set(wmOperatorType *ot) /* api callbacks */ ot->exec = object_mode_set_exec; - ot->poll = ED_operator_object_active_editable; + ot->poll = object_mode_set_poll; //ED_operator_object_active_editable; /* flags */ ot->flag = 0; /* no register/undo here, leave it to operators being called */ diff --git a/source/blender/editors/screen/screen_ops.c b/source/blender/editors/screen/screen_ops.c index 7c1ff0db2a9..7806038b313 100644 --- a/source/blender/editors/screen/screen_ops.c +++ b/source/blender/editors/screen/screen_ops.c @@ -2204,7 +2204,6 @@ static int keyframe_jump_exec(bContext *C, wmOperator *op) Main *bmain = CTX_data_main(C); Scene *scene = CTX_data_scene(C); Object *ob = CTX_data_active_object(C); - bGPdata *gpd = CTX_data_gpencil_data(C); bDopeSheet ads = {NULL}; DLRBT_Tree keys; ActKeyColumn *ak; @@ -2229,11 +2228,12 @@ static int keyframe_jump_exec(bContext *C, wmOperator *op) /* populate tree with keyframe nodes */ scene_to_keylist(&ads, scene, &keys, NULL); + gpencil_to_keylist(&ads, scene->gpd, &keys); - if (ob) + if (ob) { ob_to_keylist(&ads, ob, &keys, NULL); - - gpencil_to_keylist(&ads, gpd, &keys); + gpencil_to_keylist(&ads, ob->gpd, &keys); + } { Mask *mask = CTX_data_edit_mask(C); diff --git a/source/blender/editors/space_action/action_edit.c b/source/blender/editors/space_action/action_edit.c index 0dd5c7ca775..ce0db9e5523 100644 --- a/source/blender/editors/space_action/action_edit.c +++ b/source/blender/editors/space_action/action_edit.c @@ -549,9 +549,10 @@ static int actkeys_copy_exec(bContext *C, wmOperator *op) /* copy keyframes */ if (ac.datatype == ANIMCONT_GPENCIL) { - /* FIXME... */ - BKE_report(op->reports, RPT_ERROR, "Keyframe pasting is not available for grease pencil mode"); - return OPERATOR_CANCELLED; + if (ED_gpencil_anim_copybuf_copy(&ac) == false) { + /* Nothing got copied - An error about this should be been logged already */ + return OPERATOR_CANCELLED; + } } else if (ac.datatype == ANIMCONT_MASK) { /* FIXME... */ @@ -599,7 +600,13 @@ static int actkeys_paste_exec(bContext *C, wmOperator *op) ac.reports = op->reports; /* paste keyframes */ - if (ELEM(ac.datatype, ANIMCONT_GPENCIL, ANIMCONT_MASK)) { + if (ac.datatype == ANIMCONT_GPENCIL) { + if (ED_gpencil_anim_copybuf_paste(&ac, offset_mode) == false) { + /* An error occurred - Reports should have been fired already */ + return OPERATOR_CANCELLED; + } + } + else if (ac.datatype == ANIMCONT_MASK) { /* FIXME... */ BKE_report(op->reports, RPT_ERROR, "Keyframe pasting is not available for grease pencil or mask mode"); return OPERATOR_CANCELLED; diff --git a/source/blender/editors/space_time/space_time.c b/source/blender/editors/space_time/space_time.c index c5dfc123f39..4c64c52ba2b 100644 --- a/source/blender/editors/space_time/space_time.c +++ b/source/blender/editors/space_time/space_time.c @@ -349,14 +349,16 @@ static void time_draw_keyframes(const bContext *C, ARegion *ar) { Scene *scene = CTX_data_scene(C); Object *ob = CTX_data_active_object(C); - bGPdata *gpd = CTX_data_gpencil_data(C); View2D *v2d = &ar->v2d; bool onlysel = ((scene->flag & SCE_KEYS_NO_SELONLY) == 0); - /* draw grease pencil keyframes (if available) */ - if (gpd) { - UI_ThemeColor(TH_TIME_GP_KEYFRAME); - time_draw_idblock_keyframes(v2d, (ID *)gpd, onlysel); + /* draw grease pencil keyframes (if available) */ + UI_ThemeColor(TH_TIME_GP_KEYFRAME); + if (scene->gpd) { + time_draw_idblock_keyframes(v2d, (ID *)scene->gpd, onlysel); + } + if (ob && ob->gpd) { + time_draw_idblock_keyframes(v2d, (ID *)ob->gpd, onlysel); } /* draw scene keyframes first diff --git a/source/blender/editors/space_view3d/view3d_draw.c b/source/blender/editors/space_view3d/view3d_draw.c index 5a839c25e05..e7223ddf065 100644 --- a/source/blender/editors/space_view3d/view3d_draw.c +++ b/source/blender/editors/space_view3d/view3d_draw.c @@ -941,6 +941,8 @@ static void draw_selected_name(Scene *scene, Object *ob, rcti *rect) /* color depends on whether there is a keyframe */ if (id_frame_has_keyframe((ID *)ob, /* BKE_scene_frame_get(scene) */ (float)cfra, ANIMFILTER_KEYS_LOCAL)) UI_ThemeColor(TH_VERTEX_SELECT); + else if (ED_gpencil_has_keyframe_v3d(scene, ob, cfra)) + UI_ThemeColor(TH_CFRAME); // XXX else UI_ThemeColor(TH_TEXT_HI); } @@ -2349,7 +2351,7 @@ void ED_view3d_draw_depth_gpencil(Scene *scene, ARegion *ar, View3D *v3d) glEnable(GL_DEPTH_TEST); if (v3d->flag2 & V3D_SHOW_GPENCIL) { - ED_gpencil_draw_view3d(scene, v3d, ar, true); + ED_gpencil_draw_view3d(NULL, scene, v3d, ar, true); } v3d->zbuf = zbuf; @@ -2853,9 +2855,11 @@ static void view3d_draw_objects( /* must be before xray draw which clears the depth buffer */ if (v3d->flag2 & V3D_SHOW_GPENCIL) { + wmWindowManager *wm = (C != NULL) ? CTX_wm_manager(C) : NULL; + /* must be before xray draw which clears the depth buffer */ if (v3d->zbuf) glDisable(GL_DEPTH_TEST); - ED_gpencil_draw_view3d(scene, v3d, ar, true); + ED_gpencil_draw_view3d(wm, scene, v3d, ar, true); if (v3d->zbuf) glEnable(GL_DEPTH_TEST); } @@ -3266,7 +3270,7 @@ void ED_view3d_draw_offscreen( if (v3d->flag2 & V3D_SHOW_GPENCIL) { /* draw grease-pencil stuff - needed to get paint-buffer shown too (since it's 2D) */ - ED_gpencil_draw_view3d(scene, v3d, ar, false); + ED_gpencil_draw_view3d(NULL, scene, v3d, ar, false); } /* freeing the images again here could be done after the operator runs, leaving for now */ @@ -3481,6 +3485,9 @@ ImBuf *ED_view3d_draw_offscreen_imbuf_simple( if (use_solid_tex) v3d.flag2 |= V3D_SOLID_TEX; + + if (draw_background) + v3d.flag3 |= V3D_SHOW_WORLD; rv3d.persp = RV3D_CAMOB; @@ -3949,6 +3956,7 @@ static void view3d_main_region_draw_info(const bContext *C, Scene *scene, ARegion *ar, View3D *v3d, const char *grid_unit, bool render_border) { + wmWindowManager *wm = CTX_wm_manager(C); RegionView3D *rv3d = ar->regiondata; rcti rect; @@ -3972,7 +3980,7 @@ static void view3d_main_region_draw_info(const bContext *C, Scene *scene, if (v3d->flag2 & V3D_SHOW_GPENCIL) { /* draw grease-pencil stuff - needed to get paint-buffer shown too (since it's 2D) */ - ED_gpencil_draw_view3d(scene, v3d, ar, false); + ED_gpencil_draw_view3d(wm, scene, v3d, ar, false); } if ((v3d->flag2 & V3D_RENDER_OVERRIDE) == 0) { @@ -3999,8 +4007,6 @@ static void view3d_main_region_draw_info(const bContext *C, Scene *scene, } if ((v3d->flag2 & V3D_RENDER_OVERRIDE) == 0) { - wmWindowManager *wm = CTX_wm_manager(C); - if ((U.uiflag & USER_SHOW_FPS) && ED_screen_animation_no_scrub(wm)) { ED_scene_draw_fps(scene, &rect); } diff --git a/source/blender/editors/space_view3d/view3d_header.c b/source/blender/editors/space_view3d/view3d_header.c index f5247fff4a7..0713377d210 100644 --- a/source/blender/editors/space_view3d/view3d_header.c +++ b/source/blender/editors/space_view3d/view3d_header.c @@ -34,6 +34,7 @@ #include "DNA_scene_types.h" #include "DNA_object_types.h" +#include "DNA_gpencil_types.h" #include "BLI_utildefines.h" @@ -287,6 +288,7 @@ void uiTemplateHeader3D(uiLayout *layout, struct bContext *C) PointerRNA v3dptr, toolsptr, sceneptr; Object *ob = OBACT; Object *obedit = CTX_data_edit_object(C); + bGPdata *gpd = CTX_data_gpencil_data(C); uiBlock *block; uiLayout *row; bool is_paint = false; @@ -303,7 +305,10 @@ void uiTemplateHeader3D(uiLayout *layout, struct bContext *C) UI_block_emboss_set(block, UI_EMBOSS); /* mode */ - if (ob) { + if ((gpd) && (gpd->flag & GP_DATA_STROKE_EDITMODE)) { + modeselect = OB_MODE_GPENCIL; + } + else if (ob) { modeselect = ob->mode; is_paint = ELEM(ob->mode, OB_MODE_SCULPT, OB_MODE_VERTEX_PAINT, OB_MODE_WEIGHT_PAINT, OB_MODE_TEXTURE_PAINT); } diff --git a/source/blender/editors/transform/transform_conversions.c b/source/blender/editors/transform/transform_conversions.c index 16642dec5b0..dc0b153d6e9 100644 --- a/source/blender/editors/transform/transform_conversions.c +++ b/source/blender/editors/transform/transform_conversions.c @@ -7767,45 +7767,7 @@ static void createTransGPencil(bContext *C, TransInfo *t) */ // XXX: should this be allowed when framelock is enabled? if (gpf->framenum != cfra) { - bGPDframe *new_frame = gpencil_frame_duplicate(gpf); - bGPDframe *gf; - bool found = false; - - /* Find frame to insert it before */ - for (gf = gpf->next; gf; gf = gf->next) { - if (gf->framenum > cfra) { - /* Add it here */ - BLI_insertlinkbefore(&gpl->frames, gf, new_frame); - - found = true; - break; - } - else if (gf->framenum == cfra) { - /* This only happens when we're editing with framelock on... - * - Delete the new frame and don't do anything else here... - */ - //printf("GP Frame convert to TransData - Copy aborted for frame %d -> %d\n", gpf->framenum, gf->framenum); - free_gpencil_strokes(new_frame); - MEM_freeN(new_frame); - new_frame = NULL; - - found = true; - break; - } - } - - if (found == false) { - /* Add new frame to the end */ - BLI_addtail(&gpl->frames, new_frame); - } - - /* Edit the new frame instead, if it did get created + added */ - if (new_frame) { - // TODO: tag this one as being "newly created" so that we can remove it if the edit is cancelled - new_frame->framenum = cfra; - - gpf = new_frame; - } + gpf = gpencil_frame_addcopy(gpl, cfra); } /* Loop over strokes, adding TransData for points as needed... */ @@ -7930,7 +7892,8 @@ void createTransData(bContext *C, TransInfo *t) } } else if (t->options & CTX_GPENCIL_STROKES) { - t->flag |= T_POINTS; // XXX... + t->options |= CTX_GPENCIL_STROKES; + t->flag |= T_POINTS; createTransGPencil(C, t); if (t->data && (t->flag & T_PROP_EDIT)) { diff --git a/source/blender/editors/transform/transform_generics.c b/source/blender/editors/transform/transform_generics.c index 3b40a55a270..84087ec8840 100644 --- a/source/blender/editors/transform/transform_generics.c +++ b/source/blender/editors/transform/transform_generics.c @@ -39,6 +39,7 @@ #include "DNA_anim_types.h" #include "DNA_armature_types.h" #include "DNA_brush_types.h" +#include "DNA_gpencil_types.h" #include "DNA_lattice_types.h" #include "DNA_screen_types.h" #include "DNA_sequence_types.h" @@ -1095,6 +1096,7 @@ void initTransInfo(bContext *C, TransInfo *t, wmOperator *op, const wmEvent *eve ScrArea *sa = CTX_wm_area(C); Object *obedit = CTX_data_edit_object(C); Object *ob = CTX_data_active_object(C); + bGPdata *gpd = CTX_data_gpencil_data(C); PropertyRNA *prop; t->scene = sce; @@ -1164,6 +1166,11 @@ void initTransInfo(bContext *C, TransInfo *t, wmOperator *op, const wmEvent *eve t->remove_on_cancel = true; } } + + /* GPencil editing context */ + if ((gpd) && (gpd->flag & GP_DATA_STROKE_EDITMODE)) { + t->options |= CTX_GPENCIL_STROKES; + } /* Assign the space type, some exceptions for running in different mode */ if (sa == NULL) { diff --git a/source/blender/editors/transform/transform_manipulator.c b/source/blender/editors/transform/transform_manipulator.c index 8131758f93c..74d27fb3068 100644 --- a/source/blender/editors/transform/transform_manipulator.c +++ b/source/blender/editors/transform/transform_manipulator.c @@ -37,12 +37,14 @@ #include "DNA_armature_types.h" #include "DNA_curve_types.h" +#include "DNA_gpencil_types.h" #include "DNA_lattice_types.h" #include "DNA_meta_types.h" #include "DNA_screen_types.h" #include "DNA_scene_types.h" #include "DNA_view3d_types.h" +#include "BLI_listbase.h" #include "BLI_math.h" #include "BLI_utildefines.h" @@ -272,6 +274,8 @@ static int calc_manipulator_stats(const bContext *C) RegionView3D *rv3d = ar->regiondata; Base *base; Object *ob = OBACT; + bGPdata *gpd = CTX_data_gpencil_data(C); + const bool is_gp_edit = ((gpd) && (gpd->flag & GP_DATA_STROKE_EDITMODE)); int a, totsel = 0; /* transform widget matrix */ @@ -282,8 +286,32 @@ static int calc_manipulator_stats(const bContext *C) /* transform widget centroid/center */ INIT_MINMAX(scene->twmin, scene->twmax); zero_v3(scene->twcent); - - if (obedit) { + + if (is_gp_edit) { + CTX_DATA_BEGIN(C, bGPDstroke *, gps, editable_gpencil_strokes) + { + /* we're only interested in selected points here... */ + if (gps->flag & GP_STROKE_SELECT) { + bGPDspoint *pt; + int i; + + /* Change selection status of all points, then make the stroke match */ + for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) { + if (pt->flag & GP_SPOINT_SELECT) { + calc_tw_center(scene, &pt->x); + totsel++; + } + } + } + } + CTX_DATA_END; + + /* selection center */ + if (totsel) { + mul_v3_fl(scene->twcent, 1.0f / (float)totsel); /* centroid! */ + } + } + else if (obedit) { ob = obedit; if ((ob->lay & v3d->lay) == 0) return 0; @@ -546,7 +574,7 @@ static int calc_manipulator_stats(const bContext *C) } /* global, local or normal orientation? */ - if (ob && totsel) { + if (ob && totsel && !is_gp_edit) { switch (v3d->twmode) { @@ -1595,9 +1623,12 @@ void BIF_draw_manipulator(const bContext *C) case V3D_AROUND_CENTER_BOUNDS: case V3D_AROUND_ACTIVE: { - Object *ob; + bGPdata *gpd = CTX_data_gpencil_data(C); + Object *ob = OBACT; + if (((v3d->around == V3D_AROUND_ACTIVE) && (scene->obedit == NULL)) && - ((ob = OBACT) && !(ob->mode & OB_MODE_POSE))) + ((gpd == NULL) || !(gpd->flag & GP_DATA_STROKE_EDITMODE)) && + (!(ob->mode & OB_MODE_POSE))) { copy_v3_v3(rv3d->twmat[3], ob->obmat[3]); } diff --git a/source/blender/makesdna/DNA_action_types.h b/source/blender/makesdna/DNA_action_types.h index d574694c70d..9c17a1f4f5b 100644 --- a/source/blender/makesdna/DNA_action_types.h +++ b/source/blender/makesdna/DNA_action_types.h @@ -596,6 +596,9 @@ typedef enum eDopeSheet_FilterFlag { ADS_FILTER_BY_FCU_NAME = (1 << 27), /* for F-Curves, filter by the displayed name (i.e. to isolate all Location curves only) */ ADS_FILTER_ONLY_ERRORS = (1 << 28), /* show only F-Curves which are disabled/have errors - for debugging drivers */ + /* GPencil Mode */ + ADS_FILTER_GP_3DONLY = (1 << 29), /* GP Mode - Only show datablocks used in the scene */ + /* combination filters (some only used at runtime) */ ADS_FILTER_NOOBDATA = (ADS_FILTER_NOCAM | ADS_FILTER_NOMAT | ADS_FILTER_NOLAM | ADS_FILTER_NOCUR | ADS_FILTER_NOPART | ADS_FILTER_NOARM | ADS_FILTER_NOSPK | ADS_FILTER_NOMODIFIERS) } eDopeSheet_FilterFlag; diff --git a/source/blender/makesdna/DNA_gpencil_types.h b/source/blender/makesdna/DNA_gpencil_types.h index beffbc4c017..aa98ddb11ae 100644 --- a/source/blender/makesdna/DNA_gpencil_types.h +++ b/source/blender/makesdna/DNA_gpencil_types.h @@ -51,7 +51,10 @@ typedef struct bGPDspoint { /* bGPDspoint->flag */ typedef enum eGPDspoint_Flag { /* stroke point is selected (for editing) */ - GP_SPOINT_SELECT = (1 << 0) + GP_SPOINT_SELECT = (1 << 0), + + /* stroke point is tagged (for some editing operation) */ + GP_SPOINT_TAG = (1 << 1), } eGPSPoint_Flag; /* Grease-Pencil Annotations - 'Stroke' @@ -190,6 +193,7 @@ typedef enum eGPdata_Flag { /* is the block overriding all clicks? */ /* GP_DATA_EDITPAINT = (1 << 3), */ +/* ------------------------------------------------ DEPRECATED */ /* new strokes are added in viewport space */ GP_DATA_VIEWALIGN = (1 << 4), @@ -198,9 +202,13 @@ typedef enum eGPdata_Flag { GP_DATA_DEPTH_STROKE = (1 << 6), GP_DATA_DEPTH_STROKE_ENDPOINTS = (1 << 7), +/* ------------------------------------------------ DEPRECATED */ /* Stroke Editing Mode - Toggle to enable alternative keymap for easier editing of stroke points */ - GP_DATA_STROKE_EDITMODE = (1 << 8) + GP_DATA_STROKE_EDITMODE = (1 << 8), + + /* Convenience/cache flag to make it easier to quickly toggle onion skinning on/off */ + GP_DATA_SHOW_ONIONSKINS = (1 << 9) } eGPdata_Flag; #endif /* __DNA_GPENCIL_TYPES_H__ */ diff --git a/source/blender/makesdna/DNA_object_types.h b/source/blender/makesdna/DNA_object_types.h index cb39655ddc9..d53c8360bb5 100644 --- a/source/blender/makesdna/DNA_object_types.h +++ b/source/blender/makesdna/DNA_object_types.h @@ -675,6 +675,7 @@ typedef enum ObjectMode { OB_MODE_TEXTURE_PAINT = 1 << 4, OB_MODE_PARTICLE_EDIT = 1 << 5, OB_MODE_POSE = 1 << 6, + OB_MODE_GPENCIL = 1 << 7, /* NOTE: Just a dummy to make the UI nicer */ } ObjectMode; /* any mode where the brush system is used */ diff --git a/source/blender/makesdna/DNA_scene_types.h b/source/blender/makesdna/DNA_scene_types.h index 5e3dc6f648d..4052b97bc9c 100644 --- a/source/blender/makesdna/DNA_scene_types.h +++ b/source/blender/makesdna/DNA_scene_types.h @@ -911,6 +911,7 @@ typedef enum StereoViews { STEREO_MONO_ID = 3, } StereoViews; +/* *************************************************************** */ /* Markers */ typedef struct TimeMarker { @@ -1041,6 +1042,7 @@ typedef struct Sculpt { typedef struct UvSculpt { Paint paint; } UvSculpt; + /* ------------------------------------------- */ /* Vertex Paint */ @@ -1066,6 +1068,65 @@ enum { VP_ONLYVGROUP = (1 << 7) /* weight paint only */ }; +/* ------------------------------------------- */ +/* GPencil Stroke Sculpting */ + +/* Brush types */ +typedef enum eGP_EditBrush_Types { + GP_EDITBRUSH_TYPE_SMOOTH = 0, + GP_EDITBRUSH_TYPE_THICKNESS = 1, + GP_EDITBRUSH_TYPE_GRAB = 2, + GP_EDITBRUSH_TYPE_PUSH = 3, + GP_EDITBRUSH_TYPE_TWIST = 4, + GP_EDITBRUSH_TYPE_PINCH = 5, + GP_EDITBRUSH_TYPE_RANDOMISE = 6, + GP_EDITBRUSH_TYPE_SUBDIVIDE = 7, + GP_EDITBRUSH_TYPE_SIMPLIFY = 8, + GP_EDITBRUSH_TYPE_CLONE = 9, + + /* !!! Update GP_EditBrush_Data brush[###]; below !!! */ + TOT_GP_EDITBRUSH_TYPES +} eGP_EditBrush_Types; + + +/* Settings for a GPencil Stroke Sculpting Brush */ +typedef struct GP_EditBrush_Data { + short size; /* radius of brush */ + short flag; /* eGP_EditBrush_Flag */ + float strength; /* strength of effect */ +} GP_EditBrush_Data; + +/* GP_EditBrush_Data.flag */ +typedef enum eGP_EditBrush_Flag { + /* invert the effect of the brush */ + GP_EDITBRUSH_FLAG_INVERT = (1 << 0), + /* adjust strength using pen pressure */ + GP_EDITBRUSH_FLAG_USE_PRESSURE = (1 << 1), + + /* strength of brush falls off with distance from cursor */ + GP_EDITBRUSH_FLAG_USE_FALLOFF = (1 << 2), + + /* smooth brush affects pressure values as well */ + GP_EDITBRUSH_FLAG_SMOOTH_PRESSURE = (1 << 3) +} eGP_EditBrush_Flag; + + + +/* GPencil Stroke Sculpting Settings */ +typedef struct GP_BrushEdit_Settings { + GP_EditBrush_Data brush[10]; /* TOT_GP_EDITBRUSH_TYPES */ + void *paintcursor; /* runtime */ + + int brushtype; /* eGP_EditBrush_Types */ + int flag; /* eGP_BrushEdit_SettingsFlag */ +} GP_BrushEdit_Settings; + +/* GP_BrushEdit_Settings.flag */ +typedef enum eGP_BrushEdit_SettingsFlag { + /* only affect selected points */ + GP_BRUSHEDIT_FLAG_SELECT_MASK = (1 << 0) +} eGP_BrushEdit_SettingsFlag; + /* *************************************************************** */ /* Transform Orientations */ @@ -1172,6 +1233,10 @@ typedef enum { UNIFIED_PAINT_BRUSH_ALPHA_PRESSURE = (1 << 4) } UnifiedPaintSettingsFlags; +/* *************************************************************** */ +/* Stats */ + +/* Stats for Meshes */ typedef struct MeshStatVis { char type; char _pad1[2]; @@ -1228,7 +1293,13 @@ typedef struct ToolSettings { char gpencil_flags; /* flags/options for how the tool works */ char gpencil_src; /* for main 3D view Grease Pencil, where data comes from */ - char pad[4]; + char gpencil_v3d_align; /* stroke placement settings: 3D View */ + char gpencil_v2d_align; /* : General 2D Editor */ + char gpencil_seq_align; /* : Sequencer Preview */ + char gpencil_ima_align; /* : Image Editor */ + + /* Grease Pencil Sculpt */ + struct GP_BrushEdit_Settings gp_sculpt; /* Image Paint (8 byttse aligned please!) */ struct ImagePaintSettings imapaint; @@ -1917,7 +1988,12 @@ typedef enum ImagePaintMode { #define EDGE_MODE_TAG_FREESTYLE 5 /* toolsettings->gpencil_flags */ -#define GP_TOOL_FLAG_PAINTSESSIONS_ON (1<<0) +typedef enum eGPencil_Flags { + /* "Continuous Drawing" - The drawing operator enters a mode where multiple strokes can be drawn */ + GP_TOOL_FLAG_PAINTSESSIONS_ON = (1 << 0), + /* When creating new frames, the last frame gets used as the basis for the new one */ + GP_TOOL_FLAG_RETAIN_LAST = (1 << 1), +} eGPencil_Flags; /* toolsettings->gpencil_src */ typedef enum eGPencil_Source_3D { @@ -1925,6 +2001,22 @@ typedef enum eGPencil_Source_3D { GP_TOOL_SOURCE_OBJECT = 1 } eGPencil_Source_3d; +/* toolsettings->gpencil_*_align - Stroke Placement mode flags */ +typedef enum eGPencil_Placement_Flags { + /* New strokes are added in viewport/data space (i.e. not screen space) */ + GP_PROJECT_VIEWSPACE = (1 << 0), + + /* Viewport space, but relative to render canvas (Sequencer Preview Only) */ + GP_PROJECT_CANVAS = (1 << 1), + + /* Project into the screen's Z values */ + GP_PROJECT_DEPTH_VIEW = (1 << 2), + GP_PROJECT_DEPTH_STROKE = (1 << 3), + + /* "Use Endpoints" */ + GP_PROJECT_DEPTH_STROKE_ENDPOINTS = (1 << 4), +} eGPencil_Placement_Flags; + /* toolsettings->particle flag */ #define PE_KEEP_LENGTHS 1 #define PE_LOCK_FIRST 2 diff --git a/source/blender/makesrna/RNA_access.h b/source/blender/makesrna/RNA_access.h index 480d34ae34e..8b785c8a65f 100644 --- a/source/blender/makesrna/RNA_access.h +++ b/source/blender/makesrna/RNA_access.h @@ -259,6 +259,8 @@ extern StructRNA RNA_GPencilFrame; extern StructRNA RNA_GPencilLayer; extern StructRNA RNA_GPencilStroke; extern StructRNA RNA_GPencilStrokePoint; +extern StructRNA RNA_GPencilSculptSettings; +extern StructRNA RNA_GPencilSculptBrush; extern StructRNA RNA_GameBooleanProperty; extern StructRNA RNA_GameFloatProperty; extern StructRNA RNA_GameIntProperty; diff --git a/source/blender/makesrna/RNA_enum_types.h b/source/blender/makesrna/RNA_enum_types.h index 0066236e3f0..0576820e08f 100644 --- a/source/blender/makesrna/RNA_enum_types.h +++ b/source/blender/makesrna/RNA_enum_types.h @@ -111,6 +111,8 @@ extern EnumPropertyItem rna_enum_brush_sculpt_tool_items[]; extern EnumPropertyItem rna_enum_brush_vertex_tool_items[]; extern EnumPropertyItem rna_enum_brush_image_tool_items[]; +extern EnumPropertyItem rna_enum_gpencil_sculpt_brush_items[]; + extern EnumPropertyItem rna_enum_symmetrize_direction_items[]; extern EnumPropertyItem rna_enum_texture_type_items[]; diff --git a/source/blender/makesrna/intern/rna_action.c b/source/blender/makesrna/intern/rna_action.c index 9ae9f11b462..5d90b9fcae8 100644 --- a/source/blender/makesrna/intern/rna_action.c +++ b/source/blender/makesrna/intern/rna_action.c @@ -495,6 +495,14 @@ static void rna_def_dopesheet(BlenderRNA *brna) RNA_def_property_ui_text(prop, "Display Grease Pencil", "Include visualization of Grease Pencil related animation data and frames"); RNA_def_property_ui_icon(prop, ICON_GREASEPENCIL, 0); RNA_def_property_update(prop, NC_ANIMATION | ND_ANIMCHAN | NA_EDITED, NULL); + + /* GPencil Mode Settings */ + prop = RNA_def_property(srna, "show_gpencil_3d_only", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, NULL, "filterflag", ADS_FILTER_GP_3DONLY); + RNA_def_property_ui_text(prop, "Active Scene Only", + "Only show Grease Pencil datablocks used as part of the active scene"); + RNA_def_property_ui_icon(prop, ICON_SCENE_DATA, 0); + RNA_def_property_update(prop, NC_ANIMATION | ND_ANIMCHAN | NA_EDITED, NULL); } static void rna_def_action_group(BlenderRNA *brna) diff --git a/source/blender/makesrna/intern/rna_gpencil.c b/source/blender/makesrna/intern/rna_gpencil.c index 80a52a0a3d1..6367b8a4738 100644 --- a/source/blender/makesrna/intern/rna_gpencil.c +++ b/source/blender/makesrna/intern/rna_gpencil.c @@ -50,11 +50,46 @@ #include "BKE_gpencil.h" +#include "DNA_object_types.h" + + static void rna_GPencil_update(Main *UNUSED(bmain), Scene *UNUSED(scene), PointerRNA *UNUSED(ptr)) { WM_main_add_notifier(NC_GPENCIL | NA_EDITED, NULL); } +static void rna_GPencil_editmode_update(Main *UNUSED(bmain), Scene *scene, PointerRNA *ptr) +{ + /* Notify all places where GPencil data lives that the editing state is different */ + WM_main_add_notifier(NC_GPENCIL | NA_EDITED, NULL); + WM_main_add_notifier(NC_SCENE | ND_MODE, NULL); +} + +static void rna_GPencil_onion_skinning_update(Main *bmain, Scene *scene, PointerRNA *ptr) +{ + bGPdata *gpd = (bGPdata *)ptr->id.data; + bGPDlayer *gpl; + bool enabled = false; + + /* Ensure that the datablock's onionskinning toggle flag + * stays in sync with the status of the actual layers + */ + for (gpl = gpd->layers.first; gpl; gpl = gpl->next) { + if (gpl->flag & GP_LAYER_ONIONSKIN) { + enabled = true; + } + } + + if (enabled) + gpd->flag |= GP_DATA_SHOW_ONIONSKINS; + else + gpd->flag &= ~GP_DATA_SHOW_ONIONSKINS; + + + /* Now do standard updates... */ + rna_GPencil_update(bmain, scene, ptr); +} + static char *rna_GPencilLayer_path(PointerRNA *ptr) { bGPDlayer *gpl = (bGPDlayer *)ptr->data; @@ -201,6 +236,30 @@ static void rna_GPencilLayer_info_set(PointerRNA *ptr, const char *value) BLI_uniquename(&gpd->layers, gpl, DATA_("GP_Layer"), '.', offsetof(bGPDlayer, info), sizeof(gpl->info)); } +static void rna_GPencil_use_onion_skinning_set(PointerRNA *ptr, const int value) +{ + bGPdata *gpd = ptr->id.data; + bGPDlayer *gpl; + + /* set new value */ + if (value) { + /* enable on active layer (it's the one that's most likely to be of interest right now) */ + gpl = gpencil_layer_getactive(gpd); + if (gpl) { + gpl->flag |= GP_LAYER_ONIONSKIN; + } + + gpd->flag |= GP_DATA_SHOW_ONIONSKINS; + } + else { + /* disable on all layers - allowa quickly turning them all off, without having to check */ + for (gpl = gpd->layers.first; gpl; gpl = gpl->next) { + gpl->flag &= ~GP_LAYER_ONIONSKIN; + } + + gpd->flag &= ~GP_DATA_SHOW_ONIONSKINS; + } +} static bGPDstroke *rna_GPencil_stroke_point_find_stroke(const bGPdata *gpd, const bGPDspoint *pt, bGPDlayer **r_gpl, bGPDframe **r_gpf) { @@ -701,7 +760,7 @@ static void rna_def_gpencil_layer(BlenderRNA *brna) prop = RNA_def_property(srna, "use_onion_skinning", PROP_BOOLEAN, PROP_NONE); RNA_def_property_boolean_sdna(prop, NULL, "flag", GP_LAYER_ONIONSKIN); RNA_def_property_ui_text(prop, "Onion Skinning", "Ghost frames on either side of frame"); - RNA_def_property_update(prop, NC_GPENCIL | ND_DATA, "rna_GPencil_update"); + RNA_def_property_update(prop, NC_GPENCIL | ND_DATA, "rna_GPencil_onion_skinning_update"); prop = RNA_def_property(srna, "ghost_before_range", PROP_INT, PROP_NONE); RNA_def_property_int_sdna(prop, NULL, "gstep"); @@ -849,14 +908,6 @@ static void rna_def_gpencil_data(BlenderRNA *brna) PropertyRNA *prop; FunctionRNA *func; - static EnumPropertyItem draw_mode_items[] = { - {GP_DATA_VIEWALIGN, "CURSOR", 0, "Cursor", "Draw stroke at the 3D cursor"}, - {0, "VIEW", 0, "View", "Stick stroke to the view "}, /* weird, GP_DATA_VIEWALIGN is inverted */ - {GP_DATA_VIEWALIGN | GP_DATA_DEPTH_VIEW, "SURFACE", 0, "Surface", "Stick stroke to surfaces"}, - {GP_DATA_VIEWALIGN | GP_DATA_DEPTH_STROKE, "STROKE", 0, "Stroke", "Stick stroke to other strokes"}, - {0, NULL, 0, NULL, NULL} - }; - srna = RNA_def_struct(brna, "GreasePencil", "ID"); RNA_def_struct_sdna(srna, "bGPdata"); RNA_def_struct_ui_text(srna, "Grease Pencil", "Freehand annotation sketchbook"); @@ -873,21 +924,17 @@ static void rna_def_gpencil_data(BlenderRNA *brna) rna_def_animdata_common(srna); /* Flags */ - prop = RNA_def_property(srna, "draw_mode", PROP_ENUM, PROP_NONE); - RNA_def_property_enum_bitflag_sdna(prop, NULL, "flag"); - RNA_def_property_enum_items(prop, draw_mode_items); - RNA_def_property_ui_text(prop, "Draw Mode", ""); - RNA_def_property_update(prop, NC_GPENCIL | ND_DATA, NULL); - - prop = RNA_def_property(srna, "use_stroke_endpoints", PROP_BOOLEAN, PROP_NONE); - RNA_def_property_boolean_sdna(prop, NULL, "flag", GP_DATA_DEPTH_STROKE_ENDPOINTS); - RNA_def_property_ui_text(prop, "Only Endpoints", "Only use the first and last parts of the stroke for snapping"); - RNA_def_property_update(prop, NC_GPENCIL | ND_DATA, NULL); - prop = RNA_def_property(srna, "use_stroke_edit_mode", PROP_BOOLEAN, PROP_NONE); RNA_def_property_boolean_sdna(prop, NULL, "flag", GP_DATA_STROKE_EDITMODE); - RNA_def_property_ui_text(prop, "Stroke Edit Mode", "Enable alternative keymap to make editing stroke points easier"); - RNA_def_property_update(prop, NC_GPENCIL | ND_DATA | ND_GPENCIL_EDITMODE, "rna_GPencil_update"); + RNA_def_property_ui_text(prop, "Stroke Edit Mode", "Edit Grease Pencil strokes instead of viewport data"); + RNA_def_property_update(prop, NC_GPENCIL | ND_DATA | ND_GPENCIL_EDITMODE, "rna_GPencil_editmode_update"); + + prop = RNA_def_property(srna, "use_onion_skinning", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, NULL, "flag", GP_DATA_SHOW_ONIONSKINS); + RNA_def_property_boolean_funcs(prop, NULL, "rna_GPencil_use_onion_skinning_set"); + RNA_def_property_ui_text(prop, "Onion Skins", + "Show ghosts of the frames before and after the current frame, toggle to enable on active layer or disable all"); + RNA_def_property_update(prop, NC_GPENCIL | ND_DATA, NULL); /* API Functions */ func = RNA_def_function(srna, "clear", "rna_GPencil_clear"); diff --git a/source/blender/makesrna/intern/rna_object.c b/source/blender/makesrna/intern/rna_object.c index 96749e32e45..81b1e4ac4be 100644 --- a/source/blender/makesrna/intern/rna_object.c +++ b/source/blender/makesrna/intern/rna_object.c @@ -69,6 +69,7 @@ EnumPropertyItem rna_enum_object_mode_items[] = { {OB_MODE_WEIGHT_PAINT, "WEIGHT_PAINT", ICON_WPAINT_HLT, "Weight Paint", ""}, {OB_MODE_TEXTURE_PAINT, "TEXTURE_PAINT", ICON_TPAINT_HLT, "Texture Paint", ""}, {OB_MODE_PARTICLE_EDIT, "PARTICLE_EDIT", ICON_PARTICLEMODE, "Particle Edit", ""}, + {OB_MODE_GPENCIL, "GPENCIL_EDIT", ICON_GREASEPENCIL, "Edit Strokes", "Edit Grease Pencil Strokes"}, {0, NULL, 0, NULL, NULL} }; diff --git a/source/blender/makesrna/intern/rna_scene.c b/source/blender/makesrna/intern/rna_scene.c index 4f9fe8b45ab..2624e351c74 100644 --- a/source/blender/makesrna/intern/rna_scene.c +++ b/source/blender/makesrna/intern/rna_scene.c @@ -2057,11 +2057,19 @@ static void rna_def_tool_settings(BlenderRNA *brna) "unless the active object already has Grease Pencil data (i.e. for old files)"}, {GP_TOOL_SOURCE_OBJECT, "OBJECT", 0, "Object", "Grease Pencil data-blocks attached to the active object are used " - "(required using pre 2.73 add-ons, e.g. BSurfaces)"}, + "(required when using pre 2.73 add-ons, e.g. BSurfaces)"}, {0, NULL, 0, NULL, NULL} }; - - + + static EnumPropertyItem gpencil_stroke_placement_items[] = { + {GP_PROJECT_VIEWSPACE, "CURSOR", 0, "Cursor", "Draw stroke at the 3D cursor"}, + {0, "VIEW", 0, "View", "Stick stroke to the view "}, /* weird, GP_PROJECT_VIEWALIGN is inverted */ + {GP_PROJECT_VIEWSPACE | GP_PROJECT_DEPTH_VIEW, "SURFACE", 0, "Surface", "Stick stroke to surfaces"}, + {GP_PROJECT_VIEWSPACE | GP_PROJECT_DEPTH_STROKE, "STROKE", 0, "Stroke", "Stick stroke to other strokes"}, + {0, NULL, 0, NULL, NULL} + }; + + srna = RNA_def_struct(brna, "ToolSettings", NULL); RNA_def_struct_path_func(srna, "rna_ToolSettings_path"); RNA_def_struct_ui_text(srna, "Tool Settings", ""); @@ -2269,12 +2277,19 @@ static void rna_def_tool_settings(BlenderRNA *brna) RNA_def_property_update(prop, NC_SCENE | ND_TOOLSETTINGS, NULL); /* header redraw */ /* Grease Pencil */ - prop = RNA_def_property(srna, "use_grease_pencil_sessions", PROP_BOOLEAN, PROP_NONE); + prop = RNA_def_property(srna, "use_gpencil_continuous_drawing", PROP_BOOLEAN, PROP_NONE); RNA_def_property_boolean_sdna(prop, NULL, "gpencil_flags", GP_TOOL_FLAG_PAINTSESSIONS_ON); - RNA_def_property_ui_text(prop, "Use Sketching Sessions", + RNA_def_property_ui_text(prop, "Use Continuous Drawing", "Allow drawing multiple strokes at a time with Grease Pencil"); RNA_def_property_update(prop, NC_SCENE | ND_TOOLSETTINGS, NULL); /* xxx: need toolbar to be redrawn... */ + prop = RNA_def_property(srna, "use_gpencil_additive_drawing", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, NULL, "gpencil_flags", GP_TOOL_FLAG_RETAIN_LAST); + RNA_def_property_ui_text(prop, "Use Additive Drawing", + "When creating new frames, the strokes from the previous/active frame " + "are included as the basis for the new one"); + RNA_def_property_update(prop, NC_SCENE | ND_TOOLSETTINGS, NULL); + prop = RNA_def_property(srna, "grease_pencil_source", PROP_ENUM, PROP_NONE); RNA_def_property_enum_bitflag_sdna(prop, NULL, "gpencil_src"); RNA_def_property_enum_items(prop, gpencil_source_3d_items); @@ -2282,6 +2297,45 @@ static void rna_def_tool_settings(BlenderRNA *brna) "Datablock where active Grease Pencil data is found from"); RNA_def_property_update(prop, NC_GPENCIL | ND_DATA, NULL); + prop = RNA_def_property(srna, "gpencil_sculpt", PROP_POINTER, PROP_NONE); + RNA_def_property_pointer_sdna(prop, NULL, "gp_sculpt"); + RNA_def_property_struct_type(prop, "GPencilSculptSettings"); + RNA_def_property_ui_text(prop, "Grease Pencil Sculpt", ""); + + /* Grease Pencil - 3D View Stroke Placement */ + prop = RNA_def_property(srna, "gpencil_stroke_placement_view3d", PROP_ENUM, PROP_NONE); + RNA_def_property_enum_bitflag_sdna(prop, NULL, "gpencil_v3d_align"); + RNA_def_property_enum_items(prop, gpencil_stroke_placement_items); + RNA_def_property_ui_text(prop, "Stroke Placement (3D View)", ""); + RNA_def_property_update(prop, NC_GPENCIL | ND_DATA, NULL); + + prop = RNA_def_property(srna, "use_gpencil_stroke_endpoints", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, NULL, "gpencil_v3d_align", GP_PROJECT_DEPTH_STROKE_ENDPOINTS); + RNA_def_property_ui_text(prop, "Only Endpoints", "Only use the first and last parts of the stroke for snapping"); + RNA_def_property_update(prop, NC_GPENCIL | ND_DATA, NULL); + + /* Grease Pencil - 2D Views Stroke Placement */ + prop = RNA_def_property(srna, "gpencil_stroke_placement_view2d", PROP_ENUM, PROP_NONE); + RNA_def_property_enum_bitflag_sdna(prop, NULL, "gpencil_v2d_align"); + RNA_def_property_enum_items(prop, gpencil_stroke_placement_items); + RNA_def_property_ui_text(prop, "Stroke Placement (2D View)", ""); + RNA_def_property_update(prop, NC_GPENCIL | ND_DATA, NULL); + + /* Grease Pencil - Sequencer Preview Stroke Placement */ + prop = RNA_def_property(srna, "gpencil_stroke_placement_sequencer_preview", PROP_ENUM, PROP_NONE); + RNA_def_property_enum_bitflag_sdna(prop, NULL, "gpencil_seq_align"); + RNA_def_property_enum_items(prop, gpencil_stroke_placement_items); + RNA_def_property_ui_text(prop, "Stroke Placement (Sequencer Preview)", ""); + RNA_def_property_update(prop, NC_GPENCIL | ND_DATA, NULL); + + /* Grease Pencil - Image Editor Stroke Placement */ + prop = RNA_def_property(srna, "gpencil_stroke_placement_image_editor", PROP_ENUM, PROP_NONE); + RNA_def_property_enum_bitflag_sdna(prop, NULL, "gpencil_ima_align"); + RNA_def_property_enum_items(prop, gpencil_stroke_placement_items); + RNA_def_property_ui_text(prop, "Stroke Placement (Image Editor)", ""); + RNA_def_property_update(prop, NC_GPENCIL | ND_DATA, NULL); + + /* Auto Keying */ prop = RNA_def_property(srna, "use_keyframe_insert_auto", PROP_BOOLEAN, PROP_NONE); RNA_def_property_boolean_sdna(prop, NULL, "autokey_mode", AUTOKEY_ON); diff --git a/source/blender/makesrna/intern/rna_sculpt_paint.c b/source/blender/makesrna/intern/rna_sculpt_paint.c index 9e70d6ce3d6..94161d95426 100644 --- a/source/blender/makesrna/intern/rna_sculpt_paint.c +++ b/source/blender/makesrna/intern/rna_sculpt_paint.c @@ -60,6 +60,20 @@ static EnumPropertyItem particle_edit_hair_brush_items[] = { {0, NULL, 0, NULL, NULL} }; +EnumPropertyItem rna_enum_gpencil_sculpt_brush_items[] = { + {GP_EDITBRUSH_TYPE_SMOOTH, "SMOOTH", 0, "Smooth", "Smooth stroke points"}, + {GP_EDITBRUSH_TYPE_THICKNESS, "THICKNESS", 0, "Thickness", "Adjust thickness of strokes"}, + {GP_EDITBRUSH_TYPE_GRAB, "GRAB", 0, "Grab", "Translate the set of points initially within the brush circle"}, + {GP_EDITBRUSH_TYPE_PUSH, "PUSH", 0, "Push", "Move points out of the way, as if combing them"}, + {GP_EDITBRUSH_TYPE_TWIST, "TWIST", 0, "Twist", "Rotate points around the midpoint of the brush"}, + {GP_EDITBRUSH_TYPE_PINCH, "PINCH", 0, "Pinch", "Pull points towards the midpoint of the brush"}, + {GP_EDITBRUSH_TYPE_RANDOMISE, "RANDOMISE", 0, "Randomise", "Introduce jitter/randomness into strokes"}, + //{GP_EDITBRUSH_TYPE_SUBDIVIDE, "SUBDIVIDE", 0, "Subdivide", "Increase point density for higher resolution strokes when zoomed in"}, + //{GP_EDITBRUSH_TYPE_SIMPLIFY, "SIMPLIFY", 0, "Simplify", "Reduce density of stroke points"}, + {GP_EDITBRUSH_TYPE_CLONE, "CLONE", 0, "Clone", "Paste copies of the strokes stored on the clipboard"}, + {0, NULL, 0, NULL, NULL} +}; + EnumPropertyItem rna_enum_symmetrize_direction_items[] = { {BMO_SYMMETRIZE_NEGATIVE_X, "NEGATIVE_X", 0, "-X to +X", ""}, {BMO_SYMMETRIZE_POSITIVE_X, "POSITIVE_X", 0, "+X to -X", ""}, @@ -359,6 +373,30 @@ static int rna_ImaPaint_detect_data(ImagePaintSettings *imapaint) { return imapaint->missing_data == 0; } + + +static PointerRNA rna_GPencilSculptSettings_brush_get(PointerRNA *ptr) +{ + GP_BrushEdit_Settings *gset = (GP_BrushEdit_Settings *)ptr->data; + GP_EditBrush_Data *brush = NULL; + + if ((gset->brushtype >= 0) && (gset->brushtype < TOT_GP_EDITBRUSH_TYPES)) + brush = &gset->brush[gset->brushtype]; + + return rna_pointer_inherit_refine(ptr, &RNA_GPencilSculptBrush, brush); +} + +static char *rna_GPencilSculptSettings_path(PointerRNA *UNUSED(ptr)) +{ + return BLI_strdup("tool_settings.gpencil_sculpt"); +} + +static char *rna_GPencilSculptBrush_path(PointerRNA *UNUSED(ptr)) +{ + return BLI_strdup("tool_settings.gpencil_sculpt.brush"); +} + + #else static void rna_def_paint_curve(BlenderRNA *brna) @@ -945,6 +983,72 @@ static void rna_def_particle_edit(BlenderRNA *brna) RNA_def_property_ui_text(prop, "Curve", ""); } +static void rna_def_gpencil_sculpt(BlenderRNA *brna) +{ + static EnumPropertyItem prop_direction_items[]= { + {0, "ADD", 0, "Add", "Add effect of brush"}, + {GP_EDITBRUSH_FLAG_INVERT, "SUBTRACT", 0, "Subtract", "Subtract effect of brush"}, + {0, NULL, 0, NULL, NULL}}; + + StructRNA *srna; + PropertyRNA *prop; + + /* == Settings == */ + srna = RNA_def_struct(brna, "GPencilSculptSettings", NULL); + RNA_def_struct_sdna(srna, "GP_BrushEdit_Settings"); + RNA_def_struct_path_func(srna, "rna_GPencilSculptSettings_path"); + RNA_def_struct_ui_text(srna, "GPencil Sculpt Settings", "Properties for Grease Pencil stroke sculpting tool"); + + prop = RNA_def_property(srna, "tool", PROP_ENUM, PROP_NONE); + RNA_def_property_enum_sdna(prop, NULL, "brushtype"); + RNA_def_property_enum_items(prop, rna_enum_gpencil_sculpt_brush_items); + RNA_def_property_ui_text(prop, "Tool", ""); + + prop = RNA_def_property(srna, "brush", PROP_POINTER, PROP_NONE); + RNA_def_property_struct_type(prop, "GPencilSculptBrush"); + RNA_def_property_pointer_funcs(prop, "rna_GPencilSculptSettings_brush_get", NULL, NULL, NULL); + RNA_def_property_ui_text(prop, "Brush", ""); + + prop = RNA_def_property(srna, "use_select_mask", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, NULL, "flag", GP_BRUSHEDIT_FLAG_SELECT_MASK); + RNA_def_property_ui_text(prop, "Selection Mask", "Only sculpt selected stroke points"); + RNA_def_property_ui_icon(prop, ICON_VERTEXSEL, 0); // FIXME: this needs a custom icon + + + /* brush */ + srna = RNA_def_struct(brna, "GPencilSculptBrush", NULL); + RNA_def_struct_sdna(srna, "GP_EditBrush_Data"); + RNA_def_struct_path_func(srna, "rna_GPencilSculptBrush_path"); + RNA_def_struct_ui_text(srna, "GPencil Sculpt Brush", "Stroke editing brush"); + + prop = RNA_def_property(srna, "size", PROP_INT, PROP_PIXEL); + RNA_def_property_range(prop, 1, MAX_BRUSH_PIXEL_RADIUS); + RNA_def_property_ui_range(prop, 1, 100, 10, 3); // XXX: too big + RNA_def_property_ui_text(prop, "Radius", "Radius of the brush in pixels"); + + prop = RNA_def_property(srna, "strength", PROP_FLOAT, PROP_FACTOR); + RNA_def_property_range(prop, 0.001, 1.0); + RNA_def_property_ui_text(prop, "Strength", "Brush strength"); + + prop = RNA_def_property(srna, "use_pressure_strength", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, NULL, "flag", GP_EDITBRUSH_FLAG_USE_PRESSURE); + RNA_def_property_ui_icon(prop, ICON_STYLUS_PRESSURE, 0); + RNA_def_property_ui_text(prop, "Strength Pressure", "Enable tablet pressure sensitivity for strength"); + + prop = RNA_def_property(srna, "use_falloff", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, NULL, "flag", GP_EDITBRUSH_FLAG_USE_FALLOFF); + RNA_def_property_ui_text(prop, "Use Falloff", "Strength of brush decays with distance from cursor"); + + prop = RNA_def_property(srna, "affect_pressure", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, NULL, "flag", GP_EDITBRUSH_FLAG_SMOOTH_PRESSURE); + RNA_def_property_ui_text(prop, "Affect Pressure", "Affect pressure values as well when smoothing strokes"); + + prop = RNA_def_property(srna, "direction", PROP_ENUM, PROP_NONE); + RNA_def_property_enum_bitflag_sdna(prop, NULL, "flag"); + RNA_def_property_enum_items(prop, prop_direction_items); + RNA_def_property_ui_text(prop, "Direction", ""); +} + void RNA_def_sculpt_paint(BlenderRNA *brna) { /* *** Non-Animated *** */ @@ -956,6 +1060,7 @@ void RNA_def_sculpt_paint(BlenderRNA *brna) rna_def_vertex_paint(brna); rna_def_image_paint(brna); rna_def_particle_edit(brna); + rna_def_gpencil_sculpt(brna); RNA_define_animate_sdna(true); } diff --git a/source/blender/makesrna/intern/rna_wm.c b/source/blender/makesrna/intern/rna_wm.c index 20e9d64a47a..8e01e15b9a3 100644 --- a/source/blender/makesrna/intern/rna_wm.c +++ b/source/blender/makesrna/intern/rna_wm.c @@ -86,6 +86,9 @@ static EnumPropertyItem event_mouse_type_items[] = { {ACTIONMOUSE, "ACTIONMOUSE", 0, "Action", ""}, {SELECTMOUSE, "SELECTMOUSE", 0, "Select", ""}, {0, "", 0, NULL, NULL}, + {TABLET_STYLUS, "PEN", 0, "Pen", ""}, + {TABLET_ERASER, "ERASER", 0, "Eraser", ""}, + {0, "", 0, NULL, NULL}, {MOUSEMOVE, "MOUSEMOVE", 0, "Move", ""}, {MOUSEPAN, "TRACKPADPAN", 0, "Mouse/Trackpad Pan", ""}, {MOUSEZOOM, "TRACKPADZOOM", 0, "Mouse/Trackpad Zoom", ""}, @@ -180,6 +183,9 @@ EnumPropertyItem rna_enum_event_type_items[] = { {ACTIONMOUSE, "ACTIONMOUSE", 0, "Action Mouse", "MBA"}, {SELECTMOUSE, "SELECTMOUSE", 0, "Select Mouse", "MBS"}, {0, "", 0, NULL, NULL}, + {TABLET_STYLUS, "PEN", 0, "Pen", ""}, + {TABLET_ERASER, "ERASER", 0, "Eraser", ""}, + {0, "", 0, NULL, NULL}, {MOUSEMOVE, "MOUSEMOVE", 0, "Mouse Move", "MsMov"}, {INBETWEEN_MOUSEMOVE, "INBETWEEN_MOUSEMOVE", 0, "In-between Move", "MsSubMov"}, {MOUSEPAN, "TRACKPADPAN", 0, "Mouse/Trackpad Pan", "MsPan"}, diff --git a/source/blender/windowmanager/intern/wm_event_system.c b/source/blender/windowmanager/intern/wm_event_system.c index b2debf643d0..b19030a27a6 100644 --- a/source/blender/windowmanager/intern/wm_event_system.c +++ b/source/blender/windowmanager/intern/wm_event_system.c @@ -1538,8 +1538,24 @@ static int wm_eventmatch(wmEvent *winevent, wmKeyMapItem *kmi) if (ISKEYBOARD(winevent->type) && (winevent->ascii || winevent->utf8_buf[0])) return 1; } - if (kmitype != KM_ANY) - if (winevent->type != kmitype) return 0; + if (kmitype != KM_ANY) { + if (ELEM(kmitype, TABLET_STYLUS, TABLET_ERASER)) { + const wmTabletData *wmtab = winevent->tablet_data; + + if (wmtab == NULL) + return 0; + else if (winevent->type != LEFTMOUSE) /* tablet events can occur on hover + keypress */ + return 0; + else if ((kmitype == TABLET_STYLUS) && (wmtab->Active != EVT_TABLET_STYLUS)) + return 0; + else if ((kmitype == TABLET_ERASER) && (wmtab->Active != EVT_TABLET_ERASER)) + return 0; + } + else { + if (winevent->type != kmitype) + return 0; + } + } if (kmi->val != KM_ANY) if (winevent->val != kmi->val) return 0; diff --git a/source/blender/windowmanager/intern/wm_init_exit.c b/source/blender/windowmanager/intern/wm_init_exit.c index 94941e92430..966f0fa66a1 100644 --- a/source/blender/windowmanager/intern/wm_init_exit.c +++ b/source/blender/windowmanager/intern/wm_init_exit.c @@ -513,6 +513,7 @@ void WM_exit_ext(bContext *C, const bool do_python) free_anim_copybuf(); free_anim_drivers_copybuf(); free_fmodifiers_copybuf(); + ED_gpencil_anim_copybuf_free(); ED_gpencil_strokes_copybuf_free(); ED_clipboard_posebuf_free(); BKE_node_clipboard_clear(); diff --git a/source/blender/windowmanager/intern/wm_keymap.c b/source/blender/windowmanager/intern/wm_keymap.c index 6c451525742..72b26cc6207 100644 --- a/source/blender/windowmanager/intern/wm_keymap.c +++ b/source/blender/windowmanager/intern/wm_keymap.c @@ -202,6 +202,9 @@ int WM_keymap_map_type_get(wmKeyMapItem *kmi) if (kmi->type == KM_TEXTINPUT) { return KMI_TYPE_TEXTINPUT; } + if (ELEM(kmi->type, TABLET_STYLUS, TABLET_ERASER)) { + return KMI_TYPE_MOUSE; + } return KMI_TYPE_KEYBOARD; } diff --git a/source/blender/windowmanager/wm_event_types.h b/source/blender/windowmanager/wm_event_types.h index 390e769aa88..c32ded28126 100644 --- a/source/blender/windowmanager/wm_event_types.h +++ b/source/blender/windowmanager/wm_event_types.h @@ -92,6 +92,10 @@ enum { WM_IME_COMPOSITE_EVENT = 0x0015, /* IME event, GHOST_kEventImeCompositionEnd in ghost */ WM_IME_COMPOSITE_END = 0x0016, + + /* Tablet/Pen Specific Events */ + TABLET_STYLUS = 0x001a, + TABLET_ERASER = 0x001b, /* *** Start of keyboard codes. *** */ -- cgit v1.2.3