From 29f3af95272590d26f610ae828b2eeee89c82a00 Mon Sep 17 00:00:00 2001 From: Antonio Vazquez Date: Mon, 9 Mar 2020 16:27:24 +0100 Subject: GPencil: Refactor of Draw Engine, Vertex Paint and all internal functions This commit is a full refactor of the grease pencil modules including Draw Engine, Modifiers, VFX, depsgraph update, improvements in operators and conversion of Sculpt and Weight paint tools to real brushes. Also, a huge code cleanup has been done at all levels. Thanks to @fclem for his work and yo @pepeland and @mendio for the testing and help in the development. Differential Revision: https://developer.blender.org/D6293 --- .../editors/animation/anim_channels_defines.c | 10 +- .../blender/editors/animation/anim_channels_edit.c | 2 +- source/blender/editors/armature/armature_naming.c | 2 +- source/blender/editors/gpencil/CMakeLists.txt | 6 +- source/blender/editors/gpencil/annotate_draw.c | 37 +- source/blender/editors/gpencil/annotate_paint.c | 21 +- source/blender/editors/gpencil/drawgpencil.c | 304 +-- .../blender/editors/gpencil/editaction_gpencil.c | 58 +- .../blender/editors/gpencil/gpencil_add_monkey.c | 93 +- .../blender/editors/gpencil/gpencil_add_stroke.c | 8 +- source/blender/editors/gpencil/gpencil_armature.c | 4 +- source/blender/editors/gpencil/gpencil_brush.c | 2343 -------------------- source/blender/editors/gpencil/gpencil_convert.c | 104 +- source/blender/editors/gpencil/gpencil_data.c | 571 ++++- source/blender/editors/gpencil/gpencil_edit.c | 493 ++-- source/blender/editors/gpencil/gpencil_fill.c | 170 +- source/blender/editors/gpencil/gpencil_intern.h | 68 +- .../blender/editors/gpencil/gpencil_interpolate.c | 145 +- source/blender/editors/gpencil/gpencil_merge.c | 126 +- source/blender/editors/gpencil/gpencil_ops.c | 316 ++- .../editors/gpencil/gpencil_ops_versioning.c | 17 +- source/blender/editors/gpencil/gpencil_paint.c | 1374 ++++-------- source/blender/editors/gpencil/gpencil_primitive.c | 119 +- .../blender/editors/gpencil/gpencil_sculpt_paint.c | 2150 ++++++++++++++++++ source/blender/editors/gpencil/gpencil_select.c | 202 +- source/blender/editors/gpencil/gpencil_undo.c | 4 +- source/blender/editors/gpencil/gpencil_utils.c | 483 ++-- source/blender/editors/gpencil/gpencil_uv.c | 587 +++++ .../blender/editors/gpencil/gpencil_vertex_ops.c | 899 ++++++++ .../blender/editors/gpencil/gpencil_vertex_paint.c | 1414 ++++++++++++ .../blender/editors/gpencil/gpencil_weight_paint.c | 901 ++++++++ source/blender/editors/include/ED_gpencil.h | 56 +- source/blender/editors/interface/interface.c | 6 +- .../interface/interface_eyedropper_gpencil_color.c | 114 +- .../blender/editors/interface/interface_handlers.c | 41 +- source/blender/editors/interface/interface_icons.c | 53 +- .../blender/editors/interface/interface_layout.c | 2 +- .../editors/interface/interface_templates.c | 48 + source/blender/editors/object/object_add.c | 4 +- source/blender/editors/object/object_edit.c | 3 +- .../editors/object/object_gpencil_modifier.c | 7 +- source/blender/editors/object/object_modes.c | 5 +- source/blender/editors/object/object_transform.c | 10 +- source/blender/editors/screen/area.c | 72 + source/blender/editors/screen/screen_context.c | 10 +- source/blender/editors/sculpt_paint/paint_ops.c | 322 +++ source/blender/editors/space_action/action_edit.c | 2 +- .../blender/editors/space_action/action_select.c | 4 +- .../blender/editors/space_outliner/outliner_draw.c | 5 +- .../editors/space_outliner/outliner_select.c | 2 +- source/blender/editors/space_view3d/space_view3d.c | 6 + .../editors/space_view3d/view3d_gizmo_ruler.c | 13 +- .../blender/editors/space_view3d/view3d_select.c | 3 +- .../blender/editors/transform/transform_convert.c | 10 +- .../editors/transform/transform_convert_gpencil.c | 14 +- .../blender/editors/transform/transform_generics.c | 7 +- .../blender/editors/transform/transform_gizmo_3d.c | 6 +- source/blender/editors/undo/ed_undo.c | 3 +- 58 files changed, 9279 insertions(+), 4580 deletions(-) delete mode 100644 source/blender/editors/gpencil/gpencil_brush.c create mode 100644 source/blender/editors/gpencil/gpencil_sculpt_paint.c create mode 100644 source/blender/editors/gpencil/gpencil_uv.c create mode 100644 source/blender/editors/gpencil/gpencil_vertex_ops.c create mode 100644 source/blender/editors/gpencil/gpencil_vertex_paint.c create mode 100644 source/blender/editors/gpencil/gpencil_weight_paint.c (limited to 'source/blender/editors') diff --git a/source/blender/editors/animation/anim_channels_defines.c b/source/blender/editors/animation/anim_channels_defines.c index d1fd1ebd06f..ebde475a075 100644 --- a/source/blender/editors/animation/anim_channels_defines.c +++ b/source/blender/editors/animation/anim_channels_defines.c @@ -5105,10 +5105,16 @@ void ANIM_channel_draw_widgets(const bContext *C, /* Mask Layer. */ UI_block_emboss_set(block, UI_EMBOSS_NONE); - prop = RNA_struct_find_property(&ptr, "mask_layer"); + prop = RNA_struct_find_property(&ptr, "use_mask_layer"); gp_rna_path = RNA_path_from_ID_to_property(&ptr, prop); if (RNA_path_resolve_property(&id_ptr, gp_rna_path, &ptr, &prop)) { - icon = (gpl->flag & GP_LAYER_USE_MASK) ? ICON_MOD_MASK : ICON_LAYER_ACTIVE; + icon = ICON_LAYER_ACTIVE; + if (gpl->flag & GP_LAYER_USE_MASK) { + icon = ICON_MOD_MASK; + } + else { + icon = ICON_LAYER_ACTIVE; + } uiDefAutoButR(block, &ptr, prop, diff --git a/source/blender/editors/animation/anim_channels_edit.c b/source/blender/editors/animation/anim_channels_edit.c index 20f565990e9..8b5e22db61a 100644 --- a/source/blender/editors/animation/anim_channels_edit.c +++ b/source/blender/editors/animation/anim_channels_edit.c @@ -3135,7 +3135,7 @@ static int mouse_anim_channels(bContext *C, bAnimContext *ac, int channel_index, if (gpl->flag & GP_LAYER_SELECT) { ANIM_set_active_channel(ac, ac->data, ac->datatype, filter, gpl, ANIMTYPE_GPLAYER); /* update other layer status */ - BKE_gpencil_layer_setactive(gpd, gpl); + BKE_gpencil_layer_active_set(gpd, gpl); BKE_gpencil_layer_autolock_set(gpd, false); DEG_id_tag_update(&gpd->id, ID_RECALC_GEOMETRY); } diff --git a/source/blender/editors/armature/armature_naming.c b/source/blender/editors/armature/armature_naming.c index 0b286b4ee2b..1f421d23b27 100644 --- a/source/blender/editors/armature/armature_naming.c +++ b/source/blender/editors/armature/armature_naming.c @@ -299,7 +299,7 @@ void ED_armature_bone_rename(Main *bmain, if (ob->type == OB_GPENCIL) { bGPdata *gpd = (bGPdata *)ob->data; - for (bGPDlayer *gpl = gpd->layers.first; gpl; gpl = gpl->next) { + LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd->layers) { if ((gpl->parent != NULL) && (gpl->parent->data == arm)) { if (STREQ(gpl->parsubstr, oldname)) { BLI_strncpy(gpl->parsubstr, newname, MAXBONENAME); diff --git a/source/blender/editors/gpencil/CMakeLists.txt b/source/blender/editors/gpencil/CMakeLists.txt index 21f1801f7eb..512e1ec6270 100644 --- a/source/blender/editors/gpencil/CMakeLists.txt +++ b/source/blender/editors/gpencil/CMakeLists.txt @@ -43,7 +43,6 @@ set(SRC gpencil_add_monkey.c gpencil_add_stroke.c gpencil_armature.c - gpencil_brush.c gpencil_convert.c gpencil_data.c gpencil_edit.c @@ -54,9 +53,14 @@ set(SRC gpencil_ops_versioning.c gpencil_paint.c gpencil_primitive.c + gpencil_sculpt_paint.c gpencil_select.c gpencil_undo.c gpencil_utils.c + gpencil_uv.c + gpencil_vertex_paint.c + gpencil_vertex_ops.c + gpencil_weight_paint.c gpencil_intern.h ) diff --git a/source/blender/editors/gpencil/annotate_draw.c b/source/blender/editors/gpencil/annotate_draw.c index 1a30555a584..ef9b6d2943b 100644 --- a/source/blender/editors/gpencil/annotate_draw.c +++ b/source/blender/editors/gpencil/annotate_draw.c @@ -33,6 +33,7 @@ #include "BLI_sys_types.h" #include "BLI_math.h" +#include "BLI_listbase.h" #include "BLI_utildefines.h" #include "BLF_api.h" @@ -562,7 +563,7 @@ static void annotation_draw_strokes(bGPdata *UNUSED(gpd), { GPU_program_point_size(true); - for (bGPDstroke *gps = gpf->strokes.first; gps; gps = gps->next) { + LISTBASE_FOREACH (bGPDstroke *, gps, &gpf->strokes) { /* check if stroke can be drawn */ if (annotation_can_draw_stroke(gps, dflag) == false) { continue; @@ -625,7 +626,7 @@ static void annotation_draw_strokes(bGPdata *UNUSED(gpd), } /* Draw selected verts for strokes being edited */ -static void annotation_draw_strokes_edit(bGPdata *gpd, +static void annotation_draw_strokes_edit(bGPdata *UNUSED(gpd), bGPDlayer *gpl, const bGPDframe *gpf, int offsx, @@ -660,7 +661,7 @@ static void annotation_draw_strokes_edit(bGPdata *gpd, GPU_program_point_size(true); /* draw stroke verts */ - for (bGPDstroke *gps = gpf->strokes.first; gps; gps = gps->next) { + LISTBASE_FOREACH (bGPDstroke *, gps, &gpf->strokes) { /* check if stroke can be drawn */ if (annotation_can_draw_stroke(gps, dflag) == false) { continue; @@ -689,6 +690,9 @@ static void annotation_draw_strokes_edit(bGPdata *gpd, vsize = bsize + 2; } + /* Why? */ + UNUSED_VARS(vsize); + float selectColor[4]; UI_GetThemeColor3fv(TH_GP_VERTEX_SELECT, selectColor); selectColor[3] = alpha; @@ -709,31 +713,12 @@ static void annotation_draw_strokes_edit(bGPdata *gpd, immBegin(GPU_PRIM_POINTS, gps->totpoints); - /* Draw start and end point differently if enabled stroke direction hint */ - bool show_direction_hint = (gpd->flag & GP_DATA_SHOW_DIRECTION) && (gps->totpoints > 1); - /* Draw all the stroke points (selected or not) */ bGPDspoint *pt = gps->points; for (int i = 0; i < gps->totpoints; i++, pt++) { /* size and color first */ - if (show_direction_hint && i == 0) { - /* start point in green bigger */ - immAttr3f(color, 0.0f, 1.0f, 0.0f); - immAttr1f(size, vsize + 4); - } - else if (show_direction_hint && (i == gps->totpoints - 1)) { - /* end point in red smaller */ - immAttr3f(color, 1.0f, 0.0f, 0.0f); - immAttr1f(size, vsize + 1); - } - else if (pt->flag & GP_SPOINT_SELECT) { - immAttr3fv(color, selectColor); - immAttr1f(size, vsize); - } - else { - immAttr3fv(color, gpl->color); - immAttr1f(size, bsize); - } + immAttr3fv(color, gpl->color); + immAttr1f(size, bsize); /* then position */ if (gps->flag & GP_STROKE_3DSPACE) { @@ -857,7 +842,7 @@ static void annotation_draw_data_layers( { float ink[4]; - for (bGPDlayer *gpl = gpd->layers.first; gpl; gpl = gpl->next) { + LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd->layers) { /* verify never thickness is less than 1 */ CLAMP_MIN(gpl->thickness, 1.0f); short lthick = gpl->thickness; @@ -872,7 +857,7 @@ static void annotation_draw_data_layers( } /* get frame to draw */ - bGPDframe *gpf = BKE_gpencil_layer_getframe(gpl, cfra, GP_GETFRAME_USE_PREV); + bGPDframe *gpf = BKE_gpencil_layer_frame_get(gpl, cfra, GP_GETFRAME_USE_PREV); if (gpf == NULL) { continue; } diff --git a/source/blender/editors/gpencil/annotate_paint.c b/source/blender/editors/gpencil/annotate_paint.c index fe163e5b6e5..3e33f811225 100644 --- a/source/blender/editors/gpencil/annotate_paint.c +++ b/source/blender/editors/gpencil/annotate_paint.c @@ -537,9 +537,7 @@ static short gp_stroke_addpoint(tGPsdata *p, const float mval[2], float pressure pts->pressure = pt->pressure; pts->strength = pt->strength; pts->time = pt->time; - - /* force fill recalc */ - gps->flag |= GP_STROKE_RECALC_GEOMETRY; + gps->tot_triangles = 0; } /* increment counters */ @@ -604,14 +602,13 @@ static void gp_stroke_newfrombuffer(tGPsdata *p) /* copy appropriate settings for stroke */ gps->totpoints = totelem; gps->thickness = gpl->thickness; - gps->gradient_f = 1.0f; - gps->gradient_s[0] = 1.0f; - gps->gradient_s[1] = 1.0f; + gps->fill_opacity_fac = 1.0f; + gps->hardeness = 1.0f; + copy_v2_fl(gps->aspect_ratio, 1.0f); + gps->uv_scale = 1.0f; gps->flag = gpd->runtime.sbuffer_sflag; gps->inittime = p->inittime; - - /* enable recalculation flag by default (only used if hq fill) */ - gps->flag |= GP_STROKE_RECALC_GEOMETRY; + gps->tot_triangles = 0; /* allocate enough memory for a continuous array for storage points */ gps->points = MEM_callocN(sizeof(bGPDspoint) * gps->totpoints, "gp_stroke_points"); @@ -1207,7 +1204,7 @@ static void gp_paint_initstroke(tGPsdata *p, eGPencil_PaintModes paintmode, Deps ToolSettings *ts = scene->toolsettings; /* get active layer (or add a new one if non-existent) */ - p->gpl = BKE_gpencil_layer_getactive(p->gpd); + p->gpl = BKE_gpencil_layer_active_get(p->gpd); if (p->gpl == NULL) { /* tag for annotations */ p->gpd->flag |= GP_DATA_ANNOTATIONS; @@ -1235,7 +1232,7 @@ static void gp_paint_initstroke(tGPsdata *p, eGPencil_PaintModes paintmode, Deps */ bool has_layer_to_erase = false; - if (gpencil_layer_is_editable(p->gpl)) { + if (BKE_gpencil_layer_is_editable(p->gpl)) { /* Ensure that there's stuff to erase here (not including selection mask below)... */ if (p->gpl->actframe && p->gpl->actframe->strokes.first) { has_layer_to_erase = true; @@ -1263,7 +1260,7 @@ static void gp_paint_initstroke(tGPsdata *p, eGPencil_PaintModes paintmode, Deps add_frame_mode = GP_GETFRAME_ADD_NEW; } - p->gpf = BKE_gpencil_layer_getframe(p->gpl, CFRA, add_frame_mode); + p->gpf = BKE_gpencil_layer_frame_get(p->gpl, CFRA, add_frame_mode); if (p->gpf == NULL) { p->status = GP_STATUS_ERROR; diff --git a/source/blender/editors/gpencil/drawgpencil.c b/source/blender/editors/gpencil/drawgpencil.c index 4d879306cec..db13f57b192 100644 --- a/source/blender/editors/gpencil/drawgpencil.c +++ b/source/blender/editors/gpencil/drawgpencil.c @@ -233,128 +233,8 @@ static void gp_draw_stroke_volumetric_3d(const bGPDspoint *points, } /* --------------- Stroke Fills ----------------- */ -/* calc bounding box in 2d using flat projection data */ -static void gp_calc_2d_bounding_box( - const float (*points2d)[2], int totpoints, float minv[2], float maxv[2], bool expand) -{ - copy_v2_v2(minv, points2d[0]); - copy_v2_v2(maxv, points2d[0]); - - for (int i = 1; i < totpoints; i++) { - /* min */ - if (points2d[i][0] < minv[0]) { - minv[0] = points2d[i][0]; - } - if (points2d[i][1] < minv[1]) { - minv[1] = points2d[i][1]; - } - /* max */ - if (points2d[i][0] > maxv[0]) { - maxv[0] = points2d[i][0]; - } - if (points2d[i][1] > maxv[1]) { - maxv[1] = points2d[i][1]; - } - } - /* If not expanded, use a perfect square */ - if (expand == false) { - if (maxv[0] > maxv[1]) { - maxv[1] = maxv[0]; - } - else { - maxv[0] = maxv[1]; - } - } -} - -/* calc texture coordinates using flat projected points */ -static void gp_calc_stroke_text_coordinates(const float (*points2d)[2], - int totpoints, - const float minv[2], - float maxv[2], - float (*r_uv)[2]) -{ - float d[2]; - d[0] = maxv[0] - minv[0]; - d[1] = maxv[1] - minv[1]; - for (int i = 0; i < totpoints; i++) { - r_uv[i][0] = (points2d[i][0] - minv[0]) / d[0]; - r_uv[i][1] = (points2d[i][1] - minv[1]) / d[1]; - } -} - -/* Triangulate stroke for high quality fill - * (this is done only if cache is null or stroke was modified). */ -static void gp_triangulate_stroke_fill(bGPDstroke *gps) -{ - BLI_assert(gps->totpoints >= 3); - - /* allocate memory for temporary areas */ - gps->tot_triangles = gps->totpoints - 2; - uint(*tmp_triangles)[3] = MEM_mallocN(sizeof(*tmp_triangles) * gps->tot_triangles, - "GP Stroke temp triangulation"); - float(*points2d)[2] = MEM_mallocN(sizeof(*points2d) * gps->totpoints, - "GP Stroke temp 2d points"); - float(*uv)[2] = MEM_mallocN(sizeof(*uv) * gps->totpoints, "GP Stroke temp 2d uv data"); - - int direction = 0; - - /* convert to 2d and triangulate */ - BKE_gpencil_stroke_2d_flat(gps->points, gps->totpoints, points2d, &direction); - BLI_polyfill_calc(points2d, (uint)gps->totpoints, direction, tmp_triangles); - - /* calc texture coordinates automatically */ - float minv[2]; - float maxv[2]; - /* first needs bounding box data */ - gp_calc_2d_bounding_box((const float(*)[2])points2d, gps->totpoints, minv, maxv, false); - /* calc uv data */ - gp_calc_stroke_text_coordinates((const float(*)[2])points2d, gps->totpoints, minv, maxv, uv); - - /* Number of triangles */ - gps->tot_triangles = gps->totpoints - 2; - /* save triangulation data in stroke cache */ - if (gps->tot_triangles > 0) { - if (gps->triangles == NULL) { - gps->triangles = MEM_callocN(sizeof(*gps->triangles) * gps->tot_triangles, - "GP Stroke triangulation"); - } - else { - gps->triangles = MEM_recallocN(gps->triangles, sizeof(*gps->triangles) * gps->tot_triangles); - } - - for (int i = 0; i < gps->tot_triangles; i++) { - bGPDtriangle *stroke_triangle = &gps->triangles[i]; - memcpy(stroke_triangle->verts, tmp_triangles[i], sizeof(uint[3])); - /* copy texture coordinates */ - copy_v2_v2(stroke_triangle->uv[0], uv[tmp_triangles[i][0]]); - copy_v2_v2(stroke_triangle->uv[1], uv[tmp_triangles[i][1]]); - copy_v2_v2(stroke_triangle->uv[2], uv[tmp_triangles[i][2]]); - } - } - else { - /* No triangles needed - Free anything allocated previously */ - if (gps->triangles) { - MEM_freeN(gps->triangles); - } - - gps->triangles = NULL; - } - - /* disable recalculation flag */ - if (gps->flag & GP_STROKE_RECALC_GEOMETRY) { - gps->flag &= ~GP_STROKE_RECALC_GEOMETRY; - } - - /* clear memory */ - MEM_SAFE_FREE(tmp_triangles); - MEM_SAFE_FREE(points2d); - MEM_SAFE_FREE(uv); -} - /* add a new fill point and texture coordinates to vertex buffer */ static void gp_add_filldata_tobuffer(const bGPDspoint *pt, - const float uv[2], uint pos, uint texcoord, short flag, @@ -375,48 +255,10 @@ static void gp_add_filldata_tobuffer(const bGPDspoint *pt, fpt[2] = 0.0f; /* 2d always is z=0.0f */ } - immAttr2f(texcoord, uv[0], uv[1]); /* texture coordinates */ - immVertex3fv(pos, fpt); /* position */ + immAttr2f(texcoord, pt->uv_fill[0], pt->uv_fill[1]); /* texture coordinates */ + immVertex3fv(pos, fpt); /* position */ } -#if 0 /* GPXX disabled, not used in annotations */ -/* assign image texture for filling stroke */ -static int gp_set_filling_texture(Image *image, short flag) -{ - ImBuf *ibuf; - uint *bind = &image->bindcode[TEXTARGET_TEXTURE_2D]; - int error = GL_NO_ERROR; - ImageUser iuser = {NULL}; - void *lock; - - iuser.ok = true; - - ibuf = BKE_image_acquire_ibuf(image, &iuser, &lock); - - if (ibuf == NULL || ibuf->rect == NULL) { - BKE_image_release_ibuf(image, ibuf, NULL); - return (int)GL_INVALID_OPERATION; - } - - GPU_create_gl_tex( - bind, ibuf->rect, ibuf->rect_float, ibuf->x, ibuf->y, GL_TEXTURE_2D, false, false, image); - - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - if (flag & GP_STYLE_COLOR_TEX_CLAMP) { - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP); - } - else { - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); - } - BKE_image_release_ibuf(image, ibuf, NULL); - - return error; -} -#endif - /* draw fills for shapes */ static void gp_draw_stroke_fill(bGPdata *gpd, bGPDstroke *gps, @@ -428,18 +270,12 @@ static void gp_draw_stroke_fill(bGPdata *gpd, const float color[4]) { BLI_assert(gps->totpoints >= 3); + BLI_assert(gps->tot_triangles >= 1); const bool use_mat = (gpd->mat != NULL); Material *ma = (use_mat) ? gpd->mat[gps->mat_nr] : BKE_material_default_gpencil(); MaterialGPencilStyle *gp_style = (ma) ? ma->gp_style : NULL; - /* Calculate triangles cache for filling area (must be done only after changes) */ - if ((gps->flag & GP_STROKE_RECALC_GEOMETRY) || (gps->tot_triangles == 0) || - (gps->triangles == NULL)) { - gp_triangulate_stroke_fill(gps); - } - BLI_assert(gps->tot_triangles >= 1); - GPUVertFormat *format = immVertexFormat(); uint pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 3, GPU_FETCH_FLOAT); uint texcoord = GPU_vertformat_attr_add(format, "texCoord", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); @@ -450,25 +286,13 @@ static void gp_draw_stroke_fill(bGPdata *gpd, immUniform1i("fill_type", gp_style->fill_style); immUniform1f("mix_factor", gp_style->mix_factor); - immUniform1f("gradient_angle", gp_style->gradient_angle); - immUniform1f("gradient_radius", gp_style->gradient_radius); - immUniform1f("pattern_gridsize", gp_style->pattern_gridsize); - immUniform2fv("gradient_scale", gp_style->gradient_scale); - immUniform2fv("gradient_shift", gp_style->gradient_shift); - immUniform1f("texture_angle", gp_style->texture_angle); immUniform2fv("texture_scale", gp_style->texture_scale); immUniform2fv("texture_offset", gp_style->texture_offset); immUniform1f("texture_opacity", gp_style->texture_opacity); - immUniform1i("t_mix", (gp_style->flag & GP_STYLE_FILL_TEX_MIX) != 0); - immUniform1i("t_flip", (gp_style->flag & GP_STYLE_COLOR_FLIP_FILL) != 0); -#if 0 /* GPXX disabled, not used in annotations */ - /* image texture */ - if ((gp_style->fill_style == GP_STYLE_FILL_STYLE_TEXTURE) || - (gp_style->flag & GP_STYLE_COLOR_TEX_MIX)) { - gp_set_filling_texture(gp_style->ima, gp_style->flag); - } -#endif + immUniform1i("t_mix", (gp_style->flag & GP_MATERIAL_FILL_TEX_MIX) != 0); + immUniform1i("t_flip", (gp_style->flag & GP_MATERIAL_FLIP_FILL) != 0); + /* Draw all triangles for filling the polygon (cache must be calculated before) */ immBegin(GPU_PRIM_TRIS, gps->tot_triangles * 3); /* TODO: use batch instead of immediate mode, to share vertices */ @@ -477,7 +301,6 @@ static void gp_draw_stroke_fill(bGPdata *gpd, for (int i = 0; i < gps->tot_triangles; i++, stroke_triangle++) { for (int j = 0; j < 3; j++) { gp_add_filldata_tobuffer(&gps->points[stroke_triangle->verts[j]], - stroke_triangle->uv[j], pos, texcoord, gps->flag, @@ -887,15 +710,15 @@ static void gp_draw_strokes(tGPDdraw *tgpw) Material *ma = (use_mat) ? tgpw->gpd->mat[gps->mat_nr] : BKE_material_default_gpencil(); MaterialGPencilStyle *gp_style = (ma) ? ma->gp_style : NULL; - if ((gp_style == NULL) || (gp_style->flag & GP_STYLE_COLOR_HIDE) || + if ((gp_style == NULL) || (gp_style->flag & GP_MATERIAL_HIDE) || /* if onion and ghost flag do not draw*/ - (tgpw->onion && (gp_style->flag & GP_STYLE_COLOR_ONIONSKIN))) { + (tgpw->onion && (gp_style->flag & GP_MATERIAL_ONIONSKIN))) { continue; } /* if disable fill, the colors with fill must be omitted too except fill boundary strokes */ if ((tgpw->disable_fill == 1) && (gp_style->fill_rgba[3] > 0.0f) && - ((gps->flag & GP_STROKE_NOFILL) == 0) && (gp_style->flag & GP_STYLE_FILL_SHOW)) { + ((gps->flag & GP_STROKE_NOFILL) == 0) && (gp_style->flag & GP_MATERIAL_FILL_SHOW)) { continue; } @@ -980,7 +803,7 @@ static void gp_draw_strokes(tGPDdraw *tgpw) } } - if (gp_style->mode == GP_STYLE_MODE_DOTS) { + if (gp_style->mode == GP_MATERIAL_MODE_DOT) { /* volumetric stroke drawing */ if (tgpw->disable_fill != 1) { gp_draw_stroke_volumetric_3d(gps->points, gps->totpoints, sthickness, ink); @@ -1061,7 +884,7 @@ static void gp_draw_strokes(tGPDdraw *tgpw) copy_v4_v4(ink, tcolor); } } - if (gp_style->mode == GP_STYLE_MODE_DOTS) { + if (gp_style->mode == GP_MATERIAL_MODE_DOT) { /* blob/disk-based "volumetric" drawing */ gp_draw_stroke_volumetric_2d(gps->points, gps->totpoints, @@ -1116,113 +939,8 @@ static void gp_draw_strokes(tGPDdraw *tgpw) /* ----- General Drawing ------ */ -/* draw interpolate strokes (used only while operator is running) */ -void ED_gp_draw_interpolation(const bContext *C, tGPDinterpolate *tgpi, const int type) -{ - tGPDdraw tgpw; - ARegion *region = CTX_wm_region(C); - RegionView3D *rv3d = region->regiondata; - tGPDinterpolate_layer *tgpil; - Object *obact = CTX_data_active_object(C); - /* Drawing code is expected to run with fully evaluated depsgraph. */ - Depsgraph *depsgraph = CTX_data_expect_evaluated_depsgraph(C); - - float color[4]; - - UI_GetThemeColor3fv(TH_GP_VERTEX_SELECT, color); - color[3] = 0.6f; - int dflag = 0; - /* if 3d stuff, enable flags */ - if (type == REGION_DRAW_POST_VIEW) { - dflag |= (GP_DRAWDATA_ONLY3D | GP_DRAWDATA_NOSTATUS); - } - - tgpw.rv3d = rv3d; - tgpw.depsgraph = depsgraph; - tgpw.ob = obact; - tgpw.gpd = tgpi->gpd; - tgpw.offsx = 0; - tgpw.offsy = 0; - tgpw.winx = tgpi->region->winx; - tgpw.winy = tgpi->region->winy; - tgpw.dflag = dflag; - - /* turn on alpha-blending */ - GPU_blend(true); - for (tgpil = tgpi->ilayers.first; tgpil; tgpil = tgpil->next) { - /* calculate parent position */ - ED_gpencil_parent_location(depsgraph, obact, tgpi->gpd, tgpil->gpl, tgpw.diff_mat); - if (tgpil->interFrame) { - tgpw.gpl = tgpil->gpl; - tgpw.gpf = tgpil->interFrame; - tgpw.t_gpf = tgpil->interFrame; - tgpw.gps = NULL; - - tgpw.lthick = tgpil->gpl->line_change; - tgpw.opacity = 1.0; - copy_v4_v4(tgpw.tintcolor, color); - tgpw.onion = true; - tgpw.custonion = true; - if (obact->totcol == 0) { - tgpw.gpd->mat = NULL; - } - - gp_draw_strokes(&tgpw); - } - } - GPU_blend(false); -} - /* wrapper to draw strokes for filling operator */ void ED_gp_draw_fill(tGPDdraw *tgpw) { gp_draw_strokes(tgpw); } - -/* draw a short status message in the top-right corner */ -static void UNUSED_FUNCTION(gp_draw_status_text)(const bGPdata *gpd, ARegion *region) -{ - - /* Cannot draw any status text when drawing OpenGL Renders */ - if (G.f & G_FLAG_RENDER_VIEWPORT) { - return; - } - - /* Get bounds of region - Necessary to avoid problems with region overlap. */ - const rcti *rect = ED_region_visible_rect(region); - - /* for now, this should only be used to indicate when we are in stroke editmode */ - if (gpd->flag & GP_DATA_STROKE_EDITMODE) { - const char *printable = IFACE_("GPencil Stroke Editing"); - float printable_size[2]; - - int font_id = BLF_default(); - - BLF_width_and_height( - font_id, printable, BLF_DRAW_STR_DUMMY_MAX, &printable_size[0], &printable_size[1]); - - int xco = (rect->xmax - U.widget_unit) - (int)printable_size[0]; - int yco = (rect->ymax - U.widget_unit); - - /* text label */ - UI_FontThemeColor(font_id, TH_TEXT_HI); -#ifdef WITH_INTERNATIONAL - BLF_draw_default(xco, yco, 0.0f, printable, BLF_DRAW_STR_DUMMY_MAX); -#else - BLF_draw_default_ascii(xco, yco, 0.0f, printable, BLF_DRAW_STR_DUMMY_MAX); -#endif - - /* grease pencil icon... */ - // XXX: is this too intrusive? - GPU_blend_set_func_separate( - GPU_SRC_ALPHA, GPU_ONE_MINUS_SRC_ALPHA, GPU_ONE, GPU_ONE_MINUS_SRC_ALPHA); - GPU_blend(true); - - xco -= U.widget_unit; - yco -= (int)printable_size[1] / 2; - - UI_icon_draw(xco, yco, ICON_GREASEPENCIL); - - GPU_blend(false); - } -} diff --git a/source/blender/editors/gpencil/editaction_gpencil.c b/source/blender/editors/gpencil/editaction_gpencil.c index f4636e81966..86355787b3c 100644 --- a/source/blender/editors/gpencil/editaction_gpencil.c +++ b/source/blender/editors/gpencil/editaction_gpencil.c @@ -58,15 +58,13 @@ /* Loops over the gp-frames for a gp-layer, and applies the given callback */ bool ED_gplayer_frames_looper(bGPDlayer *gpl, Scene *scene, short (*gpf_cb)(bGPDframe *, Scene *)) { - bGPDframe *gpf; - /* error checker */ if (gpl == NULL) { return false; } /* do loop */ - for (gpf = gpl->frames.first; gpf; gpf = gpf->next) { + LISTBASE_FOREACH (bGPDframe *, gpf, &gpl->frames) { /* execute callback */ if (gpf_cb(gpf, scene)) { return true; @@ -83,7 +81,6 @@ bool ED_gplayer_frames_looper(bGPDlayer *gpl, Scene *scene, short (*gpf_cb)(bGPD /* make a listing all the gp-frames in a layer as cfraelems */ void ED_gplayer_make_cfra_list(bGPDlayer *gpl, ListBase *elems, bool onlysel) { - bGPDframe *gpf; CfraElem *ce; /* error checking */ @@ -92,7 +89,7 @@ void ED_gplayer_make_cfra_list(bGPDlayer *gpl, ListBase *elems, bool onlysel) } /* loop through gp-frames, adding */ - for (gpf = gpl->frames.first; gpf; gpf = gpf->next) { + LISTBASE_FOREACH (bGPDframe *, gpf, &gpl->frames) { if ((onlysel == 0) || (gpf->flag & GP_FRAME_SELECT)) { ce = MEM_callocN(sizeof(CfraElem), "CfraElem"); @@ -110,15 +107,13 @@ void ED_gplayer_make_cfra_list(bGPDlayer *gpl, ListBase *elems, bool onlysel) /* check if one of the frames in this layer is selected */ bool ED_gplayer_frame_select_check(bGPDlayer *gpl) { - bGPDframe *gpf; - /* error checking */ if (gpl == NULL) { return false; } /* stop at the first one found */ - for (gpf = gpl->frames.first; gpf; gpf = gpf->next) { + LISTBASE_FOREACH (bGPDframe *, gpf, &gpl->frames) { if (gpf->flag & GP_FRAME_SELECT) { return true; } @@ -151,15 +146,13 @@ static void gpframe_select(bGPDframe *gpf, short select_mode) /* set all/none/invert select (like above, but with SELECT_* modes) */ void ED_gpencil_select_frames(bGPDlayer *gpl, short select_mode) { - bGPDframe *gpf; - /* error checking */ if (gpl == NULL) { return; } /* handle according to mode */ - for (gpf = gpl->frames.first; gpf; gpf = gpf->next) { + LISTBASE_FOREACH (bGPDframe *, gpf, &gpl->frames) { gpframe_select(gpf, select_mode); } } @@ -185,7 +178,7 @@ void ED_gpencil_select_frame(bGPDlayer *gpl, int selx, short select_mode) return; } - gpf = BKE_gpencil_layer_find_frame(gpl, selx); + gpf = BKE_gpencil_layer_frame_find(gpl, selx); if (gpf) { gpframe_select(gpf, select_mode); @@ -195,14 +188,12 @@ void ED_gpencil_select_frame(bGPDlayer *gpl, int selx, short select_mode) /* select the frames in this layer that occur within the bounds specified */ void ED_gplayer_frames_select_box(bGPDlayer *gpl, float min, float max, short select_mode) { - bGPDframe *gpf; - if (gpl == NULL) { return; } /* only select those frames which are in bounds */ - for (gpf = gpl->frames.first; gpf; gpf = gpf->next) { + LISTBASE_FOREACH (bGPDframe *, gpf, &gpl->frames) { if (IN_RANGE(gpf->framenum, min, max)) { gpframe_select(gpf, select_mode); } @@ -215,14 +206,12 @@ void ED_gplayer_frames_select_region(KeyframeEditData *ked, short tool, short select_mode) { - bGPDframe *gpf; - if (gpl == NULL) { return; } /* only select frames which are within the region */ - for (gpf = gpl->frames.first; gpf; gpf = gpf->next) { + LISTBASE_FOREACH (bGPDframe *, gpf, &gpl->frames) { /* construct a dummy point coordinate to do this testing with */ float pt[2] = {0}; @@ -251,7 +240,6 @@ void ED_gplayer_frames_select_region(KeyframeEditData *ked, /* Delete selected frames */ bool ED_gplayer_frames_delete(bGPDlayer *gpl) { - bGPDframe *gpf, *gpfn; bool changed = false; /* error checking */ @@ -260,11 +248,9 @@ bool ED_gplayer_frames_delete(bGPDlayer *gpl) } /* check for frames to delete */ - for (gpf = gpl->frames.first; gpf; gpf = gpfn) { - gpfn = gpf->next; - + LISTBASE_FOREACH_MUTABLE (bGPDframe *, gpf, &gpl->frames) { if (gpf->flag & GP_FRAME_SELECT) { - BKE_gpencil_layer_delframe(gpl, gpf); + BKE_gpencil_layer_frame_delete(gpl, gpf); changed = true; } } @@ -275,16 +261,13 @@ bool ED_gplayer_frames_delete(bGPDlayer *gpl) /* Duplicate selected frames from given gp-layer */ void ED_gplayer_frames_duplicate(bGPDlayer *gpl) { - bGPDframe *gpf, *gpfn; - /* error checking */ if (gpl == NULL) { return; } /* duplicate selected frames */ - for (gpf = gpl->frames.first; gpf; gpf = gpfn) { - gpfn = gpf->next; + LISTBASE_FOREACH_MUTABLE (bGPDframe *, gpf, &gpl->frames) { /* duplicate this frame */ if (gpf->flag & GP_FRAME_SELECT) { @@ -304,13 +287,11 @@ void ED_gplayer_frames_duplicate(bGPDlayer *gpl) */ void ED_gplayer_frames_keytype_set(bGPDlayer *gpl, short type) { - bGPDframe *gpf; - if (gpl == NULL) { return; } - for (gpf = gpl->frames.first; gpf; gpf = gpf->next) { + LISTBASE_FOREACH (bGPDframe *, gpf, &gpl->frames) { if (gpf->flag & GP_FRAME_SELECT) { gpf->key_type = type; } @@ -370,10 +351,9 @@ bool ED_gpencil_anim_copybuf_copy(bAnimContext *ac) 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 = gpl->frames.first; gpf; gpf = gpf->next) { + LISTBASE_FOREACH (bGPDframe *, gpf, &gpl->frames) { /* if frame is selected, make duplicate it and its strokes */ if (gpf->flag & GP_FRAME_SELECT) { /* make a copy of this frame */ @@ -489,7 +469,7 @@ bool ED_gpencil_anim_copybuf_paste(bAnimContext *ac, const short offset_mode) gpfs->framenum += offset; /* get frame to copy data into (if no frame returned, then just ignore) */ - gpf = BKE_gpencil_layer_getframe(gpld, gpfs->framenum, GP_GETFRAME_ADD_NEW); + gpf = BKE_gpencil_layer_frame_get(gpld, gpfs->framenum, GP_GETFRAME_ADD_NEW); if (gpf) { bGPDstroke *gps, *gpsn; @@ -502,21 +482,15 @@ bool ED_gpencil_anim_copybuf_paste(bAnimContext *ac, const short offset_mode) */ for (gps = gpfs->strokes.first; gps; gps = gps->next) { /* make a copy of stroke, then of its points array */ - gpsn = MEM_dupallocN(gps); - gpsn->points = MEM_dupallocN(gps->points); - if (gps->dvert != NULL) { - gpsn->dvert = MEM_dupallocN(gps->dvert); - BKE_gpencil_stroke_weights_duplicate(gps, gpsn); - } - /* duplicate triangle information */ - gpsn->triangles = MEM_dupallocN(gps->triangles); + gpsn = BKE_gpencil_stroke_duplicate(gps, true); + /* append stroke to frame */ BLI_addtail(&gpf->strokes, gpsn); } /* if no strokes (i.e. new frame) added, free gpf */ if (BLI_listbase_is_empty(&gpf->strokes)) { - BKE_gpencil_layer_delframe(gpld, gpf); + BKE_gpencil_layer_frame_delete(gpld, gpf); } } diff --git a/source/blender/editors/gpencil/gpencil_add_monkey.c b/source/blender/editors/gpencil/gpencil_add_monkey.c index 8b4620d233b..f08188c48ce 100644 --- a/source/blender/editors/gpencil/gpencil_add_monkey.c +++ b/source/blender/editors/gpencil/gpencil_add_monkey.c @@ -66,17 +66,20 @@ static int gpencil_monkey_color( ma = BKE_gpencil_object_material_new(bmain, ob, pct->name, &idx); copy_v4_v4(ma->gp_style->stroke_rgba, pct->line); + srgb_to_linearrgb_v4(ma->gp_style->stroke_rgba, ma->gp_style->stroke_rgba); + copy_v4_v4(ma->gp_style->fill_rgba, pct->fill); + srgb_to_linearrgb_v4(ma->gp_style->fill_rgba, ma->gp_style->fill_rgba); if (!stroke) { - ma->gp_style->flag &= ~GP_STYLE_STROKE_SHOW; + ma->gp_style->flag &= ~GP_MATERIAL_STROKE_SHOW; } if (!fill) { - ma->gp_style->flag &= ~GP_STYLE_FILL_SHOW; + ma->gp_style->flag &= ~GP_MATERIAL_FILL_SHOW; } else { - ma->gp_style->flag |= GP_STYLE_FILL_SHOW; + ma->gp_style->flag |= GP_MATERIAL_FILL_SHOW; } return idx; @@ -855,89 +858,117 @@ void ED_gpencil_create_monkey(bContext *C, Object *ob, float mat[4][4]) bGPDframe *frameLines = BKE_gpencil_frame_addnew(Lines, CFRA); /* generate strokes */ - gps = BKE_gpencil_add_stroke(frameFills, color_Skin, 270, 75); + gps = BKE_gpencil_stroke_add(frameFills, color_Skin, 270, 75, false); BKE_gpencil_stroke_add_points(gps, data0, 270, mat); + BKE_gpencil_stroke_geometry_update(gps); - gps = BKE_gpencil_add_stroke(frameFills, color_Skin_Shadow, 33, 60); + gps = BKE_gpencil_stroke_add(frameFills, color_Skin_Shadow, 33, 60, false); BKE_gpencil_stroke_add_points(gps, data1, 33, mat); + BKE_gpencil_stroke_geometry_update(gps); - gps = BKE_gpencil_add_stroke(frameFills, color_Skin_Shadow, 18, 60); + gps = BKE_gpencil_stroke_add(frameFills, color_Skin_Shadow, 18, 60, false); BKE_gpencil_stroke_add_points(gps, data2, 18, mat); + BKE_gpencil_stroke_geometry_update(gps); - gps = BKE_gpencil_add_stroke(frameFills, color_Skin_Light, 64, 60); + gps = BKE_gpencil_stroke_add(frameFills, color_Skin_Light, 64, 60, false); BKE_gpencil_stroke_add_points(gps, data3, 64, mat); + BKE_gpencil_stroke_geometry_update(gps); - gps = BKE_gpencil_add_stroke(frameFills, color_Skin_Light, 33, 60); + gps = BKE_gpencil_stroke_add(frameFills, color_Skin_Light, 33, 60, false); BKE_gpencil_stroke_add_points(gps, data4, 33, mat); + BKE_gpencil_stroke_geometry_update(gps); - gps = BKE_gpencil_add_stroke(frameFills, color_Skin_Light, 64, 60); + gps = BKE_gpencil_stroke_add(frameFills, color_Skin_Light, 64, 60, false); BKE_gpencil_stroke_add_points(gps, data5, 64, mat); + BKE_gpencil_stroke_geometry_update(gps); - gps = BKE_gpencil_add_stroke(frameFills, color_Skin_Light, 33, 60); + gps = BKE_gpencil_stroke_add(frameFills, color_Skin_Light, 33, 60, false); BKE_gpencil_stroke_add_points(gps, data6, 33, mat); + BKE_gpencil_stroke_geometry_update(gps); - gps = BKE_gpencil_add_stroke(frameFills, color_Skin_Light, 18, 40); + gps = BKE_gpencil_stroke_add(frameFills, color_Skin_Light, 18, 40, false); BKE_gpencil_stroke_add_points(gps, data7, 18, mat); + BKE_gpencil_stroke_geometry_update(gps); - gps = BKE_gpencil_add_stroke(frameFills, color_Eyes, 49, 60); + gps = BKE_gpencil_stroke_add(frameFills, color_Eyes, 49, 60, false); BKE_gpencil_stroke_add_points(gps, data8, 49, mat); + BKE_gpencil_stroke_geometry_update(gps); - gps = BKE_gpencil_add_stroke(frameFills, color_Skin_Shadow, 33, 60); + gps = BKE_gpencil_stroke_add(frameFills, color_Skin_Shadow, 33, 60, false); BKE_gpencil_stroke_add_points(gps, data9, 33, mat); + BKE_gpencil_stroke_geometry_update(gps); - gps = BKE_gpencil_add_stroke(frameFills, color_Eyes, 49, 60); + gps = BKE_gpencil_stroke_add(frameFills, color_Eyes, 49, 60, false); BKE_gpencil_stroke_add_points(gps, data10, 49, mat); + BKE_gpencil_stroke_geometry_update(gps); - gps = BKE_gpencil_add_stroke(frameFills, color_Skin_Shadow, 18, 40); + gps = BKE_gpencil_stroke_add(frameFills, color_Skin_Shadow, 18, 40, false); BKE_gpencil_stroke_add_points(gps, data11, 18, mat); + BKE_gpencil_stroke_geometry_update(gps); - gps = BKE_gpencil_add_stroke(frameFills, color_Skin_Shadow, 18, 40); + gps = BKE_gpencil_stroke_add(frameFills, color_Skin_Shadow, 18, 40, false); BKE_gpencil_stroke_add_points(gps, data12, 18, mat); + BKE_gpencil_stroke_geometry_update(gps); - gps = BKE_gpencil_add_stroke(frameLines, color_Black, 33, 60); + gps = BKE_gpencil_stroke_add(frameLines, color_Black, 33, 60, false); BKE_gpencil_stroke_add_points(gps, data13, 33, mat); + BKE_gpencil_stroke_geometry_update(gps); - gps = BKE_gpencil_add_stroke(frameLines, color_Black, 33, 60); + gps = BKE_gpencil_stroke_add(frameLines, color_Black, 33, 60, false); BKE_gpencil_stroke_add_points(gps, data14, 33, mat); + BKE_gpencil_stroke_geometry_update(gps); - gps = BKE_gpencil_add_stroke(frameLines, color_Black, 65, 60); + gps = BKE_gpencil_stroke_add(frameLines, color_Black, 65, 60, false); BKE_gpencil_stroke_add_points(gps, data15, 65, mat); + BKE_gpencil_stroke_geometry_update(gps); - gps = BKE_gpencil_add_stroke(frameLines, color_Black, 34, 60); + gps = BKE_gpencil_stroke_add(frameLines, color_Black, 34, 60, false); BKE_gpencil_stroke_add_points(gps, data16, 34, mat); + BKE_gpencil_stroke_geometry_update(gps); - gps = BKE_gpencil_add_stroke(frameLines, color_Black, 33, 60); + gps = BKE_gpencil_stroke_add(frameLines, color_Black, 33, 60, false); BKE_gpencil_stroke_add_points(gps, data17, 33, mat); + BKE_gpencil_stroke_geometry_update(gps); - gps = BKE_gpencil_add_stroke(frameLines, color_Black, 33, 40); + gps = BKE_gpencil_stroke_add(frameLines, color_Black, 33, 40, false); BKE_gpencil_stroke_add_points(gps, data18, 33, mat); + BKE_gpencil_stroke_geometry_update(gps); - gps = BKE_gpencil_add_stroke(frameLines, color_Black, 34, 40); + gps = BKE_gpencil_stroke_add(frameLines, color_Black, 34, 40, false); BKE_gpencil_stroke_add_points(gps, data19, 34, mat); + BKE_gpencil_stroke_geometry_update(gps); - gps = BKE_gpencil_add_stroke(frameLines, color_Black, 33, 60); + gps = BKE_gpencil_stroke_add(frameLines, color_Black, 33, 60, false); BKE_gpencil_stroke_add_points(gps, data20, 33, mat); + BKE_gpencil_stroke_geometry_update(gps); - gps = BKE_gpencil_add_stroke(frameLines, color_Black, 64, 60); + gps = BKE_gpencil_stroke_add(frameLines, color_Black, 64, 60, false); BKE_gpencil_stroke_add_points(gps, data21, 64, mat); + BKE_gpencil_stroke_geometry_update(gps); - gps = BKE_gpencil_add_stroke(frameLines, color_Pupils, 26, 60); + gps = BKE_gpencil_stroke_add(frameLines, color_Pupils, 26, 60, false); BKE_gpencil_stroke_add_points(gps, data22, 26, mat); + BKE_gpencil_stroke_geometry_update(gps); - gps = BKE_gpencil_add_stroke(frameLines, color_Pupils, 26, 60); + gps = BKE_gpencil_stroke_add(frameLines, color_Pupils, 26, 60, false); BKE_gpencil_stroke_add_points(gps, data23, 26, mat); + BKE_gpencil_stroke_geometry_update(gps); - gps = BKE_gpencil_add_stroke(frameLines, color_Black, 33, 60); + gps = BKE_gpencil_stroke_add(frameLines, color_Black, 33, 60, false); BKE_gpencil_stroke_add_points(gps, data24, 33, mat); + BKE_gpencil_stroke_geometry_update(gps); - gps = BKE_gpencil_add_stroke(frameLines, color_Black, 18, 40); + gps = BKE_gpencil_stroke_add(frameLines, color_Black, 18, 40, false); BKE_gpencil_stroke_add_points(gps, data25, 18, mat); + BKE_gpencil_stroke_geometry_update(gps); - gps = BKE_gpencil_add_stroke(frameLines, color_Black, 18, 40); + gps = BKE_gpencil_stroke_add(frameLines, color_Black, 18, 40, false); BKE_gpencil_stroke_add_points(gps, data26, 18, mat); + BKE_gpencil_stroke_geometry_update(gps); - gps = BKE_gpencil_add_stroke(frameLines, color_Black, 33, 60); + gps = BKE_gpencil_stroke_add(frameLines, color_Black, 33, 60, false); BKE_gpencil_stroke_add_points(gps, data27, 33, mat); + BKE_gpencil_stroke_geometry_update(gps); /* update depsgraph */ DEG_id_tag_update(&gpd->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY); diff --git a/source/blender/editors/gpencil/gpencil_add_stroke.c b/source/blender/editors/gpencil/gpencil_add_stroke.c index dfcf7f6e765..d4e17144ca2 100644 --- a/source/blender/editors/gpencil/gpencil_add_stroke.c +++ b/source/blender/editors/gpencil/gpencil_add_stroke.c @@ -65,10 +65,13 @@ static int gp_stroke_material(Main *bmain, Object *ob, const ColorTemplate *pct, ma = BKE_gpencil_object_material_new(bmain, ob, pct->name, &idx); copy_v4_v4(ma->gp_style->stroke_rgba, pct->line); + srgb_to_linearrgb_v4(ma->gp_style->stroke_rgba, ma->gp_style->stroke_rgba); + copy_v4_v4(ma->gp_style->fill_rgba, pct->fill); + srgb_to_linearrgb_v4(ma->gp_style->fill_rgba, ma->gp_style->fill_rgba); if (fill) { - ma->gp_style->flag |= GP_STYLE_FILL_SHOW; + ma->gp_style->flag |= GP_MATERIAL_FILL_SHOW; } return idx; @@ -240,8 +243,9 @@ void ED_gpencil_create_stroke(bContext *C, Object *ob, float mat[4][4]) UNUSED_VARS(frame_color); /* generate stroke */ - gps = BKE_gpencil_add_stroke(frame_lines, color_black, 175, 75); + gps = BKE_gpencil_stroke_add(frame_lines, color_black, 175, 75, false); BKE_gpencil_stroke_add_points(gps, data0, 175, mat); + BKE_gpencil_stroke_geometry_update(gps); /* update depsgraph */ DEG_id_tag_update(&gpd->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY); diff --git a/source/blender/editors/gpencil/gpencil_armature.c b/source/blender/editors/gpencil/gpencil_armature.c index c0871fd32fc..02913a19523 100644 --- a/source/blender/editors/gpencil/gpencil_armature.c +++ b/source/blender/editors/gpencil/gpencil_armature.c @@ -357,7 +357,7 @@ static void gpencil_add_verts_to_dgroups( } /* loop all strokes */ - for (bGPDlayer *gpl = gpd->layers.first; gpl; gpl = gpl->next) { + LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd->layers) { bGPDframe *init_gpf = (is_multiedit) ? gpl->frames.first : gpl->actframe; bGPDspoint *pt = NULL; @@ -368,7 +368,7 @@ static void gpencil_add_verts_to_dgroups( continue; } - for (bGPDstroke *gps = gpf->strokes.first; gps; gps = gps->next) { + LISTBASE_FOREACH (bGPDstroke *, gps, &gpf->strokes) { /* skip strokes that are invalid for current view */ if (ED_gpencil_stroke_can_use(C, gps) == false) { continue; diff --git a/source/blender/editors/gpencil/gpencil_brush.c b/source/blender/editors/gpencil/gpencil_brush.c deleted file mode 100644 index 14354ff38f4..00000000000 --- a/source/blender/editors/gpencil/gpencil_brush.c +++ /dev/null @@ -1,2343 +0,0 @@ -/* - * 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 - * Brush based operators for editing Grease Pencil strokes - */ - -/** \file - * \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 "PIL_time.h" - -#include "BLT_translation.h" - -#include "DNA_meshdata_types.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 "DNA_object_types.h" - -#include "BKE_colortools.h" -#include "BKE_context.h" -#include "BKE_deform.h" -#include "BKE_gpencil.h" -#include "BKE_gpencil_modifier.h" -#include "BKE_material.h" -#include "BKE_object_deform.h" -#include "BKE_report.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 "DEG_depsgraph.h" -#include "DEG_depsgraph_query.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... */ - struct Main *bmain; - Scene *scene; - Object *object; - - ScrArea *sa; - ARegion *region; - - /* Current GPencil datablock */ - bGPdata *gpd; - - /* Brush Settings */ - GP_Sculpt_Settings *settings; - GP_Sculpt_Data *gp_brush; - GP_Sculpt_Data *gp_brush_old; - - eGP_Sculpt_Types brush_type; - eGP_Sculpt_Types brush_type_old; - eGP_Sculpt_Flag flag; - eGP_Sculpt_SelectMaskFlag mask; - - /* Space Conversion Data */ - GP_SpaceConversion gsc; - - /* Is the brush currently painting? */ - bool is_painting; - bool is_weight_mode; - bool is_transformed; - - /* Start of new sculpt stroke */ - bool first; - - /* Is multiframe editing enabled, and are we using falloff for that? */ - bool is_multiframe; - bool use_multiframe_falloff; - - /* Current frame */ - int cfra; - - /* Brush Runtime Data: */ - /* - position and pressure - * - the *_prev variants are the previous values - */ - float mval[2], mval_prev[2]; - float pressure, pressure_prev; - - /* - effect vector (e.g. 2D/3D translation for grab brush) */ - float dvec[3]; - - /* rotation for evaluated data */ - float rot_eval; - - /* - multiframe falloff factor */ - float mf_falloff; - - /* active vertex group */ - int vrgroup; - - /* 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 */ - - /* Object invert matrix */ - float inv_mat[4][4]; - - RNG *rng; -} tGP_BrushEditData; - -/* Callback for performing some brush operation on a single point */ -typedef bool (*GP_BrushApplyCb)(tGP_BrushEditData *gso, - bGPDstroke *gps, - float rotation, - int pt_index, - const int radius, - const int co[2]); - -/* ************************************************ */ -/* Utility Functions */ - -/* apply lock axis reset */ -static void gpsculpt_compute_lock_axis(tGP_BrushEditData *gso, - bGPDspoint *pt, - const float save_pt[3]) -{ - if (gso->sa->spacetype != SPACE_VIEW3D) { - return; - } - - const ToolSettings *ts = gso->scene->toolsettings; - const View3DCursor *cursor = &gso->scene->cursor; - const int axis = ts->gp_sculpt.lock_axis; - - /* lock axis control */ - switch (axis) { - case GP_LOCKAXIS_X: { - pt->x = save_pt[0]; - break; - } - case GP_LOCKAXIS_Y: { - pt->y = save_pt[1]; - break; - } - case GP_LOCKAXIS_Z: { - pt->z = save_pt[2]; - break; - } - case GP_LOCKAXIS_CURSOR: { - /* Compute a plane with cursor normal and position of the point before do the sculpt. */ - const float scale[3] = {1.0f, 1.0f, 1.0f}; - float plane_normal[3] = {0.0f, 0.0f, 1.0f}; - float plane[4]; - float mat[4][4]; - float r_close[3]; - - loc_eul_size_to_mat4(mat, cursor->location, cursor->rotation_euler, scale); - - mul_mat3_m4_v3(mat, plane_normal); - plane_from_point_normal_v3(plane, save_pt, plane_normal); - - /* find closest point to the plane with the new position */ - closest_to_plane_v3(r_close, plane, &pt->x); - copy_v3_v3(&pt->x, r_close); - break; - } - default: { - break; - } - } -} - -/* Context ---------------------------------------- */ - -/* Get the sculpting settings */ -static GP_Sculpt_Settings *gpsculpt_get_settings(Scene *scene) -{ - return &scene->toolsettings->gp_sculpt; -} - -/* Get the active brush */ -static GP_Sculpt_Data *gpsculpt_get_brush(Scene *scene, bool is_weight_mode) -{ - GP_Sculpt_Settings *gset = &scene->toolsettings->gp_sculpt; - GP_Sculpt_Data *gp_brush = NULL; - if (is_weight_mode) { - gp_brush = &gset->brush[gset->weighttype]; - } - else { - gp_brush = &gset->brush[gset->brushtype]; - } - - return gp_brush; -} - -/* Brush Operations ------------------------------- */ - -/* Invert behavior 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->gp_brush->flag & GP_SCULPT_FLAG_INVERT) != 0); - - /* During runtime, the user can hold down the Ctrl key to invert the basic behavior */ - if (gso->flag & GP_SCULPT_FLAG_INVERT) { - invert ^= true; - } - - /* set temporary status */ - if (invert) { - gso->gp_brush->flag |= GP_SCULPT_FLAG_TMP_INVERT; - } - else { - gso->gp_brush->flag &= ~GP_SCULPT_FLAG_TMP_INVERT; - } - - return invert; -} - -/* Compute strength of effect */ -static float gp_brush_influence_calc(tGP_BrushEditData *gso, const int radius, const int co[2]) -{ - GP_Sculpt_Data *gp_brush = gso->gp_brush; - - /* basic strength factor from brush settings */ - float influence = gp_brush->strength; - - /* use pressure? */ - if (gp_brush->flag & GP_SCULPT_FLAG_USE_PRESSURE) { - influence *= gso->pressure; - } - - /* distance fading */ - if (gp_brush->flag & GP_SCULPT_FLAG_USE_FALLOFF) { - int mval_i[2]; - round_v2i_v2fl(mval_i, gso->mval); - float distance = (float)len_v2v2_int(mval_i, co); - float fac; - - CLAMP(distance, 0.0f, (float)radius); - fac = 1.0f - (distance / (float)radius); - - influence *= fac; - } - - /* apply multiframe falloff */ - influence *= gso->mf_falloff; - - /* return influence */ - return influence; -} - -/* Force recal filling data */ -static void gp_recalc_geometry(bGPDstroke *gps) -{ - bGPDstroke *gps_orig = gps->runtime.gps_orig; - if (gps_orig) { - gps_orig->flag |= GP_STROKE_RECALC_GEOMETRY; - gps_orig->tot_triangles = 0; - } - else { - gps->flag |= GP_STROKE_RECALC_GEOMETRY; - gps->tot_triangles = 0; - } -} - -/* ************************************************ */ -/* 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, - float UNUSED(rot_eval), - int pt_index, - const int radius, - const int co[2]) -{ - // GP_Sculpt_Data *gp_brush = gso->brush; - float inf = gp_brush_influence_calc(gso, radius, co); - /* need one flag enabled by default */ - if ((gso->settings->flag & - (GP_SCULPT_SETT_FLAG_APPLY_POSITION | GP_SCULPT_SETT_FLAG_APPLY_STRENGTH | - GP_SCULPT_SETT_FLAG_APPLY_THICKNESS | GP_SCULPT_SETT_FLAG_APPLY_UV)) == 0) { - gso->settings->flag |= GP_SCULPT_SETT_FLAG_APPLY_POSITION; - } - - /* perform smoothing */ - if (gso->settings->flag & GP_SCULPT_SETT_FLAG_APPLY_POSITION) { - BKE_gpencil_smooth_stroke(gps, pt_index, inf); - } - if (gso->settings->flag & GP_SCULPT_SETT_FLAG_APPLY_STRENGTH) { - BKE_gpencil_smooth_stroke_strength(gps, pt_index, inf); - } - if (gso->settings->flag & GP_SCULPT_SETT_FLAG_APPLY_THICKNESS) { - BKE_gpencil_smooth_stroke_thickness(gps, pt_index, inf); - } - if (gso->settings->flag & GP_SCULPT_SETT_FLAG_APPLY_UV) { - BKE_gpencil_smooth_stroke_uv(gps, pt_index, inf); - } - - gp_recalc_geometry(gps); - - 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, - float UNUSED(rot_eval), - int pt_index, - const int radius, - const int co[2]) -{ - bGPDspoint *pt = gps->points + pt_index; - 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; -} - -/* ----------------------------------------------- */ -/* Color Strength Brush */ - -/* Make color more or less transparent by the specified amounts */ -static bool gp_brush_strength_apply(tGP_BrushEditData *gso, - bGPDstroke *gps, - float UNUSED(rot_eval), - int pt_index, - const int radius, - const int co[2]) -{ - bGPDspoint *pt = gps->points + pt_index; - float inf; - - /* Compute strength of effect - * - We divide the strength, 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) / 2.0f; - CLAMP_MIN(inf, 0.01f); - - /* apply */ - if (gp_brush_invert_check(gso)) { - /* make line more transparent - reduce alpha factor */ - pt->strength -= inf; - } - else { - /* make line more opaque - increase stroke strength */ - pt->strength += inf; - } - /* Strength should stay within [0.0, 1.0] */ - CLAMP(pt->strength, 0.0f, 1.0f); - - /* smooth the strength */ - BKE_gpencil_smooth_stroke_strength(gps, pt_index, inf); - - 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; - /* angles to calc transformation */ - float *rot_eval; - - /* 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); - memset(data->rot_eval, 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"); - data->rot_eval = MEM_callocN(sizeof(float) * data->capacity, "GP Stroke Grab Rotations"); - - /* 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, - float rot_eval, - int pt_index, - 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] = pt_index; - data->weights[data->size] = inf; - data->rot_eval[data->size] = rot_eval; - 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) { - RegionView3D *rv3d = gso->region->regiondata; - float *rvec = gso->object->loc; - 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]); - - /* apply evaluated data transformation */ - if (gso->rot_eval != 0.0f) { - const float cval = cos(gso->rot_eval); - const float sval = sin(gso->rot_eval); - float r[2]; - r[0] = (mval_f[0] * cval) - (mval_f[1] * sval); - r[1] = (mval_f[0] * sval) + (mval_f[1] * cval); - copy_v2_v2(mval_f, r); - } - - ED_view3d_win_to_delta(gso->region, 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, - const float diff_mat[4][4]) -{ - 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}; - - /* get evaluated transformation */ - gso->rot_eval = data->rot_eval[i]; - gp_brush_grab_calc_dvec(gso); - - /* adjust the amount of displacement to apply */ - mul_v3_v3fl(delta, gso->dvec, data->weights[i]); - - float fpt[3]; - float save_pt[3]; - copy_v3_v3(save_pt, &pt->x); - /* apply transformation */ - mul_v3_m4v3(fpt, diff_mat, &pt->x); - /* apply */ - add_v3_v3v3(&pt->x, fpt, delta); - /* undo transformation to the init parent position */ - float inverse_diff_mat[4][4]; - invert_m4_m4(inverse_diff_mat, diff_mat); - mul_m4_v3(inverse_diff_mat, &pt->x); - - /* compute lock axis */ - gpsculpt_compute_lock_axis(gso, pt, save_pt); - } - gp_recalc_geometry(gps); -} - -/* 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_SAFE_FREE(data->points); - MEM_SAFE_FREE(data->weights); - MEM_SAFE_FREE(data->rot_eval); - - /* ... 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, - float UNUSED(rot_eval), - int pt_index, - const int radius, - const int co[2]) -{ - bGPDspoint *pt = gps->points + pt_index; - float save_pt[3]; - copy_v3_v3(save_pt, &pt->x); - - 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 */ - mul_mat3_m4_v3(gso->inv_mat, delta); /* only rotation component */ - add_v3_v3(&pt->x, delta); - - /* compute lock axis */ - gpsculpt_compute_lock_axis(gso, pt, save_pt); - - gps->flag |= GP_STROKE_RECALC_GEOMETRY; - - /* 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() - */ - RegionView3D *rv3d = gso->region->regiondata; - const float *rvec = gso->object->loc; - float zfac = ED_view3d_calc_zfac(rv3d, rvec, NULL); - - float mval_f[2]; - copy_v2_v2(mval_f, gso->mval); - float mval_prj[2]; - float dvec[3]; - - if (ED_view3d_project_float_global(gso->region, 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->region, 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, - float UNUSED(rot_eval), - int pt_index, - const int radius, - const int co[2]) -{ - bGPDspoint *pt = gps->points + pt_index; - float fac, inf; - float vec[3]; - float save_pt[3]; - copy_v3_v3(save_pt, &pt->x); - - /* 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) */ - float fpt[3]; - mul_v3_m4v3(fpt, gso->object->obmat, &pt->x); - sub_v3_v3v3(vec, fpt, 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(fpt, gso->dvec, vec); - mul_v3_m4v3(&pt->x, gso->object->imat, fpt); - - /* compute lock axis */ - gpsculpt_compute_lock_axis(gso, pt, save_pt); - - gp_recalc_geometry(gps); - - /* 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, - float UNUSED(rot_eval), - int pt_index, - const int radius, - const int co[2]) -{ - bGPDspoint *pt = gps->points + pt_index; - float angle, inf; - float save_pt[3]; - copy_v3_v3(save_pt, &pt->x); - - /* 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; - } - - /* Rotate in 2D or 3D space? */ - if (gps->flag & GP_STROKE_3DSPACE) { - /* Perform rotation in 3D space... */ - RegionView3D *rv3d = gso->region->regiondata; - float rmat[3][3]; - float axis[3]; - float vec[3]; - - /* Compute rotation matrix - rotate around view vector by angle */ - negate_v3_v3(axis, rv3d->persinv[2]); - normalize_v3(axis); - - axis_angle_normalized_to_mat3(rmat, axis, angle); - - /* Rotate point */ - float fpt[3]; - mul_v3_m4v3(fpt, gso->object->obmat, &pt->x); - sub_v3_v3v3(vec, fpt, gso->dvec); /* make relative to center - * (center is stored in dvec) */ - mul_m3_v3(rmat, vec); - add_v3_v3v3(fpt, vec, gso->dvec); /* restore */ - mul_v3_m4v3(&pt->x, gso->object->imat, fpt); - - /* compute lock axis */ - gpsculpt_compute_lock_axis(gso, pt, save_pt); - } - else { - const float axis[3] = {0.0f, 0.0f, 1.0f}; - float vec[3] = {0.0f}; - float rmat[3][3]; - - /* Express position of point relative to cursor, ready to rotate */ - // XXX: There is still some offset here, but it's close to working as expected... - vec[0] = (float)(co[0] - gso->mval[0]); - vec[1] = (float)(co[1] - gso->mval[1]); - - /* rotate point */ - axis_angle_normalized_to_mat3(rmat, axis, angle); - mul_m3_v3(rmat, vec); - - /* Convert back to screen-coordinates */ - vec[0] += (float)gso->mval[0]; - vec[1] += (float)gso->mval[1]; - - /* Map from screen-coordinates to final coordinate space */ - if (gps->flag & GP_STROKE_2DSPACE) { - View2D *v2d = gso->gsc.v2d; - UI_view2d_region_to_view(v2d, vec[0], vec[1], &pt->x, &pt->y); - } - else { - // XXX - copy_v2_v2(&pt->x, vec); - } - } - - gp_recalc_geometry(gps); - - /* done */ - return true; -} - -/* ----------------------------------------------- */ -/* Randomize Brush */ -/* Apply some random jitter to the point */ -static bool gp_brush_randomize_apply(tGP_BrushEditData *gso, - bGPDstroke *gps, - float UNUSED(rot_eval), - int pt_index, - const int radius, - const int co[2]) -{ - bGPDspoint *pt = gps->points + pt_index; - float save_pt[3]; - copy_v3_v3(save_pt, &pt->x); - - /* 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_rng_get_float(gso->rng) * inf; - /* need one flag enabled by default */ - if ((gso->settings->flag & - (GP_SCULPT_SETT_FLAG_APPLY_POSITION | GP_SCULPT_SETT_FLAG_APPLY_STRENGTH | - GP_SCULPT_SETT_FLAG_APPLY_THICKNESS | GP_SCULPT_SETT_FLAG_APPLY_UV)) == 0) { - gso->settings->flag |= GP_SCULPT_SETT_FLAG_APPLY_POSITION; - } - - /* apply random to position */ - if (gso->settings->flag & GP_SCULPT_SETT_FLAG_APPLY_POSITION) { - /* 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 - * view-space as needed. - */ - float mvec[2], svec[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]; - - /* scale the displacement by the random displacement, and apply */ - if (BLI_rng_get_float(gso->rng) > 0.5f) { - mul_v2_fl(svec, -fac); - } - else { - mul_v2_fl(svec, fac); - } - - /* convert to dataspace */ - if (gps->flag & GP_STROKE_3DSPACE) { - /* 3D: Project to 3D space */ - if (gso->sa->spacetype == SPACE_VIEW3D) { - bool flip; - RegionView3D *rv3d = gso->region->regiondata; - float zfac = ED_view3d_calc_zfac(rv3d, &pt->x, &flip); - if (flip == false) { - float dvec[3]; - ED_view3d_win_to_delta(gso->gsc.region, svec, dvec, zfac); - add_v3_v3(&pt->x, dvec); - /* compute lock axis */ - gpsculpt_compute_lock_axis(gso, pt, save_pt); - } - } - else { - /* ERROR */ - BLI_assert(!"3D stroke being sculpted in non-3D view"); - } - } - else { - /* 2D: As-is */ - // XXX: v2d scaling/offset? - float nco[2]; - nco[0] = (float)co[0] + svec[0]; - nco[1] = (float)co[1] + svec[1]; - - copy_v2_v2(&pt->x, nco); - } - } - /* apply random to strength */ - if (gso->settings->flag & GP_SCULPT_SETT_FLAG_APPLY_STRENGTH) { - if (BLI_rng_get_float(gso->rng) > 0.5f) { - pt->strength += fac; - } - else { - pt->strength -= fac; - } - CLAMP_MIN(pt->strength, 0.0f); - CLAMP_MAX(pt->strength, 1.0f); - } - /* apply random to thickness (use pressure) */ - if (gso->settings->flag & GP_SCULPT_SETT_FLAG_APPLY_THICKNESS) { - if (BLI_rng_get_float(gso->rng) > 0.5f) { - pt->pressure += fac; - } - else { - pt->pressure -= fac; - } - /* only limit lower value */ - CLAMP_MIN(pt->pressure, 0.0f); - } - /* apply random to UV (use pressure) */ - if (gso->settings->flag & GP_SCULPT_SETT_FLAG_APPLY_UV) { - if (BLI_rng_get_float(gso->rng) > 0.5f) { - pt->uv_rot += fac; - } - else { - pt->uv_rot -= fac; - } - CLAMP(pt->uv_rot, -M_PI_2, M_PI_2); - } - - gp_recalc_geometry(gps); - - /* done */ - return true; -} - -/* Weight Paint Brush */ -/* Change weight paint for vertex groups */ -static bool gp_brush_weight_apply(tGP_BrushEditData *gso, - bGPDstroke *gps, - float UNUSED(rot_eval), - int pt_index, - const int radius, - const int co[2]) -{ - /* create dvert */ - BKE_gpencil_dvert_ensure(gps); - - MDeformVert *dvert = gps->dvert + pt_index; - 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; - - /* need a vertex group */ - if (gso->vrgroup == -1) { - if (gso->object) { - BKE_object_defgroup_add(gso->object); - DEG_relations_tag_update(gso->bmain); - gso->vrgroup = 0; - } - } - else { - bDeformGroup *defgroup = BLI_findlink(&gso->object->defbase, gso->vrgroup); - if (defgroup->flag & DG_LOCK_WEIGHT) { - return false; - } - } - /* get current weight */ - MDeformWeight *dw = BKE_defvert_ensure_index(dvert, gso->vrgroup); - float curweight = dw ? dw->weight : 0.0f; - - if (gp_brush_invert_check(gso)) { - curweight -= inf; - } - else { - /* increase weight */ - curweight += inf; - /* verify maximum target weight */ - CLAMP_MAX(curweight, gso->gp_brush->weight); - } - - CLAMP(curweight, 0.0f, 1.0f); - if (dw) { - dw->weight = curweight; - } - - 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 follows the cursor - * "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; - - /** Mapping from colors referenced per stroke, to the new colors in the "pasted" strokes. */ - GHash *new_colors; -} 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"); - } - - /* Init colormap for mapping between the pasted stroke's source color (names) - * and the final colors that will be used here instead. - */ - data->new_colors = gp_copybuf_validate_colormap(C); -} - -/* 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 copybuf colormap */ - if (data->new_colors) { - BLI_ghash_free(data->new_colors, NULL, NULL); - data->new_colors = 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; - - Object *ob = CTX_data_active_object(C); - bGPdata *gpd = (bGPdata *)ob->data; - Scene *scene = CTX_data_scene(C); - 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; - - bGPDlayer *gpl = NULL; - /* Try to use original layer. */ - if (gps->runtime.tmp_layerinfo != NULL) { - gpl = BLI_findstring(&gpd->layers, gps->runtime.tmp_layerinfo, offsetof(bGPDlayer, info)); - } - - /* if not available, use active layer. */ - if (gpl == NULL) { - gpl = CTX_data_active_gpencil_layer(C); - } - bGPDframe *gpf = BKE_gpencil_layer_getframe(gpl, CFRA, GP_GETFRAME_ADD_NEW); - - /* Make a new stroke */ - new_stroke = MEM_dupallocN(gps); - - new_stroke->points = MEM_dupallocN(gps->points); - if (gps->dvert != NULL) { - new_stroke->dvert = MEM_dupallocN(gps->dvert); - BKE_gpencil_stroke_weights_duplicate(gps, new_stroke); - } - new_stroke->triangles = MEM_dupallocN(gps->triangles); - - new_stroke->next = new_stroke->prev = NULL; - BLI_addtail(&gpf->strokes, new_stroke); - - /* Fix color references */ - Material *ma = BLI_ghash_lookup(data->new_colors, POINTER_FROM_INT(new_stroke->mat_nr)); - new_stroke->mat_nr = BKE_gpencil_object_material_get_index(ob, ma); - if (!ma || new_stroke->mat_nr < 0) { - new_stroke->mat_nr = 0; - } - /* 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++) { - /* Rotate around center new position */ - mul_mat3_m4_v3(gso->object->obmat, &pt->x); /* only rotation component */ - - /* assume that the delta can just be applied, and then everything works */ - add_v3_v3(&pt->x, delta); - mul_m4_v3(gso->object->imat, &pt->x); - } - - /* 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(tGP_BrushEditData *gso) -{ - tGPSB_CloneBrushData *data = gso->customdata; - size_t snum; - - /* Compute the amount of movement to apply (overwrites dvec) */ - gso->rot_eval = 0.0f; - 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->gp_brush->flag & GP_SCULPT_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->gp_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 Continuous Mode */ - if (1 /*gso->brush->mode == GP_EDITBRUSH_CLONE_MODE_STAMP*/) { - /* Stamp - Proceed to translate the newly added strokes */ - gp_brush_clone_adjust(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; -} - -/* ************************************************ */ -/* Header Info for GPencil Sculpt */ - -static void gpsculpt_brush_header_set(bContext *C, tGP_BrushEditData *gso) -{ - const char *brush_name = NULL; - char str[UI_MAX_DRAW_STR] = ""; - - RNA_enum_name(rna_enum_gpencil_sculpt_brush_items, gso->brush_type, &brush_name); - - BLI_snprintf(str, - sizeof(str), - TIP_("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_workspace_status_text(C, str); -} - -/* ************************************************ */ -/* Grease Pencil Sculpting Operator */ - -/* Init/Exit ----------------------------------------------- */ - -static bool gpsculpt_brush_init(bContext *C, wmOperator *op) -{ - Scene *scene = CTX_data_scene(C); - ToolSettings *ts = CTX_data_tool_settings(C); - Object *ob = CTX_data_active_object(C); - - const bool is_weight_mode = ob->mode == OB_MODE_WEIGHT_GPENCIL; - /* set the brush using the tool */ -#if 0 - GP_Sculpt_Settings *gset = &ts->gp_sculpt; - eGP_Sculpt_Types mode = is_weight_mode ? gset->weighttype : gset->brushtype; -#endif - tGP_BrushEditData *gso; - - /* setup operator data */ - gso = MEM_callocN(sizeof(tGP_BrushEditData), "tGP_BrushEditData"); - op->customdata = gso; - - gso->bmain = CTX_data_main(C); - /* store state */ - gso->settings = gpsculpt_get_settings(scene); - gso->gp_brush = gpsculpt_get_brush(scene, is_weight_mode); - gso->is_weight_mode = is_weight_mode; - - if (is_weight_mode) { - gso->brush_type = gso->settings->weighttype; - } - else { - gso->brush_type = gso->settings->brushtype; - } - - /* Random generator, only init once. */ - uint rng_seed = (uint)(PIL_check_seconds_timer_i() & UINT_MAX); - rng_seed ^= POINTER_AS_UINT(gso); - gso->rng = BLI_rng_new(rng_seed); - - 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() */ - - /* some brushes cannot use pressure for radius */ - if (ELEM(gso->brush_type, GP_SCULPT_TYPE_GRAB, GP_SCULPT_TYPE_CLONE)) { - gso->gp_brush->flag &= ~GP_SCULPT_FLAG_PRESSURE_RADIUS; - } - - gso->scene = scene; - gso->object = ob; - if (ob) { - invert_m4_m4(gso->inv_mat, ob->obmat); - gso->vrgroup = ob->actdef - 1; - if (!BLI_findlink(&ob->defbase, gso->vrgroup)) { - gso->vrgroup = -1; - } - /* Check if some modifier can transform the stroke. */ - gso->is_transformed = BKE_gpencil_has_transform_modifiers(ob); - } - else { - unit_m4(gso->inv_mat); - gso->vrgroup = -1; - gso->is_transformed = false; - } - - gso->sa = CTX_wm_area(C); - gso->region = CTX_wm_region(C); - - /* save mask */ - gso->mask = ts->gpencil_selectmode_sculpt; - - /* multiframe settings */ - gso->is_multiframe = (bool)GPENCIL_MULTIEDIT_SESSIONS_ON(gso->gpd); - gso->use_multiframe_falloff = (ts->gp_sculpt.flag & GP_SCULPT_SETT_FLAG_FRAME_FALLOFF) != 0; - - /* Init multi-edit falloff curve data before doing anything, - * so we won't have to do it again later. */ - if (gso->is_multiframe) { - BKE_curvemapping_initialize(ts->gp_sculpt.cur_falloff); - } - - /* initialise custom data for brushes */ - switch (gso->brush_type) { - case GP_SCULPT_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_SCULPT_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), WM_CURSOR_CROSS); - if (gso->sa->spacetype != SPACE_VIEW3D) { - ED_gpencil_toggle_brush_cursor(C, true, NULL); - } - 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_SCULPT_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_SCULPT_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); - } - - if (gso->rng != NULL) { - BLI_rng_free(gso->rng); - } - - /* disable cursor and headerprints */ - ED_workspace_status_text(C, NULL); - WM_cursor_modal_restore(win); - if (gso->sa->spacetype != SPACE_VIEW3D) { - ED_gpencil_toggle_brush_cursor(C, false, NULL); - } - - /* disable temp invert flag */ - gso->gp_brush->flag &= ~GP_SCULPT_FLAG_TMP_INVERT; - - /* free operator data */ - MEM_freeN(gso); - op->customdata = NULL; -} - -/* poll callback for stroke sculpting operator(s) */ -static bool 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(bContext *C, tGP_BrushEditData *gso) -{ - bGPdata *gpd = gso->gpd; - - bGPDlayer *gpl; - Scene *scene = gso->scene; - 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 (gpencil_layer_is_editable(gpl) && (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. - */ - if (gpf->framenum != cfra) { - BKE_gpencil_frame_addcopy(gpl, cfra); - /* Need tag to recalculate evaluated data to avoid crashes. */ - DEG_id_tag_update(&gso->gpd->id, ID_RECALC_GEOMETRY | ID_RECALC_COPY_ON_WRITE); - WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL); - } - } - } - - /* save off new current frame, so that next update works fine */ - gso->cfra = cfra; -} - -/* Apply ----------------------------------------------- */ - -/* Get angle of the segment relative to the original segment before any transformation - * For strokes with one point only this is impossible to calculate because there isn't a - * valid reference point. - */ -static float gpsculpt_rotation_eval_get(tGP_BrushEditData *gso, - bGPDstroke *gps_eval, - bGPDspoint *pt_eval, - int idx_eval) -{ - /* If multiframe or no modifiers, return 0. */ - if ((GPENCIL_MULTIEDIT_SESSIONS_ON(gso->gpd)) || (!gso->is_transformed)) { - return 0.0f; - } - - GP_SpaceConversion *gsc = &gso->gsc; - bGPDstroke *gps_orig = gps_eval->runtime.gps_orig; - bGPDspoint *pt_orig = &gps_orig->points[pt_eval->runtime.idx_orig]; - bGPDspoint *pt_prev_eval = NULL; - bGPDspoint *pt_orig_prev = NULL; - if (idx_eval != 0) { - pt_prev_eval = &gps_eval->points[idx_eval - 1]; - } - else { - if (gps_eval->totpoints > 1) { - pt_prev_eval = &gps_eval->points[idx_eval + 1]; - } - else { - return 0.0f; - } - } - - if (pt_eval->runtime.idx_orig != 0) { - pt_orig_prev = &gps_orig->points[pt_eval->runtime.idx_orig - 1]; - } - else { - if (gps_orig->totpoints > 1) { - pt_orig_prev = &gps_orig->points[pt_eval->runtime.idx_orig + 1]; - } - else { - return 0.0f; - } - } - - /* create 2D vectors of the stroke segments */ - float v_orig_a[2], v_orig_b[2], v_eval_a[2], v_eval_b[2]; - - gp_point_3d_to_xy(gsc, GP_STROKE_3DSPACE, &pt_orig->x, v_orig_a); - gp_point_3d_to_xy(gsc, GP_STROKE_3DSPACE, &pt_orig_prev->x, v_orig_b); - sub_v2_v2(v_orig_a, v_orig_b); - - gp_point_3d_to_xy(gsc, GP_STROKE_3DSPACE, &pt_eval->x, v_eval_a); - gp_point_3d_to_xy(gsc, GP_STROKE_3DSPACE, &pt_prev_eval->x, v_eval_b); - sub_v2_v2(v_eval_a, v_eval_b); - - return angle_v2v2(v_orig_a, v_eval_a); -} - -/* Apply brush operation to points in this stroke */ -static bool gpsculpt_brush_do_stroke(tGP_BrushEditData *gso, - bGPDstroke *gps, - const float diff_mat[4][4], - GP_BrushApplyCb apply) -{ - GP_SpaceConversion *gsc = &gso->gsc; - rcti *rect = &gso->brush_rect; - GP_Sculpt_Data *gp_brush = gso->gp_brush; - const int radius = (gp_brush->flag & GP_SCULPT_FLAG_PRESSURE_RADIUS) ? - gso->gp_brush->size * gso->pressure : - gso->gp_brush->size; - const bool is_multiedit = (bool)GPENCIL_MULTIEDIT_SESSIONS_ON(gso->gpd); - bGPDstroke *gps_active = (!is_multiedit) ? gps->runtime.gps_orig : gps; - bGPDspoint *pt_active = NULL; - - bGPDspoint *pt1, *pt2; - bGPDspoint *pt = NULL; - int pc1[2] = {0}; - int pc2[2] = {0}; - int i; - int index; - bool include_last = false; - bool changed = false; - float rot_eval = 0.0f; - if (gps->totpoints == 1) { - bGPDspoint pt_temp; - pt = &gps->points[0]; - gp_point_to_parent_space(gps->points, diff_mat, &pt_temp); - gp_point_to_xy(gsc, gps, &pt_temp, &pc1[0], &pc1[1]); - - pt_active = (!is_multiedit) ? pt->runtime.pt_orig : pt; - /* 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 */ - int mval_i[2]; - round_v2i_v2fl(mval_i, gso->mval); - if (len_v2v2_int(mval_i, pc1) <= radius) { - /* apply operation to this point */ - if (pt_active != NULL) { - rot_eval = gpsculpt_rotation_eval_get(gso, gps, pt, 0); - changed = apply(gso, gps_active, rot_eval, 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 ((GPENCIL_ANY_SCULPT_MASK(gso->mask)) && (!gso->is_weight_mode)) { - if (!(pt1->flag & GP_SPOINT_SELECT) && !(pt2->flag & GP_SPOINT_SELECT)) { - include_last = false; - continue; - } - } - bGPDspoint npt; - gp_point_to_parent_space(pt1, diff_mat, &npt); - gp_point_to_xy(gsc, gps, &npt, &pc1[0], &pc1[1]); - - gp_point_to_parent_space(pt2, diff_mat, &npt); - gp_point_to_xy(gsc, gps, &npt, &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 - * brush 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... */ - pt = &gps->points[i]; - pt_active = (!is_multiedit) ? pt->runtime.pt_orig : pt; - index = (!is_multiedit) ? pt->runtime.idx_orig : i; - if (pt_active != NULL) { - rot_eval = gpsculpt_rotation_eval_get(gso, gps, pt, i); - ok = apply(gso, gps_active, rot_eval, index, 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) { - pt = &gps->points[i + 1]; - pt_active = (!is_multiedit) ? pt->runtime.pt_orig : pt; - index = (!is_multiedit) ? pt->runtime.idx_orig : i + 1; - if (pt_active != NULL) { - rot_eval = gpsculpt_rotation_eval_get(gso, gps, pt, i + 1); - ok |= apply(gso, gps_active, rot_eval, index, 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). - */ - pt = &gps->points[i]; - pt_active = (!is_multiedit) ? pt->runtime.pt_orig : pt; - index = (!is_multiedit) ? pt->runtime.idx_orig : i; - if (pt_active != NULL) { - rot_eval = gpsculpt_rotation_eval_get(gso, gps, pt, i); - changed |= apply(gso, gps_active, rot_eval, index, radius, pc1); - include_last = false; - } - } - } - } - } - - return changed; -} - -/* Apply sculpt brushes to strokes in the given frame */ -static bool gpsculpt_brush_do_frame(bContext *C, - tGP_BrushEditData *gso, - bGPDlayer *gpl, - bGPDframe *gpf, - const float diff_mat[4][4]) -{ - bool changed = false; - Object *ob = CTX_data_active_object(C); - const bool is_multiedit = (bool)GPENCIL_MULTIEDIT_SESSIONS_ON(gso->gpd); - - for (bGPDstroke *gps = gpf->strokes.first; gps; gps = gps->next) { - /* skip strokes that are invalid for current view */ - if (ED_gpencil_stroke_can_use(C, gps) == false) { - continue; - } - /* check if the color is editable */ - if (ED_gpencil_stroke_color_use(ob, gpl, gps) == false) { - continue; - } - - switch (gso->brush_type) { - case GP_SCULPT_TYPE_SMOOTH: /* Smooth strokes */ - { - changed |= gpsculpt_brush_do_stroke(gso, gps, diff_mat, gp_brush_smooth_apply); - break; - } - - case GP_SCULPT_TYPE_THICKNESS: /* Adjust stroke thickness */ - { - changed |= gpsculpt_brush_do_stroke(gso, gps, diff_mat, gp_brush_thickness_apply); - break; - } - - case GP_SCULPT_TYPE_STRENGTH: /* Adjust stroke color strength */ - { - changed |= gpsculpt_brush_do_stroke(gso, gps, diff_mat, gp_brush_strength_apply); - break; - } - - case GP_SCULPT_TYPE_GRAB: /* Grab points */ - { - bGPDstroke *gps_active = (!is_multiedit) ? gps->runtime.gps_orig : gps; - if (gps_active != NULL) { - 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_active); - changed |= gpsculpt_brush_do_stroke(gso, gps, diff_mat, gp_brush_grab_store_points); - } - else { - /* Apply effect to the stored points */ - gp_brush_grab_apply_cached(gso, gps_active, diff_mat); - changed |= true; - } - } - break; - } - - case GP_SCULPT_TYPE_PUSH: /* Push points */ - { - changed |= gpsculpt_brush_do_stroke(gso, gps, diff_mat, gp_brush_push_apply); - break; - } - - case GP_SCULPT_TYPE_PINCH: /* Pinch points */ - { - changed |= gpsculpt_brush_do_stroke(gso, gps, diff_mat, gp_brush_pinch_apply); - break; - } - - case GP_SCULPT_TYPE_TWIST: /* Twist points around midpoint */ - { - changed |= gpsculpt_brush_do_stroke(gso, gps, diff_mat, gp_brush_twist_apply); - break; - } - - case GP_SCULPT_TYPE_RANDOMIZE: /* Apply jitter */ - { - changed |= gpsculpt_brush_do_stroke(gso, gps, diff_mat, gp_brush_randomize_apply); - break; - } - - case GP_SCULPT_TYPE_WEIGHT: /* Adjust vertex group weight */ - { - changed |= gpsculpt_brush_do_stroke(gso, gps, diff_mat, gp_brush_weight_apply); - break; - } - - default: - printf("ERROR: Unknown type of GPencil Sculpt brush - %u\n", gso->brush_type); - break; - } - /* Triangulation must be calculated if changed */ - gp_recalc_geometry(gps); - } - - return changed; -} - -/* Perform two-pass brushes which modify the existing strokes */ -static bool gpsculpt_brush_apply_standard(bContext *C, tGP_BrushEditData *gso) -{ - ToolSettings *ts = CTX_data_tool_settings(C); - Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C); - Object *obact = gso->object; - Object *ob_eval = DEG_get_evaluated_object(depsgraph, obact); - bGPdata *gpd = gso->gpd; - bool changed = false; - - /* Calculate brush-specific data which applies equally to all points */ - switch (gso->brush_type) { - case GP_SCULPT_TYPE_GRAB: /* Grab points */ - case GP_SCULPT_TYPE_PUSH: /* Push points */ - { - /* calculate amount of displacement to apply */ - gso->rot_eval = 0.0f; - gp_brush_grab_calc_dvec(gso); - break; - } - - case GP_SCULPT_TYPE_PINCH: /* Pinch points */ - case GP_SCULPT_TYPE_TWIST: /* Twist points around midpoint */ - { - /* calculate midpoint of the brush (in data space) */ - gp_brush_calc_midpoint(gso); - break; - } - - case GP_SCULPT_TYPE_RANDOMIZE: /* Random jitter */ - { - /* compute the displacement vector for the cursor (in data space) */ - gso->rot_eval = 0.0f; - gp_brush_grab_calc_dvec(gso); - break; - } - - default: - break; - } - - /* Find visible strokes, and perform operations on those if hit */ - CTX_DATA_BEGIN (C, bGPDlayer *, gpl, editable_gpencil_layers) { - /* If no active frame, don't do anything... */ - if (gpl->actframe == NULL) { - continue; - } - /* Get evaluated frames array data */ - int idx_eval = BLI_findindex(&gpd->layers, gpl); - bGPDframe *gpf_eval = &ob_eval->runtime.gpencil_evaluated_frames[idx_eval]; - if (gpf_eval == NULL) { - continue; - } - - /* calculate difference matrix */ - float diff_mat[4][4]; - ED_gpencil_parent_location(depsgraph, obact, gpd, gpl, diff_mat); - - /* Active Frame or MultiFrame? */ - if (gso->is_multiframe) { - /* init multiframe falloff options */ - int f_init = 0; - int f_end = 0; - - if (gso->use_multiframe_falloff) { - BKE_gpencil_get_range_selected(gpl, &f_init, &f_end); - } - - for (bGPDframe *gpf = gpl->frames.first; gpf; gpf = gpf->next) { - /* Always do active frame; Otherwise, only include selected frames */ - if ((gpf == gpl->actframe) || (gpf->flag & GP_FRAME_SELECT)) { - /* compute multiframe falloff factor */ - if (gso->use_multiframe_falloff) { - /* Faloff depends on distance to active frame (relative to the overall frame range) */ - gso->mf_falloff = BKE_gpencil_multiframe_falloff_calc( - gpf, gpl->actframe->framenum, f_init, f_end, ts->gp_sculpt.cur_falloff); - } - else { - /* No falloff */ - gso->mf_falloff = 1.0f; - } - - /* affect strokes in this frame */ - changed |= gpsculpt_brush_do_frame(C, gso, gpl, gpf, diff_mat); - } - } - } - else { - /* Apply to active frame's strokes */ - gso->mf_falloff = 1.0f; - changed |= gpsculpt_brush_do_frame(C, gso, gpl, gpf_eval, diff_mat); - } - } - 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; - GP_Sculpt_Data *gp_brush = gso->gp_brush; - const int radius = ((gp_brush->flag & GP_SCULPT_FLAG_PRESSURE_RADIUS) ? - gso->gp_brush->size * gso->pressure : - gso->gp_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_SCULPT_FLAG_INVERT; - } - else { - gso->flag &= ~GP_SCULPT_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_SCULPT_TYPE_CLONE) { - changed = gpsculpt_brush_apply_clone(C, gso); - } - else { - changed = gpsculpt_brush_apply_standard(C, gso); - } - - /* Updates */ - if (changed) { - DEG_id_tag_update(&gso->gpd->id, ID_RECALC_GEOMETRY); - 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; - ToolSettings *ts = CTX_data_tool_settings(C); - GP_Sculpt_Settings *gset = &ts->gp_sculpt; - PointerRNA itemptr; - float mouse[2]; - - 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 and otherwise 1.0) */ - float pressure = event->tablet.pressure; - /* special exception here for too high pressure values on first touch in - * windows for some tablets: clamp the values to be sane */ - if (pressure >= 0.99f) { - pressure = 1.0f; - } - RNA_float_set(&itemptr, "pressure", pressure); - - if (!gso->is_weight_mode) { - if (event->shift) { - gso->gp_brush_old = gso->gp_brush; - gso->brush_type_old = gso->brush_type; - - gso->gp_brush = &gset->brush[GP_SCULPT_TYPE_SMOOTH]; - gso->brush_type = GP_SCULPT_TYPE_SMOOTH; - } - else { - if (gso->gp_brush_old != NULL) { - gso->gp_brush = gso->gp_brush_old; - gso->brush_type = gso->brush_type_old; - } - } - } - - /* 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"); - const bool is_playing = ED_screen_animation_playing(CTX_wm_manager(C)) != NULL; - bool needs_timer = false; - float brush_rate = 0.0f; - - /* the operator cannot work while play animation */ - if (is_playing) { - BKE_report(op->reports, RPT_ERROR, "Cannot sculpt while play animation"); - - return OPERATOR_CANCELLED; - } - - /* 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_SCULPT_TYPE_THICKNESS: - brush_rate = 0.01f; // XXX: hardcoded - needs_timer = true; - break; - - case GP_SCULPT_TYPE_STRENGTH: - brush_rate = 0.01f; // XXX: hardcoded - needs_timer = true; - break; - - case GP_SCULPT_TYPE_PINCH: - brush_rate = 0.001f; // XXX: hardcoded - needs_timer = true; - break; - - case GP_SCULPT_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 *region = CTX_wm_region(C); - - /* ensure that we'll have a new frame to draw on */ - gpsculpt_brush_init_stroke(C, gso); - - /* apply first dab... */ - gso->is_painting = true; - gpsculpt_brush_apply_event(C, op, event); - - /* redraw view with feedback */ - ED_region_tag_redraw(region); - } - - 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; - bool redraw_toolsettings = 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; - - /* Adjust brush settings */ - /* FIXME: Step increments and modifier keys are hardcoded here! */ - case WHEELUPMOUSE: - case PADPLUSKEY: - if (event->shift) { - /* increase strength */ - gso->gp_brush->strength += 0.05f; - CLAMP_MAX(gso->gp_brush->strength, 1.0f); - } - else { - /* increase brush size */ - gso->gp_brush->size += 3; - CLAMP_MAX(gso->gp_brush->size, 300); - } - - redraw_region = true; - redraw_toolsettings = true; - break; - - case WHEELDOWNMOUSE: - case PADMINUS: - if (event->shift) { - /* decrease strength */ - gso->gp_brush->strength -= 0.05f; - CLAMP_MIN(gso->gp_brush->strength, 0.0f); - } - else { - /* decrease brush size */ - gso->gp_brush->size -= 3; - CLAMP_MIN(gso->gp_brush->size, 1); - } - - redraw_region = true; - redraw_toolsettings = true; - 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(C, 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->gp_brush->strength += 0.05f; - CLAMP_MAX(gso->gp_brush->strength, 1.0f); - } - else { - /* increase brush size */ - gso->gp_brush->size += 3; - CLAMP_MAX(gso->gp_brush->size, 300); - } - - redraw_region = true; - redraw_toolsettings = true; - break; - - case WHEELDOWNMOUSE: - case PADMINUS: - if (event->shift) { - /* decrease strength */ - gso->gp_brush->strength -= 0.05f; - CLAMP_MIN(gso->gp_brush->strength, 0.0f); - } - else { - /* decrease brush size */ - gso->gp_brush->size -= 3; - CLAMP_MIN(gso->gp_brush->size, 1); - } - - redraw_region = true; - redraw_toolsettings = true; - break; - - /* Change Frame - Allowed */ - case LEFTARROWKEY: - case RIGHTARROWKEY: - case UPARROWKEY: - case DOWNARROWKEY: - return OPERATOR_PASS_THROUGH; - - /* Camera/View Gizmo's - Allowed */ - /* (See rationale in gpencil_paint.c -> gpencil_draw_modal()) */ - case PAD0: - case PAD1: - case PAD2: - case PAD3: - case PAD4: - case PAD5: - case PAD6: - case PAD7: - case PAD8: - case PAD9: - return OPERATOR_PASS_THROUGH; - - /* Unhandled event */ - default: - break; - } - } - - /* Redraw region? */ - if (redraw_region) { - ARegion *region = CTX_wm_region(C); - ED_region_tag_redraw(region); - } - - /* Redraw toolsettings (brush settings)? */ - if (redraw_toolsettings) { - DEG_id_tag_update(&gso->gpd->id, ID_RECALC_GEOMETRY); - WM_event_add_notifier(C, NC_SCENE | ND_TOOLSETTINGS, NULL); - } - - return OPERATOR_RUNNING_MODAL; -} - -/* Also used for weight paint. */ -void GPENCIL_OT_sculpt_paint(wmOperatorType *ot) -{ - /* identifiers */ - ot->name = "Stroke Sculpt"; - ot->idname = "GPENCIL_OT_sculpt_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 */ - PropertyRNA *prop; - prop = RNA_def_collection_runtime(ot->srna, "stroke", &RNA_OperatorStrokeElement, "Stroke", ""); - RNA_def_property_flag(prop, PROP_HIDDEN | PROP_SKIP_SAVE); - - 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_HIDDEN | PROP_SKIP_SAVE); -} diff --git a/source/blender/editors/gpencil/gpencil_convert.c b/source/blender/editors/gpencil/gpencil_convert.c index 255f17f13cc..934466475a5 100644 --- a/source/blender/editors/gpencil/gpencil_convert.c +++ b/source/blender/editors/gpencil/gpencil_convert.c @@ -152,7 +152,7 @@ static const EnumPropertyItem *rna_GPConvert_mode_items(bContext *UNUSED(C), * - assumes that the active space is the 3D-View */ static void gp_strokepoint_convertcoords(bContext *C, - bGPdata *gpd, + bGPdata *UNUSED(gpd), bGPDlayer *gpl, bGPDstroke *gps, bGPDspoint *source_pt, @@ -174,7 +174,7 @@ static void gp_strokepoint_convertcoords(bContext *C, /* apply parent transform */ float fpt[3]; - ED_gpencil_parent_location(depsgraph, obact, gpd, gpl, diff_mat); + BKE_gpencil_parent_matrix_get(depsgraph, obact, gpl, diff_mat); mul_v3_m4v3(fpt, diff_mat, &source_pt->x); copy_v3_v3(&pt->x, fpt); @@ -1270,7 +1270,7 @@ static void gp_layer_to_curve(bContext *C, Collection *collection = CTX_data_collection(C); Scene *scene = CTX_data_scene(C); - bGPDframe *gpf = BKE_gpencil_layer_getframe(gpl, CFRA, GP_GETFRAME_USE_PREV); + bGPDframe *gpf = BKE_gpencil_layer_frame_get(gpl, CFRA, GP_GETFRAME_USE_PREV); bGPDstroke *gps, *prev_gps = NULL; Object *ob; Curve *cu; @@ -1410,7 +1410,7 @@ static bool gp_convert_check_has_valid_timing(bContext *C, bGPDlayer *gpl, wmOpe int i; bool valid = true; - if (!gpl || !(gpf = BKE_gpencil_layer_getframe(gpl, CFRA, GP_GETFRAME_USE_PREV)) || + if (!gpl || !(gpf = BKE_gpencil_layer_frame_get(gpl, CFRA, GP_GETFRAME_USE_PREV)) || !(gps = gpf->strokes.first)) { return false; } @@ -1476,8 +1476,8 @@ static bool gp_convert_poll(bContext *C) /* only if the current view is 3D View, if there's valid data (i.e. at least one stroke!), * and if we are not in edit mode! */ - return ((sa && sa->spacetype == SPACE_VIEW3D) && (gpl = BKE_gpencil_layer_getactive(gpd)) && - (gpf = BKE_gpencil_layer_getframe(gpl, CFRA, GP_GETFRAME_USE_PREV)) && + return ((sa && sa->spacetype == SPACE_VIEW3D) && (gpl = BKE_gpencil_layer_active_get(gpd)) && + (gpf = BKE_gpencil_layer_frame_get(gpl, CFRA, GP_GETFRAME_USE_PREV)) && (gpf->strokes.first) && (!GPENCIL_ANY_EDIT_MODE(gpd))); } @@ -1487,7 +1487,7 @@ static int gp_convert_layer_exec(bContext *C, wmOperator *op) Object *ob = CTX_data_active_object(C); bGPdata *gpd = (bGPdata *)ob->data; - bGPDlayer *gpl = BKE_gpencil_layer_getactive(gpd); + bGPDlayer *gpl = BKE_gpencil_layer_active_get(gpd); Scene *scene = CTX_data_scene(C); const int mode = RNA_enum_get(op->ptr, "type"); const bool norm_weights = RNA_boolean_get(op->ptr, "use_normalize_weights"); @@ -1751,4 +1751,92 @@ void GPENCIL_OT_convert(wmOperatorType *ot) RNA_def_property_flag(prop, PROP_SKIP_SAVE); } -/* ************************************************ */ +/* Generate Grease Pencil from Image. */ +static bool image_to_gpencil_poll(bContext *C) +{ + SpaceLink *sl = CTX_wm_space_data(C); + if (sl->spacetype == SPACE_IMAGE) { + return true; + } + + return false; +} + +static int image_to_gpencil_exec(bContext *C, wmOperator *op) +{ + const float size = RNA_float_get(op->ptr, "size"); + const bool is_mask = RNA_boolean_get(op->ptr, "mask"); + + Main *bmain = CTX_data_main(C); + Scene *scene = CTX_data_scene(C); + SpaceImage *sima = CTX_wm_space_image(C); + bool done = false; + + if (sima->image == NULL) { + return OPERATOR_CANCELLED; + } + + /* Create Object. */ + const float *cur = scene->cursor.location; + ushort local_view_bits = 0; + Object *ob = ED_gpencil_add_object(C, cur, local_view_bits); + DEG_relations_tag_update(bmain); /* added object */ + + /* Create material slot. */ + Material *ma = BKE_gpencil_object_material_new(bmain, ob, "Image Material", NULL); + MaterialGPencilStyle *gp_style = ma->gp_style; + gp_style->mode = GP_MATERIAL_MODE_SQUARE; + + /* Add layer and frame. */ + bGPdata *gpd = (bGPdata *)ob->data; + bGPDlayer *gpl = BKE_gpencil_layer_addnew(gpd, "Image Layer", true); + bGPDframe *gpf = BKE_gpencil_frame_addnew(gpl, CFRA); + done = BKE_gpencil_from_image(sima, gpf, size, is_mask); + + if (done) { + /* Delete any selected point. */ + LISTBASE_FOREACH_MUTABLE (bGPDstroke *, gps, &gpf->strokes) { + gp_stroke_delete_tagged_points(gpf, gps, gps->next, GP_SPOINT_SELECT, false, 0); + } + + BKE_reportf(op->reports, RPT_INFO, "Object created"); + } + + return OPERATOR_FINISHED; +} + +void GPENCIL_OT_image_to_grease_pencil(wmOperatorType *ot) +{ + PropertyRNA *prop; + + /* identifiers */ + ot->name = "Generate Grease Pencil Object using image as source"; + ot->idname = "GPENCIL_OT_image_to_grease_pencil"; + ot->description = "Generate a Grease Pencil Object using Image as source"; + + /* api callbacks */ + ot->exec = image_to_gpencil_exec; + ot->poll = image_to_gpencil_poll; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + /* properties */ + ot->prop = RNA_def_float(ot->srna, + "size", + 0.005f, + 0.0001f, + 10.0f, + "Point Size", + "Size used for graese pencil points", + 0.001f, + 1.0f); + RNA_def_property_flag(ot->prop, PROP_SKIP_SAVE); + + prop = RNA_def_boolean(ot->srna, + "mask", + false, + "Generate Mask", + "Create an inverted image for masking using alpha channel"); + RNA_def_property_flag(prop, PROP_SKIP_SAVE); +} diff --git a/source/blender/editors/gpencil/gpencil_data.c b/source/blender/editors/gpencil/gpencil_data.c index a8c3fb02e11..9b33e999a25 100644 --- a/source/blender/editors/gpencil/gpencil_data.c +++ b/source/blender/editors/gpencil/gpencil_data.c @@ -292,7 +292,7 @@ static int gp_layer_remove_exec(bContext *C, wmOperator *op) bGPdata *gpd = (!is_annotation) ? ED_gpencil_data_get_active(C) : ED_annotation_data_get_active(C); - bGPDlayer *gpl = BKE_gpencil_layer_getactive(gpd); + bGPDlayer *gpl = BKE_gpencil_layer_active_get(gpd); /* sanity checks */ if (ELEM(NULL, gpd, gpl)) { @@ -309,15 +309,18 @@ static int gp_layer_remove_exec(bContext *C, wmOperator *op) * - if this is the only layer, this naturally becomes NULL */ if (gpl->prev) { - BKE_gpencil_layer_setactive(gpd, gpl->prev); + BKE_gpencil_layer_active_set(gpd, gpl->prev); } else { - BKE_gpencil_layer_setactive(gpd, gpl->next); + BKE_gpencil_layer_active_set(gpd, gpl->next); } /* delete the layer now... */ BKE_gpencil_layer_delete(gpd, gpl); + /* Reorder masking. */ + BKE_gpencil_layer_mask_sort_all(gpd); + /* notifiers */ DEG_id_tag_update(&gpd->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY); WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL); @@ -342,7 +345,7 @@ void GPENCIL_OT_layer_remove(wmOperatorType *ot) static bool gp_active_layer_annotation_poll(bContext *C) { bGPdata *gpd = ED_annotation_data_get_active(C); - bGPDlayer *gpl = BKE_gpencil_layer_getactive(gpd); + bGPDlayer *gpl = BKE_gpencil_layer_active_get(gpd); return (gpl != NULL); } @@ -373,7 +376,7 @@ static int gp_layer_move_exec(bContext *C, wmOperator *op) bGPdata *gpd = (!is_annotation) ? ED_gpencil_data_get_active(C) : ED_annotation_data_get_active(C); - bGPDlayer *gpl = BKE_gpencil_layer_getactive(gpd); + bGPDlayer *gpl = BKE_gpencil_layer_active_get(gpd); const int direction = RNA_enum_get(op->ptr, "type") * -1; @@ -384,6 +387,9 @@ static int gp_layer_move_exec(bContext *C, wmOperator *op) BLI_assert(ELEM(direction, -1, 0, 1)); /* we use value below */ if (BLI_listbase_link_move(&gpd->layers, gpl, direction)) { + /* Reorder masking. */ + BKE_gpencil_layer_mask_sort_all(gpd); + DEG_id_tag_update(&gpd->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY); WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL); } @@ -441,7 +447,7 @@ void GPENCIL_OT_layer_annotation_move(wmOperatorType *ot) static int gp_layer_copy_exec(bContext *C, wmOperator *UNUSED(op)) { bGPdata *gpd = ED_gpencil_data_get_active(C); - bGPDlayer *gpl = BKE_gpencil_layer_getactive(gpd); + bGPDlayer *gpl = BKE_gpencil_layer_active_get(gpd); bGPDlayer *new_layer; /* sanity checks */ @@ -460,7 +466,7 @@ static int gp_layer_copy_exec(bContext *C, wmOperator *UNUSED(op)) '.', offsetof(bGPDlayer, info), sizeof(new_layer->info)); - BKE_gpencil_layer_setactive(gpd, new_layer); + BKE_gpencil_layer_active_set(gpd, new_layer); /* notifiers */ DEG_id_tag_update(&gpd->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY); @@ -499,7 +505,7 @@ static bool gp_layer_duplicate_object_poll(bContext *C) } bGPdata *gpd = (bGPdata *)ob->data; - bGPDlayer *gpl = BKE_gpencil_layer_getactive(gpd); + bGPDlayer *gpl = BKE_gpencil_layer_active_get(gpd); if (gpl == NULL) { return false; @@ -532,7 +538,7 @@ static int gp_layer_duplicate_object_exec(bContext *C, wmOperator *op) Object *ob_src = CTX_data_active_object(C); bGPdata *gpd_src = (bGPdata *)ob_src->data; - bGPDlayer *gpl_src = BKE_gpencil_layer_getactive(gpd_src); + bGPDlayer *gpl_src = BKE_gpencil_layer_active_get(gpd_src); /* Sanity checks. */ if (ELEM(NULL, gpd_src, gpl_src, ob_dst)) { @@ -568,7 +574,7 @@ static int gp_layer_duplicate_object_exec(bContext *C, wmOperator *op) for (bGPDstroke *gps_src = gpf_src->strokes.first; gps_src; gps_src = gps_src->next) { /* Make copy of source stroke. */ - bGPDstroke *gps_dst = BKE_gpencil_stroke_duplicate(gps_src); + bGPDstroke *gps_dst = BKE_gpencil_stroke_duplicate(gps_src, true); /* Check if material is in destination object, * otherwise add the slot with the material. */ @@ -630,21 +636,21 @@ enum { static int gp_frame_duplicate_exec(bContext *C, wmOperator *op) { bGPdata *gpd = ED_gpencil_data_get_active(C); - bGPDlayer *gpl = BKE_gpencil_layer_getactive(gpd); + bGPDlayer *gpl_active = BKE_gpencil_layer_active_get(gpd); Scene *scene = CTX_data_scene(C); int mode = RNA_enum_get(op->ptr, "mode"); /* sanity checks */ - if (ELEM(NULL, gpd, gpl)) { + if (ELEM(NULL, gpd, gpl_active)) { return OPERATOR_CANCELLED; } if (mode == 0) { - BKE_gpencil_frame_addcopy(gpl, CFRA); + BKE_gpencil_frame_addcopy(gpl_active, CFRA); } else { - for (gpl = gpd->layers.first; gpl; gpl = gpl->next) { + LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd->layers) { if ((gpl->flag & GP_LAYER_LOCKED) == 0) { BKE_gpencil_frame_addcopy(gpl, CFRA); } @@ -700,16 +706,13 @@ static int gp_frame_clean_fill_exec(bContext *C, wmOperator *op) for (bGPDframe *gpf = init_gpf; gpf; gpf = gpf->next) { if ((gpf == gpl->actframe) || (mode == GP_FRAME_CLEAN_FILL_ALL)) { - bGPDstroke *gps, *gpsn; if (gpf == NULL) { continue; } /* simply delete strokes which are no fill */ - for (gps = gpf->strokes.first; gps; gps = gpsn) { - gpsn = gps->next; - + LISTBASE_FOREACH_MUTABLE (bGPDstroke *, gps, &gpf->strokes) { /* skip strokes that are invalid for current view */ if (ED_gpencil_stroke_can_use(C, gps) == false) { continue; @@ -778,8 +781,6 @@ static int gp_frame_clean_loose_exec(bContext *C, wmOperator *op) CTX_DATA_BEGIN (C, bGPDlayer *, gpl, editable_gpencil_layers) { bGPDframe *init_gpf = (is_multiedit) ? gpl->frames.first : gpl->actframe; - bGPDstroke *gps = NULL; - bGPDstroke *gpsn = NULL; for (bGPDframe *gpf = init_gpf; gpf; gpf = gpf->next) { if ((gpf == gpl->actframe) || ((gpf->flag & GP_FRAME_SELECT) && (is_multiedit))) { @@ -788,9 +789,7 @@ static int gp_frame_clean_loose_exec(bContext *C, wmOperator *op) } /* simply delete strokes which are no loose */ - for (gps = gpf->strokes.first; gps; gps = gpsn) { - gpsn = gps->next; - + LISTBASE_FOREACH_MUTABLE (bGPDstroke *, gps, &gpf->strokes) { /* skip strokes that are invalid for current view */ if (ED_gpencil_stroke_can_use(C, gps) == false) { continue; @@ -861,7 +860,7 @@ void GPENCIL_OT_frame_clean_loose(wmOperatorType *ot) static int gp_hide_exec(bContext *C, wmOperator *op) { bGPdata *gpd = ED_gpencil_data_get_active(C); - bGPDlayer *layer = BKE_gpencil_layer_getactive(gpd); + bGPDlayer *layer = BKE_gpencil_layer_active_get(gpd); bool unselected = RNA_boolean_get(op->ptr, "unselected"); /* sanity checks */ @@ -870,10 +869,8 @@ static int gp_hide_exec(bContext *C, wmOperator *op) } if (unselected) { - bGPDlayer *gpl; - /* hide unselected */ - for (gpl = gpd->layers.first; gpl; gpl = gpl->next) { + LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd->layers) { if (gpl != layer) { gpl->flag |= GP_LAYER_HIDE; } @@ -946,7 +943,6 @@ static void gp_reveal_select_frame(bContext *C, bGPDframe *frame, bool select) static int gp_reveal_exec(bContext *C, wmOperator *op) { bGPdata *gpd = ED_gpencil_data_get_active(C); - bGPDlayer *gpl; const bool select = RNA_boolean_get(op->ptr, "select"); /* sanity checks */ @@ -954,8 +950,7 @@ static int gp_reveal_exec(bContext *C, wmOperator *op) return OPERATOR_CANCELLED; } - for (gpl = gpd->layers.first; gpl; gpl = gpl->next) { - + LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd->layers) { if (gpl->flag & GP_LAYER_HIDE) { gpl->flag &= ~GP_LAYER_HIDE; @@ -1008,7 +1003,6 @@ void GPENCIL_OT_reveal(wmOperatorType *ot) 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) { @@ -1016,7 +1010,7 @@ static int gp_lock_all_exec(bContext *C, wmOperator *UNUSED(op)) } /* make all layers non-editable */ - for (gpl = gpd->layers.first; gpl; gpl = gpl->next) { + LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd->layers) { gpl->flag |= GP_LAYER_LOCKED; } @@ -1048,7 +1042,6 @@ void GPENCIL_OT_lock_all(wmOperatorType *ot) 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) { @@ -1056,7 +1049,7 @@ static int gp_unlock_all_exec(bContext *C, wmOperator *UNUSED(op)) } /* make all layers editable again */ - for (gpl = gpd->layers.first; gpl; gpl = gpl->next) { + LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd->layers) { gpl->flag &= ~GP_LAYER_LOCKED; } @@ -1087,8 +1080,7 @@ void GPENCIL_OT_unlock_all(wmOperatorType *ot) static int gp_isolate_layer_exec(bContext *C, wmOperator *op) { bGPdata *gpd = ED_gpencil_data_get_active(C); - bGPDlayer *layer = BKE_gpencil_layer_getactive(gpd); - bGPDlayer *gpl; + bGPDlayer *layer = BKE_gpencil_layer_active_get(gpd); int flags = GP_LAYER_LOCKED; bool isolate = false; @@ -1102,7 +1094,7 @@ static int gp_isolate_layer_exec(bContext *C, wmOperator *op) } /* Test whether to isolate or clear all flags */ - for (gpl = gpd->layers.first; gpl; gpl = gpl->next) { + LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd->layers) { /* Skip if this is the active layer */ if (gpl == layer) { continue; @@ -1121,7 +1113,7 @@ static int gp_isolate_layer_exec(bContext *C, wmOperator *op) /* TODO: Include onion-skinning on this list? */ if (isolate) { /* Set flags on all "other" layers */ - for (gpl = gpd->layers.first; gpl; gpl = gpl->next) { + LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd->layers) { if (gpl == layer) { continue; } @@ -1132,7 +1124,7 @@ static int gp_isolate_layer_exec(bContext *C, wmOperator *op) } else { /* Clear flags - Restore everything else */ - for (gpl = gpd->layers.first; gpl; gpl = gpl->next) { + LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd->layers) { gpl->flag &= ~flags; } } @@ -1172,7 +1164,7 @@ void GPENCIL_OT_layer_isolate(wmOperatorType *ot) static int gp_merge_layer_exec(bContext *C, wmOperator *op) { bGPdata *gpd = ED_gpencil_data_get_active(C); - bGPDlayer *gpl_next = BKE_gpencil_layer_getactive(gpd); + bGPDlayer *gpl_next = BKE_gpencil_layer_active_get(gpd); bGPDlayer *gpl_current = gpl_next->prev; if (ELEM(NULL, gpd, gpl_current, gpl_next)) { @@ -1191,7 +1183,7 @@ static int gp_merge_layer_exec(bContext *C, wmOperator *op) /* try to find frame in current layer */ bGPDframe *frame = BLI_ghash_lookup(gh_frames_cur, POINTER_FROM_INT(gpf->framenum)); if (!frame) { - bGPDframe *actframe = BKE_gpencil_layer_getframe( + bGPDframe *actframe = BKE_gpencil_layer_frame_get( gpl_current, gpf->framenum, GP_GETFRAME_USE_PREV); frame = BKE_gpencil_frame_addnew(gpl_current, gpf->framenum); /* duplicate strokes of current active frame */ @@ -1203,10 +1195,29 @@ static int gp_merge_layer_exec(bContext *C, wmOperator *op) BLI_movelisttolist(&frame->strokes, &gpf->strokes); } + /* Add Masks to destination layer. */ + LISTBASE_FOREACH (bGPDlayer_Mask *, mask, &gpl_next->mask_layers) { + /* Don't add merged layers or missing layer names. */ + if (!BKE_gpencil_layer_named_get(gpd, mask->name) || STREQ(mask->name, gpl_next->info) || + STREQ(mask->name, gpl_current->info)) { + continue; + } + if (!BKE_gpencil_layer_mask_named_get(gpl_current, mask->name)) { + bGPDlayer_Mask *mask_new = MEM_dupallocN(mask); + BLI_addtail(&gpl_current->mask_layers, mask_new); + gpl_current->act_mask++; + } + } + /* Set destination layer as active. */ + BKE_gpencil_layer_active_set(gpd, gpl_current); + /* Now delete next layer */ BKE_gpencil_layer_delete(gpd, gpl_next); BLI_ghash_free(gh_frames_cur, NULL, NULL); + /* Reorder masking. */ + BKE_gpencil_layer_mask_sort(gpd, gpl_current); + /* notifiers */ DEG_id_tag_update(&gpd->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY); WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL); @@ -1268,7 +1279,7 @@ static int gp_layer_change_exec(bContext *C, wmOperator *op) } /* Set active layer */ - BKE_gpencil_layer_setactive(gpd, gpl); + BKE_gpencil_layer_active_set(gpd, gpl); /* updates */ DEG_id_tag_update(&gpd->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY); @@ -1297,6 +1308,49 @@ void GPENCIL_OT_layer_change(wmOperatorType *ot) RNA_def_enum_funcs(ot->prop, ED_gpencil_layers_with_new_enum_itemf); } +static int gp_layer_active_exec(bContext *C, wmOperator *op) +{ + Object *ob = CTX_data_active_object(C); + bGPdata *gpd = (bGPdata *)ob->data; + int layer_num = RNA_int_get(op->ptr, "layer"); + + /* Try to get layer */ + bGPDlayer *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 */ + BKE_gpencil_layer_active_set(gpd, gpl); + + /* updates */ + DEG_id_tag_update(&gpd->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY); + WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL); + + return OPERATOR_FINISHED; +} + +void GPENCIL_OT_layer_active(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Active Layer"; + ot->idname = "GPENCIL_OT_layer_active"; + ot->description = "Active Grease Pencil layer"; + + /* callbacks */ + ot->exec = gp_layer_active_exec; + ot->poll = gp_active_layer_poll; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + /* GPencil layer to use. */ + ot->prop = RNA_def_int(ot->srna, "layer", 0, 0, INT_MAX, "Grease Pencil Layer", "", 0, INT_MAX); + RNA_def_property_flag(ot->prop, PROP_HIDDEN | PROP_SKIP_SAVE); +} /* ************************************************ */ /* ******************* Arrange Stroke Up/Down in drawing order ************************** */ @@ -1312,7 +1366,7 @@ static int gp_stroke_arrange_exec(bContext *C, wmOperator *op) { Object *ob = CTX_data_active_object(C); bGPdata *gpd = ED_gpencil_data_get_active(C); - bGPDlayer *gpl_act = BKE_gpencil_layer_getactive(gpd); + bGPDlayer *gpl_act = BKE_gpencil_layer_active_get(gpd); /* sanity checks */ if (ELEM(NULL, gpd, gpl_act, gpl_act->actframe)) { @@ -1469,7 +1523,7 @@ static int gp_stroke_change_color_exec(bContext *C, wmOperator *op) } } /* try to find slot */ - int idx = BKE_gpencil_object_material_get_index(ob, ma); + int idx = BKE_gpencil_object_material_index_get(ob, ma); if (idx < 0) { return OPERATOR_CANCELLED; } @@ -1494,7 +1548,7 @@ static int gp_stroke_change_color_exec(bContext *C, wmOperator *op) continue; } - for (bGPDstroke *gps = gpf->strokes.first; gps; gps = gps->next) { + LISTBASE_FOREACH (bGPDstroke *, gps, &gpf->strokes) { /* only if selected */ if (gps->flag & GP_STROKE_SELECT) { /* skip strokes that are invalid for current view */ @@ -1562,15 +1616,15 @@ static int gp_stroke_lock_color_exec(bContext *C, wmOperator *UNUSED(op)) for (short i = 0; i < *totcol; i++) { Material *tmp_ma = BKE_object_material_get(ob, i + 1); if (tmp_ma) { - tmp_ma->gp_style->flag |= GP_STYLE_COLOR_LOCKED; + tmp_ma->gp_style->flag |= GP_MATERIAL_LOCKED; DEG_id_tag_update(&tmp_ma->id, ID_RECALC_COPY_ON_WRITE); } } /* loop all selected strokes and unlock any color */ - for (bGPDlayer *gpl = gpd->layers.first; gpl; gpl = gpl->next) { + LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd->layers) { /* only editable and visible layers are considered */ - if (gpencil_layer_is_editable(gpl) && (gpl->actframe != NULL)) { + if (BKE_gpencil_layer_is_editable(gpl) && (gpl->actframe != NULL)) { for (bGPDstroke *gps = gpl->actframe->strokes.last; gps; gps = gps->prev) { /* only if selected */ if (gps->flag & GP_STROKE_SELECT) { @@ -1581,7 +1635,7 @@ static int gp_stroke_lock_color_exec(bContext *C, wmOperator *UNUSED(op)) /* unlock color */ Material *tmp_ma = BKE_object_material_get(ob, gps->mat_nr + 1); if (tmp_ma) { - tmp_ma->gp_style->flag &= ~GP_STYLE_COLOR_LOCKED; + tmp_ma->gp_style->flag &= ~GP_MATERIAL_LOCKED; DEG_id_tag_update(&tmp_ma->id, ID_RECALC_COPY_ON_WRITE); } } @@ -1619,28 +1673,273 @@ void GPENCIL_OT_stroke_lock_color(wmOperatorType *ot) /* ************************************************ */ /* Drawing Brushes Operators */ -/* ******************* Brush create presets ************************** */ -static int gp_brush_presets_create_exec(bContext *C, wmOperator *UNUSED(op)) +/* ******************* Brush resets ************************** */ +static int gp_brush_reset_exec(bContext *C, wmOperator *UNUSED(op)) { Main *bmain = CTX_data_main(C); ToolSettings *ts = CTX_data_tool_settings(C); - BKE_brush_gpencil_presets(bmain, ts); + const enum eContextObjectMode mode = CTX_data_mode_enum(C); + Brush *brush = NULL; + + switch (mode) { + case CTX_MODE_PAINT_GPENCIL: { + Paint *paint = &ts->gp_paint->paint; + brush = paint->brush; + if (brush && brush->gpencil_settings) { + BKE_gpencil_brush_preset_set(bmain, brush, brush->gpencil_settings->preset_type); + } + break; + } + case CTX_MODE_SCULPT_GPENCIL: { + Paint *paint = &ts->gp_sculptpaint->paint; + brush = paint->brush; + if (brush && brush->gpencil_settings) { + BKE_gpencil_brush_preset_set(bmain, brush, brush->gpencil_settings->preset_type); + } + break; + } + case CTX_MODE_WEIGHT_GPENCIL: { + Paint *paint = &ts->gp_weightpaint->paint; + brush = paint->brush; + if (brush && brush->gpencil_settings) { + BKE_gpencil_brush_preset_set(bmain, brush, brush->gpencil_settings->preset_type); + } + break; + } + case CTX_MODE_VERTEX_GPENCIL: { + Paint *paint = &ts->gp_vertexpaint->paint; + brush = paint->brush; + if (brush && brush->gpencil_settings) { + BKE_gpencil_brush_preset_set(bmain, brush, brush->gpencil_settings->preset_type); + } + break; + } + default: + break; + } /* notifiers */ - WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL); + WM_main_add_notifier(NC_BRUSH | NA_EDITED, NULL); + + return OPERATOR_FINISHED; +} + +void GPENCIL_OT_brush_reset(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Reset Brush"; + ot->idname = "GPENCIL_OT_brush_reset"; + ot->description = "Reset Brush to default parameters"; + + /* api callbacks */ + ot->exec = gp_brush_reset_exec; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; +} + +static Brush *gp_brush_get_first_by_mode(Main *bmain, + Paint *UNUSED(paint), + const enum eContextObjectMode mode, + char tool) +{ + Brush *brush_next = NULL; + for (Brush *brush = bmain->brushes.first; brush; brush = brush_next) { + brush_next = brush->id.next; + + if (brush->gpencil_settings == NULL) { + continue; + } + + if ((mode == CTX_MODE_PAINT_GPENCIL) && (brush->gpencil_tool == tool)) { + return brush; + } + + if ((mode == CTX_MODE_SCULPT_GPENCIL) && (brush->gpencil_sculpt_tool == tool)) { + return brush; + } + + if ((mode == CTX_MODE_WEIGHT_GPENCIL) && (brush->gpencil_weight_tool == tool)) { + return brush; + } + + if ((mode == CTX_MODE_VERTEX_GPENCIL) && (brush->gpencil_vertex_tool == tool)) { + return brush; + } + } + + return NULL; +} + +static void gp_brush_delete_mode_brushes(Main *bmain, + Paint *paint, + const enum eContextObjectMode mode) +{ + Brush *brush_active = paint->brush; + Brush *brush_next = NULL; + for (Brush *brush = bmain->brushes.first; brush; brush = brush_next) { + brush_next = brush->id.next; + + if ((brush->gpencil_settings == NULL) && (brush->ob_mode != OB_MODE_PAINT_GPENCIL)) { + continue; + } + + short preset = (brush->gpencil_settings) ? brush->gpencil_settings->preset_type : + GP_BRUSH_PRESET_UNKNOWN; + + if (preset != GP_BRUSH_PRESET_UNKNOWN) { + /* Verify to delete only the brushes of the current mode. */ + if (mode == CTX_MODE_PAINT_GPENCIL) { + if ((preset < GP_BRUSH_PRESET_AIRBRUSH) || (preset > GP_BRUSH_PRESET_TINT)) { + continue; + } + if ((brush_active) && (brush_active->gpencil_tool != brush->gpencil_tool)) { + continue; + } + } + + if (mode == CTX_MODE_SCULPT_GPENCIL) { + if ((preset < GP_BRUSH_PRESET_SMOOTH_STROKE) || (preset > GP_BRUSH_PRESET_CLONE_STROKE)) { + continue; + } + if ((brush_active) && (brush_active->gpencil_sculpt_tool != brush->gpencil_sculpt_tool)) { + continue; + } + } + + if (mode == CTX_MODE_WEIGHT_GPENCIL) { + if (preset != GP_BRUSH_PRESET_DRAW_WEIGHT) { + continue; + } + if ((brush_active) && (brush_active->gpencil_weight_tool != brush->gpencil_weight_tool)) { + continue; + } + } + + if (mode == CTX_MODE_VERTEX_GPENCIL) { + if ((preset < GP_BRUSH_PRESET_VERTEX_DRAW) || (preset > GP_BRUSH_PRESET_VERTEX_REPLACE)) { + continue; + } + if ((brush_active) && (brush_active->gpencil_vertex_tool != brush->gpencil_vertex_tool)) { + continue; + } + } + } + + /* Before delete, unpinn any material of the brush. */ + if ((brush->gpencil_settings) && (brush->gpencil_settings->material != NULL)) { + brush->gpencil_settings->material = NULL; + brush->gpencil_settings->flag &= ~GP_BRUSH_MATERIAL_PINNED; + } + + BKE_brush_delete(bmain, brush); + } +} + +static int gp_brush_reset_all_exec(bContext *C, wmOperator *UNUSED(op)) +{ + Main *bmain = CTX_data_main(C); + ToolSettings *ts = CTX_data_tool_settings(C); + const enum eContextObjectMode mode = CTX_data_mode_enum(C); + Paint *paint = NULL; + + switch (mode) { + case CTX_MODE_PAINT_GPENCIL: { + paint = &ts->gp_paint->paint; + break; + } + case CTX_MODE_SCULPT_GPENCIL: { + paint = &ts->gp_sculptpaint->paint; + break; + } + case CTX_MODE_WEIGHT_GPENCIL: { + paint = &ts->gp_weightpaint->paint; + break; + } + case CTX_MODE_VERTEX_GPENCIL: { + paint = &ts->gp_vertexpaint->paint; + break; + } + default: + break; + } + + char tool = '0'; + if (paint) { + Brush *brush_active = paint->brush; + if (brush_active) { + switch (mode) { + case CTX_MODE_PAINT_GPENCIL: { + tool = brush_active->gpencil_tool; + break; + } + case CTX_MODE_SCULPT_GPENCIL: { + tool = brush_active->gpencil_sculpt_tool; + break; + } + case CTX_MODE_WEIGHT_GPENCIL: { + tool = brush_active->gpencil_weight_tool; + break; + } + case CTX_MODE_VERTEX_GPENCIL: { + tool = brush_active->gpencil_vertex_tool; + break; + } + default: { + tool = brush_active->gpencil_tool; + break; + } + } + } + + gp_brush_delete_mode_brushes(bmain, paint, mode); + + switch (mode) { + case CTX_MODE_PAINT_GPENCIL: { + BKE_brush_gpencil_paint_presets(bmain, ts); + break; + } + case CTX_MODE_SCULPT_GPENCIL: { + BKE_brush_gpencil_sculpt_presets(bmain, ts); + break; + } + case CTX_MODE_WEIGHT_GPENCIL: { + BKE_brush_gpencil_weight_presets(bmain, ts); + break; + } + case CTX_MODE_VERTEX_GPENCIL: { + BKE_brush_gpencil_vertex_presets(bmain, ts); + break; + } + default: { + break; + } + } + + BKE_paint_toolslots_brush_validate(bmain, paint); + + /* Set Again the first brush of the mode. */ + Brush *deft_brush = gp_brush_get_first_by_mode(bmain, paint, mode, tool); + if (deft_brush) { + BKE_paint_brush_set(paint, deft_brush); + } + /* notifiers */ + DEG_relations_tag_update(bmain); + WM_main_add_notifier(NC_BRUSH | NA_EDITED, NULL); + } return OPERATOR_FINISHED; } -void GPENCIL_OT_brush_presets_create(wmOperatorType *ot) +void GPENCIL_OT_brush_reset_all(wmOperatorType *ot) { /* identifiers */ - ot->name = "Create Preset Brushes"; - ot->idname = "GPENCIL_OT_brush_presets_create"; - ot->description = "Create a set of predefined Grease Pencil drawing brushes"; + ot->name = "Reset All Brushes"; + ot->idname = "GPENCIL_OT_brush_reset_all"; + ot->description = "Delete all mode brushes and recreate a default set"; /* api callbacks */ - ot->exec = gp_brush_presets_create_exec; + ot->exec = gp_brush_reset_all_exec; /* flags */ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; @@ -2323,9 +2622,9 @@ int ED_gpencil_join_objects_exec(bContext *C, wmOperator *op) BKE_object_defgroup_unique_name(vgroup, ob_active); BLI_addtail(&ob_active->defbase, vgroup); /* update vertex groups in strokes in original data */ - for (bGPDlayer *gpl_src = gpd->layers.first; gpl_src; gpl_src = gpl_src->next) { - for (bGPDframe *gpf = gpl_src->frames.first; gpf; gpf = gpf->next) { - for (bGPDstroke *gps = gpf->strokes.first; gps; gps = gps->next) { + LISTBASE_FOREACH (bGPDlayer *, gpl_src, &gpd->layers) { + LISTBASE_FOREACH (bGPDframe *, gpf, &gpl_src->frames) { + LISTBASE_FOREACH (bGPDstroke *, gps, &gpf->strokes) { MDeformVert *dvert; int i; for (i = 0, dvert = gps->dvert; i < gps->totpoints; i++, dvert++) { @@ -2372,12 +2671,12 @@ int ED_gpencil_join_objects_exec(bContext *C, wmOperator *op) float inverse_diff_mat[4][4]; /* recalculate all stroke points */ - ED_gpencil_parent_location(depsgraph, ob_iter, gpd_src, gpl_src, diff_mat); + BKE_gpencil_parent_matrix_get(depsgraph, ob_iter, gpl_src, diff_mat); invert_m4_m4(inverse_diff_mat, diff_mat); Material *ma_src = NULL; for (bGPDframe *gpf = gpl_new->frames.first; gpf; gpf = gpf->next) { - for (bGPDstroke *gps = gpf->strokes.first; gps; gps = gps->next) { + LISTBASE_FOREACH (bGPDstroke *, gps, &gpf->strokes) { /* Reassign material. Look old material and try to find in destination. */ ma_src = BKE_gpencil_material(ob_src, gps->mat_nr + 1); @@ -2468,7 +2767,8 @@ static bool gpencil_active_color_poll(bContext *C) return false; } -/* **************** Lock and hide any color non used in current layer ************************** */ +/* **************** Lock and hide any color non used in current layer ************************** + */ static int gpencil_lock_layer_exec(bContext *C, wmOperator *UNUSED(op)) { bGPdata *gpd = ED_gpencil_data_get_active(C); @@ -2491,16 +2791,16 @@ static int gpencil_lock_layer_exec(bContext *C, wmOperator *UNUSED(op)) ma = BKE_gpencil_material(ob, i + 1); if (ma) { gp_style = ma->gp_style; - gp_style->flag |= GP_STYLE_COLOR_LOCKED; - gp_style->flag |= GP_STYLE_COLOR_HIDE; + gp_style->flag |= GP_MATERIAL_LOCKED; + gp_style->flag |= GP_MATERIAL_HIDE; DEG_id_tag_update(&ma->id, ID_RECALC_COPY_ON_WRITE); } } /* loop all selected strokes and unlock any color used in active layer */ - for (bGPDlayer *gpl = gpd->layers.first; gpl; gpl = gpl->next) { + LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd->layers) { /* only editable and visible layers are considered */ - if (gpencil_layer_is_editable(gpl) && (gpl->actframe != NULL) && + if (BKE_gpencil_layer_is_editable(gpl) && (gpl->actframe != NULL) && (gpl->flag & GP_LAYER_ACTIVE)) { for (bGPDstroke *gps = gpl->actframe->strokes.last; gps; gps = gps->prev) { /* skip strokes that are invalid for current view */ @@ -2514,8 +2814,8 @@ static int gpencil_lock_layer_exec(bContext *C, wmOperator *UNUSED(op)) gp_style = ma->gp_style; /* unlock/unhide color if not unlocked before */ if (gp_style != NULL) { - gp_style->flag &= ~GP_STYLE_COLOR_LOCKED; - gp_style->flag &= ~GP_STYLE_COLOR_HIDE; + gp_style->flag &= ~GP_MATERIAL_LOCKED; + gp_style->flag &= ~GP_MATERIAL_HIDE; } } } @@ -2555,11 +2855,11 @@ static int gpencil_color_isolate_exec(bContext *C, wmOperator *op) MaterialGPencilStyle *active_color = BKE_gpencil_material_settings(ob, ob->actcol); MaterialGPencilStyle *gp_style; - int flags = GP_STYLE_COLOR_LOCKED; + int flags = GP_MATERIAL_LOCKED; bool isolate = false; if (RNA_boolean_get(op->ptr, "affect_visibility")) { - flags |= GP_STYLE_COLOR_HIDE; + flags |= GP_MATERIAL_HIDE; } if (ELEM(NULL, gpd, active_color)) { @@ -2677,7 +2977,7 @@ static int gpencil_color_hide_exec(bContext *C, wmOperator *op) if (ma) { color = ma->gp_style; if (active_color != color) { - color->flag |= GP_STYLE_COLOR_HIDE; + color->flag |= GP_MATERIAL_HIDE; DEG_id_tag_update(&ma->id, ID_RECALC_COPY_ON_WRITE); } } @@ -2685,7 +2985,7 @@ static int gpencil_color_hide_exec(bContext *C, wmOperator *op) } else { /* hide selected/active */ - active_color->flag |= GP_STYLE_COLOR_HIDE; + active_color->flag |= GP_MATERIAL_HIDE; } /* updates */ @@ -2739,7 +3039,7 @@ static int gpencil_color_reveal_exec(bContext *C, wmOperator *UNUSED(op)) ma = BKE_gpencil_material(ob, i + 1); if (ma) { gp_style = ma->gp_style; - gp_style->flag &= ~GP_STYLE_COLOR_HIDE; + gp_style->flag &= ~GP_MATERIAL_HIDE; DEG_id_tag_update(&ma->id, ID_RECALC_COPY_ON_WRITE); } } @@ -2792,7 +3092,7 @@ static int gpencil_color_lock_all_exec(bContext *C, wmOperator *UNUSED(op)) ma = BKE_gpencil_material(ob, i + 1); if (ma) { gp_style = ma->gp_style; - gp_style->flag |= GP_STYLE_COLOR_LOCKED; + gp_style->flag |= GP_MATERIAL_LOCKED; DEG_id_tag_update(&ma->id, ID_RECALC_COPY_ON_WRITE); } } @@ -2845,7 +3145,7 @@ static int gpencil_color_unlock_all_exec(bContext *C, wmOperator *UNUSED(op)) ma = BKE_gpencil_material(ob, i + 1); if (ma) { gp_style = ma->gp_style; - gp_style->flag &= ~GP_STYLE_COLOR_LOCKED; + gp_style->flag &= ~GP_MATERIAL_LOCKED; DEG_id_tag_update(&ma->id, ID_RECALC_COPY_ON_WRITE); } } @@ -2900,7 +3200,7 @@ static int gpencil_color_select_exec(bContext *C, wmOperator *op) if ((gpf == gpl->actframe) || ((gpf->flag & GP_FRAME_SELECT) && (is_multiedit))) { /* verify something to do */ - for (bGPDstroke *gps = gpf->strokes.first; gps; gps = gps->next) { + LISTBASE_FOREACH (bGPDstroke *, gps, &gpf->strokes) { /* skip strokes that are invalid for current view */ if (ED_gpencil_stroke_can_use(C, gps) == false) { continue; @@ -3057,3 +3357,118 @@ bool ED_gpencil_add_lattice_modifier(const bContext *C, return true; } + +/* Masking operators */ +static int gp_layer_mask_add_exec(bContext *C, wmOperator *op) +{ + Object *ob = CTX_data_active_object(C); + if ((ob == NULL) || (ob->type != OB_GPENCIL)) { + return OPERATOR_CANCELLED; + } + + bGPdata *gpd = (bGPdata *)ob->data; + bGPDlayer *gpl_active = BKE_gpencil_layer_active_get(gpd); + if (gpl_active == NULL) { + return OPERATOR_CANCELLED; + } + char name[128]; + RNA_string_get(op->ptr, "name", name); + bGPDlayer *gpl = BKE_gpencil_layer_named_get(gpd, name); + + if (gpl == NULL) { + BKE_report(op->reports, RPT_ERROR, "Unable to find layer to add"); + return OPERATOR_CANCELLED; + } + + if (gpl == gpl_active) { + BKE_report(op->reports, RPT_ERROR, "Cannot add active layer as mask"); + return OPERATOR_CANCELLED; + } + + if (BKE_gpencil_layer_mask_named_get(gpl_active, name)) { + BKE_report(op->reports, RPT_ERROR, "Layer already added"); + return OPERATOR_CANCELLED; + } + + if (gpl_active->act_mask == 256) { + BKE_report(op->reports, RPT_ERROR, "Maximum number of masking layers reached"); + return OPERATOR_CANCELLED; + } + + BKE_gpencil_layer_mask_add(gpl_active, name); + + /* Reorder masking. */ + BKE_gpencil_layer_mask_sort(gpd, gpl_active); + + /* notifiers */ + if (gpd) { + DEG_id_tag_update(&gpd->id, + ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY | ID_RECALC_COPY_ON_WRITE); + } + WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL); + + return OPERATOR_FINISHED; +} + +void GPENCIL_OT_layer_mask_add(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Add New Mask Layer"; + ot->idname = "GPENCIL_OT_layer_mask_add"; + ot->description = "Add new layer as masking"; + + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + /* callbacks */ + ot->exec = gp_layer_mask_add_exec; + ot->poll = gp_add_poll; + + /* properties */ + RNA_def_string(ot->srna, "name", NULL, 128, "Layer", "Name of the layer"); +} + +static int gp_layer_mask_remove_exec(bContext *C, wmOperator *UNUSED(op)) +{ + Object *ob = CTX_data_active_object(C); + if ((ob == NULL) || (ob->type != OB_GPENCIL)) { + return OPERATOR_CANCELLED; + } + + bGPdata *gpd = (bGPdata *)ob->data; + bGPDlayer *gpl = BKE_gpencil_layer_active_get(gpd); + if (gpl == NULL) { + return OPERATOR_CANCELLED; + } + if (gpl->act_mask > 0) { + bGPDlayer_Mask *mask = BLI_findlink(&gpl->mask_layers, gpl->act_mask - 1); + if (mask != NULL) { + BKE_gpencil_layer_mask_remove(gpl, mask); + if ((gpl->mask_layers.first != NULL) && (gpl->act_mask == 0)) { + gpl->act_mask = 1; + } + } + } + + /* Reorder masking. */ + BKE_gpencil_layer_mask_sort(gpd, gpl); + + /* notifiers */ + DEG_id_tag_update(&gpd->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY); + WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL); + + return OPERATOR_FINISHED; +} + +void GPENCIL_OT_layer_mask_remove(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Remove Mask Layer"; + ot->idname = "GPENCIL_OT_layer_mask_remove"; + ot->description = "Remove Layer Mask"; + + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + /* callbacks */ + ot->exec = gp_layer_mask_remove_exec; + ot->poll = gp_active_layer_poll; +} diff --git a/source/blender/editors/gpencil/gpencil_edit.c b/source/blender/editors/gpencil/gpencil_edit.c index e5caeb93c73..f8ad34e8d14 100644 --- a/source/blender/editors/gpencil/gpencil_edit.c +++ b/source/blender/editors/gpencil/gpencil_edit.c @@ -355,7 +355,7 @@ static int gpencil_paintmode_toggle_exec(bContext *C, wmOperator *op) Paint *paint = &ts->gp_paint->paint; /* if not exist, create a new one */ if ((paint->brush == NULL) || (paint->brush->gpencil_settings == NULL)) { - BKE_brush_gpencil_presets(bmain, ts); + BKE_brush_gpencil_paint_presets(bmain, ts); } BKE_paint_toolslots_brush_validate(bmain, &ts->gp_paint->paint); } @@ -414,6 +414,9 @@ static bool gpencil_sculptmode_toggle_poll(bContext *C) static int gpencil_sculptmode_toggle_exec(bContext *C, wmOperator *op) { + Main *bmain = CTX_data_main(C); + ToolSettings *ts = CTX_data_tool_settings(C); + const bool back = RNA_boolean_get(op->ptr, "back"); struct wmMsgBus *mbus = CTX_wm_message_bus(C); @@ -450,6 +453,12 @@ static int gpencil_sculptmode_toggle_exec(bContext *C, wmOperator *op) ob->mode = mode; } + if (mode == OB_MODE_SCULPT_GPENCIL) { + /* be sure we have brushes */ + BKE_paint_ensure(ts, (Paint **)&ts->gp_sculptpaint); + BKE_paint_toolslots_brush_validate(bmain, &ts->gp_sculptpaint->paint); + } + /* setup other modes */ ED_gpencil_setup_modes(C, gpd, mode); /* set cache as dirty */ @@ -504,6 +513,9 @@ static bool gpencil_weightmode_toggle_poll(bContext *C) static int gpencil_weightmode_toggle_exec(bContext *C, wmOperator *op) { + Main *bmain = CTX_data_main(C); + ToolSettings *ts = CTX_data_tool_settings(C); + const bool back = RNA_boolean_get(op->ptr, "back"); struct wmMsgBus *mbus = CTX_wm_message_bus(C); @@ -540,6 +552,12 @@ static int gpencil_weightmode_toggle_exec(bContext *C, wmOperator *op) ob->mode = mode; } + if (mode == OB_MODE_WEIGHT_GPENCIL) { + /* be sure we have brushes */ + BKE_paint_ensure(ts, (Paint **)&ts->gp_weightpaint); + BKE_paint_toolslots_brush_validate(bmain, &ts->gp_weightpaint->paint); + } + /* setup other modes */ ED_gpencil_setup_modes(C, gpd, mode); /* set cache as dirty */ @@ -580,6 +598,104 @@ void GPENCIL_OT_weightmode_toggle(wmOperatorType *ot) RNA_def_property_flag(prop, PROP_HIDDEN | PROP_SKIP_SAVE); } +/* Vertex Paint Mode Management */ + +static bool gpencil_vertexmode_toggle_poll(bContext *C) +{ + /* if using gpencil object, use this gpd */ + Object *ob = CTX_data_active_object(C); + if ((ob) && (ob->type == OB_GPENCIL)) { + return ob->data != NULL; + } + return ED_gpencil_data_get_active(C) != NULL; +} +static int gpencil_vertexmode_toggle_exec(bContext *C, wmOperator *op) +{ + const bool back = RNA_boolean_get(op->ptr, "back"); + + struct wmMsgBus *mbus = CTX_wm_message_bus(C); + Main *bmain = CTX_data_main(C); + bGPdata *gpd = ED_gpencil_data_get_active(C); + ToolSettings *ts = CTX_data_tool_settings(C); + + bool is_object = false; + short mode; + /* if using a gpencil object, use this datablock */ + Object *ob = CTX_data_active_object(C); + if ((ob) && (ob->type == OB_GPENCIL)) { + gpd = ob->data; + is_object = true; + } + + if (gpd == NULL) { + return OPERATOR_CANCELLED; + } + + /* Just toggle paintmode flag... */ + gpd->flag ^= GP_DATA_STROKE_VERTEXMODE; + /* set mode */ + if (gpd->flag & GP_DATA_STROKE_VERTEXMODE) { + mode = OB_MODE_VERTEX_GPENCIL; + } + else { + mode = OB_MODE_OBJECT; + } + + if (is_object) { + /* try to back previous mode */ + if ((ob->restore_mode) && ((gpd->flag & GP_DATA_STROKE_VERTEXMODE) == 0) && (back == 1)) { + mode = ob->restore_mode; + } + ob->restore_mode = ob->mode; + ob->mode = mode; + } + + if (mode == OB_MODE_VERTEX_GPENCIL) { + /* be sure we have brushes */ + BKE_paint_ensure(ts, (Paint **)&ts->gp_vertexpaint); + BKE_paint_toolslots_brush_validate(bmain, &ts->gp_vertexpaint->paint); + } + + /* setup other modes */ + ED_gpencil_setup_modes(C, gpd, mode); + /* set cache as dirty */ + DEG_id_tag_update(&gpd->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY); + + WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | ND_GPENCIL_EDITMODE, NULL); + WM_event_add_notifier(C, NC_SCENE | ND_MODE, NULL); + + if (is_object) { + WM_msg_publish_rna_prop(mbus, &ob->id, ob, Object, mode); + } + if (G.background == false) { + WM_toolsystem_update_from_context_view3d(C); + } + + return OPERATOR_FINISHED; +} + +void GPENCIL_OT_vertexmode_toggle(wmOperatorType *ot) +{ + PropertyRNA *prop; + + /* identifiers */ + ot->name = "Strokes Vertex Mode Toggle"; + ot->idname = "GPENCIL_OT_vertexmode_toggle"; + ot->description = "Enter/Exit vertex paint mode for Grease Pencil strokes"; + + /* callbacks */ + ot->exec = gpencil_vertexmode_toggle_exec; + ot->poll = gpencil_vertexmode_toggle_poll; + + /* flags */ + ot->flag = OPTYPE_UNDO | OPTYPE_REGISTER; + + /* properties */ + prop = RNA_def_boolean( + ot->srna, "back", 0, "Return to Previous Mode", "Return to previous mode"); + RNA_def_property_flag(prop, PROP_HIDDEN | PROP_SKIP_SAVE); +} + /* ************************************************ */ /* Stroke Editing Operators */ @@ -659,23 +775,17 @@ static void gp_duplicate_points(const bGPDstroke *gps, else if (i == gps->totpoints - 1) { len = i - start_idx + 1; } - // printf("copying from %d to %d = %d\n", start_idx, i, len); /* make copies of the relevant data */ if (len) { bGPDstroke *gpsd; /* make a stupid copy first of the entire stroke (to get the flags too) */ - gpsd = MEM_dupallocN(gps); + gpsd = BKE_gpencil_stroke_duplicate((bGPDstroke *)gps, false); /* saves original layer name */ BLI_strncpy(gpsd->runtime.tmp_layerinfo, layername, sizeof(gpsd->runtime.tmp_layerinfo)); - /* initialize triangle memory - will be calculated on next redraw */ - gpsd->triangles = NULL; - gpsd->flag |= GP_STROKE_RECALC_GEOMETRY; - gpsd->tot_triangles = 0; - /* now, make a new points array, and copy of the relevant parts */ gpsd->points = MEM_mallocN(sizeof(bGPDspoint) * len, "gps stroke points copy"); memcpy(gpsd->points, gps->points + start_idx, sizeof(bGPDspoint) * len); @@ -695,8 +805,11 @@ static void gp_duplicate_points(const bGPDstroke *gps, } } + BKE_gpencil_stroke_geometry_update(gpsd); + /* add to temp buffer */ gpsd->next = gpsd->prev = NULL; + BLI_addtail(new_strokes, gpsd); /* cleanup + reset for next */ @@ -746,17 +859,12 @@ static int gp_duplicate_exec(bContext *C, wmOperator *op) bGPDstroke *gpsd; /* make direct copies of the stroke and its points */ - gpsd = MEM_dupallocN(gps); + gpsd = BKE_gpencil_stroke_duplicate(gps, true); + BLI_strncpy(gpsd->runtime.tmp_layerinfo, gpl->info, sizeof(gpsd->runtime.tmp_layerinfo)); - gpsd->points = MEM_dupallocN(gps->points); - if (gps->dvert != NULL) { - gpsd->dvert = MEM_dupallocN(gps->dvert); - BKE_gpencil_stroke_weights_duplicate(gps, gpsd); - } - /* triangle information - will be calculated on next redraw */ - gpsd->flag |= GP_STROKE_RECALC_GEOMETRY; - gpsd->triangles = NULL; + /* Initialize triangle information. */ + BKE_gpencil_stroke_geometry_update(gpsd); /* add to temp buffer */ gpsd->next = gpsd->prev = NULL; @@ -827,6 +935,7 @@ static void copy_move_point(bGPDstroke *gps, pt_final->flag = pt->flag; pt_final->uv_fac = pt->uv_fac; pt_final->uv_rot = pt->uv_rot; + copy_v4_v4(pt_final->vert_color, pt->vert_color); if (gps->dvert != NULL) { MDeformVert *dvert = &temp_dverts[from_idx]; @@ -864,7 +973,7 @@ static void gpencil_add_move_points(bGPDframe *gpf, bGPDstroke *gps) pt = &gps->points[i]; if (pt->flag == GP_SPOINT_SELECT) { /* duplicate original stroke data */ - bGPDstroke *gps_new = MEM_dupallocN(gps); + bGPDstroke *gps_new = BKE_gpencil_stroke_duplicate(gps, false); gps_new->prev = gps_new->next = NULL; /* add new points array */ @@ -876,14 +985,15 @@ static void gpencil_add_move_points(bGPDframe *gpf, bGPDstroke *gps) gps_new->dvert = MEM_callocN(sizeof(MDeformVert), __func__); } - gps->flag |= GP_STROKE_RECALC_GEOMETRY; - gps_new->triangles = NULL; - gps_new->tot_triangles = 0; BLI_insertlinkafter(&gpf->strokes, gps, gps_new); /* copy selected point data to new stroke */ copy_move_point(gps_new, gps->points, gps->dvert, i, 0, true); + /* Calc geometry data. */ + BKE_gpencil_stroke_geometry_update(gps); + BKE_gpencil_stroke_geometry_update(gps_new); + /* deselect orinal point */ pt->flag &= ~GP_SPOINT_SELECT; } @@ -926,7 +1036,6 @@ static void gpencil_add_move_points(bGPDframe *gpf, bGPDstroke *gps) copy_move_point(gps, temp_points, temp_dverts, i, i2, false); i2++; } - gps->flag |= GP_STROKE_RECALC_GEOMETRY; /* If first point, add new point at the beginning. */ if (do_first) { @@ -951,6 +1060,9 @@ static void gpencil_add_move_points(bGPDframe *gpf, bGPDstroke *gps) pt->flag |= GP_SPOINT_SELECT; } + /* Calc geometry data. */ + BKE_gpencil_stroke_geometry_update(gps); + MEM_SAFE_FREE(temp_points); MEM_SAFE_FREE(temp_dverts); } @@ -1190,7 +1302,8 @@ static int gp_strokes_copy_exec(bContext *C, wmOperator *op) bGPDstroke *gpsd; /* make direct copies of the stroke and its points */ - gpsd = MEM_dupallocN(gps); + gpsd = BKE_gpencil_stroke_duplicate(gps, false); + /* saves original layer name */ BLI_strncpy(gpsd->runtime.tmp_layerinfo, gpl->info, sizeof(gpsd->runtime.tmp_layerinfo)); gpsd->points = MEM_dupallocN(gps->points); @@ -1199,10 +1312,8 @@ static int gp_strokes_copy_exec(bContext *C, wmOperator *op) BKE_gpencil_stroke_weights_duplicate(gps, gpsd); } - /* triangles cache - will be recalculated on next redraw */ - gpsd->flag |= GP_STROKE_RECALC_GEOMETRY; - gpsd->tot_triangles = 0; - gpsd->triangles = NULL; + /* Calc geometry data. */ + BKE_gpencil_stroke_geometry_update(gpsd); /* add to temp buffer */ gpsd->next = gpsd->prev = NULL; @@ -1267,6 +1378,9 @@ void GPENCIL_OT_copy(wmOperatorType *ot) static bool gp_strokes_paste_poll(bContext *C) { + if (CTX_wm_area(C)->spacetype != SPACE_VIEW3D) { + return false; + } /* 1) Must have GP datablock to paste to * - We don't need to have an active layer though, as that can easily get added * - If the active layer is locked, we can't paste there, @@ -1285,20 +1399,17 @@ typedef enum eGP_PasteMode { static int gp_strokes_paste_exec(bContext *C, wmOperator *op) { Object *ob = CTX_data_active_object(C); - bGPdata *gpd = ED_gpencil_data_get_active(C); - bGPDlayer *gpl = CTX_data_active_gpencil_layer(C); /* only use active for copy merge */ + bGPdata *gpd = (bGPdata *)ob->data; + bGPDlayer *gpl = BKE_gpencil_layer_active_get(gpd); /* only use active for copy merge */ Scene *scene = CTX_data_scene(C); bGPDframe *gpf; eGP_PasteMode type = RNA_enum_get(op->ptr, "type"); + const bool on_back = RNA_boolean_get(op->ptr, "paste_back"); GHash *new_colors; - /* check for various error conditions */ - if (gpd == NULL) { - BKE_report(op->reports, RPT_ERROR, "No Grease Pencil data"); - return OPERATOR_CANCELLED; - } - else if (GPENCIL_MULTIEDIT_SESSIONS_ON(gpd)) { + /* Check for various error conditions. */ + if (GPENCIL_MULTIEDIT_SESSIONS_ON(gpd)) { BKE_report(op->reports, RPT_ERROR, "Operator not supported in multiframe edition"); return OPERATOR_CANCELLED; } @@ -1312,7 +1423,7 @@ static int gp_strokes_paste_exec(bContext *C, wmOperator *op) /* no active layer - let's just create one */ gpl = BKE_gpencil_layer_addnew(gpd, DATA_("GP_Layer"), true); } - else if ((gpencil_layer_is_editable(gpl) == false) && (type == GP_COPY_TO_ACTIVE)) { + else if ((BKE_gpencil_layer_is_editable(gpl) == false) && (type == GP_COPY_TO_ACTIVE)) { BKE_report( op->reports, RPT_ERROR, "Can not paste strokes when active layer is hidden or locked"); return OPERATOR_CANCELLED; @@ -1330,17 +1441,6 @@ static int gp_strokes_paste_exec(bContext *C, wmOperator *op) } if (ok == false) { - /* XXX: this check is not 100% accurate - * (i.e. image editor is incompatible with normal 2D strokes), - * but should be enough to give users a good idea of what's going on. - */ - if (CTX_wm_area(C)->spacetype == SPACE_VIEW3D) { - BKE_report(op->reports, RPT_ERROR, "Cannot paste 2D strokes in 3D View"); - } - else { - BKE_report(op->reports, RPT_ERROR, "Cannot paste 3D strokes in 2D editors"); - } - return OPERATOR_CANCELLED; } } @@ -1362,14 +1462,15 @@ static int gp_strokes_paste_exec(bContext *C, wmOperator *op) new_colors = gp_copybuf_validate_colormap(C); /* Copy over the strokes from the buffer (and adjust the colors) */ - for (bGPDstroke *gps = gp_strokes_copypastebuf.first; gps; gps = gps->next) { + bGPDstroke *gps_init = (!on_back) ? gp_strokes_copypastebuf.first : gp_strokes_copypastebuf.last; + for (bGPDstroke *gps = gps_init; gps; gps = (!on_back) ? gps->next : gps->prev) { if (ED_gpencil_stroke_can_use(C, gps)) { /* Need to verify if layer exists */ if (type != GP_COPY_TO_ACTIVE) { gpl = BLI_findstring(&gpd->layers, gps->runtime.tmp_layerinfo, offsetof(bGPDlayer, info)); if (gpl == NULL) { /* no layer - use active (only if layer deleted before paste) */ - gpl = CTX_data_active_gpencil_layer(C); + gpl = BKE_gpencil_layer_active_get(gpd); } } @@ -1378,26 +1479,26 @@ static int gp_strokes_paste_exec(bContext *C, wmOperator *op) * we are obliged to add a new frame if one * doesn't exist already */ - gpf = BKE_gpencil_layer_getframe(gpl, CFRA, GP_GETFRAME_ADD_NEW); + gpf = BKE_gpencil_layer_frame_get(gpl, CFRA, GP_GETFRAME_ADD_NEW); if (gpf) { /* Create new stroke */ - bGPDstroke *new_stroke = MEM_dupallocN(gps); + bGPDstroke *new_stroke = BKE_gpencil_stroke_duplicate(gps, true); new_stroke->runtime.tmp_layerinfo[0] = '\0'; + new_stroke->next = new_stroke->prev = NULL; - new_stroke->points = MEM_dupallocN(gps->points); - if (gps->dvert != NULL) { - new_stroke->dvert = MEM_dupallocN(gps->dvert); - BKE_gpencil_stroke_weights_duplicate(gps, new_stroke); - } - new_stroke->flag |= GP_STROKE_RECALC_GEOMETRY; - new_stroke->triangles = NULL; + /* Calc geometry data. */ + BKE_gpencil_stroke_geometry_update(new_stroke); - new_stroke->next = new_stroke->prev = NULL; - BLI_addtail(&gpf->strokes, new_stroke); + if (on_back) { + BLI_addhead(&gpf->strokes, new_stroke); + } + else { + BLI_addtail(&gpf->strokes, new_stroke); + } /* Remap material */ Material *ma = BLI_ghash_lookup(new_colors, POINTER_FROM_INT(new_stroke->mat_nr)); - new_stroke->mat_nr = BKE_gpencil_object_material_get_index(ob, ma); + new_stroke->mat_nr = BKE_gpencil_object_material_index_get(ob, ma); CLAMP_MIN(new_stroke->mat_nr, 0); } } @@ -1415,6 +1516,8 @@ static int gp_strokes_paste_exec(bContext *C, wmOperator *op) void GPENCIL_OT_paste(wmOperatorType *ot) { + PropertyRNA *prop; + static const EnumPropertyItem copy_type[] = { {GP_COPY_TO_ACTIVE, "ACTIVE", 0, "Paste to Active", ""}, {GP_COPY_BY_LAYER, "LAYER", 0, "Paste by Layer", ""}, @@ -1435,6 +1538,10 @@ void GPENCIL_OT_paste(wmOperatorType *ot) /* properties */ ot->prop = RNA_def_enum(ot->srna, "type", copy_type, GP_COPY_TO_ACTIVE, "Type", ""); + + prop = RNA_def_boolean( + ot->srna, "paste_back", 0, "Paste on Back", "Add pasted strokes behind all strokes"); + RNA_def_property_flag(prop, PROP_SKIP_SAVE); } /* ******************* Move To Layer ****************************** */ @@ -1477,7 +1584,6 @@ static int gp_move_to_layer_exec(bContext *C, wmOperator *op) */ 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)) { @@ -1485,8 +1591,7 @@ static int gp_move_to_layer_exec(bContext *C, wmOperator *op) } /* make copies of selected strokes, and deselect these once we're done */ - for (gps = gpf->strokes.first; gps; gps = gpsn) { - gpsn = gps->next; + LISTBASE_FOREACH_MUTABLE (bGPDstroke *, gps, &gpf->strokes) { /* skip strokes that are invalid for current view */ if (ED_gpencil_stroke_can_use(C, gps) == false) { @@ -1514,7 +1619,7 @@ static int gp_move_to_layer_exec(bContext *C, wmOperator *op) /* Paste them all in one go */ if (strokes.first) { - bGPDframe *gpf = BKE_gpencil_layer_getframe(target_layer, CFRA, GP_GETFRAME_ADD_NEW); + bGPDframe *gpf = BKE_gpencil_layer_frame_get(target_layer, CFRA, GP_GETFRAME_ADD_NEW); BLI_movelisttolist(&gpf->strokes, &strokes); BLI_assert((strokes.first == strokes.last) && (strokes.first == NULL)); @@ -1554,32 +1659,13 @@ void GPENCIL_OT_move_to_layer(wmOperatorType *ot) /* ********************* Add Blank Frame *************************** */ -/* Basically the same as the drawing op */ -static bool UNUSED_FUNCTION(gp_blank_frame_add_poll)(bContext *C) -{ - if (ED_operator_regionactive(C)) { - /* check if current context can support GPencil data */ - if (ED_gpencil_data_get_pointers(C, NULL) != NULL) { - return 1; - } - else { - CTX_wm_operator_poll_msg_set(C, "Failed to find Grease Pencil data to draw into"); - } - } - else { - CTX_wm_operator_poll_msg_set(C, "Active region not set"); - } - - return 0; -} - static int gp_blank_frame_add_exec(bContext *C, wmOperator *op) { bGPdata *gpd = ED_gpencil_data_get_active(C); Scene *scene = CTX_data_scene(C); int cfra = CFRA; - bGPDlayer *active_gpl = BKE_gpencil_layer_getactive(gpd); + bGPDlayer *active_gpl = BKE_gpencil_layer_active_get(gpd); const bool all_layers = RNA_boolean_get(op->ptr, "all_layers"); @@ -1599,7 +1685,7 @@ static int gp_blank_frame_add_exec(bContext *C, wmOperator *op) } /* 1) Check for an existing frame on the current frame */ - bGPDframe *gpf = BKE_gpencil_layer_find_frame(gpl, cfra); + bGPDframe *gpf = BKE_gpencil_layer_frame_find(gpl, cfra); if (gpf) { /* Shunt all frames after (and including) the existing one later by 1-frame */ for (; gpf; gpf = gpf->next) { @@ -1608,7 +1694,7 @@ static int gp_blank_frame_add_exec(bContext *C, wmOperator *op) } /* 2) Now add a new frame, with nothing in it */ - gpl->actframe = BKE_gpencil_layer_getframe(gpl, cfra, GP_GETFRAME_ADD_NEW); + gpl->actframe = BKE_gpencil_layer_frame_get(gpl, cfra, GP_GETFRAME_ADD_NEW); } CTX_DATA_END; @@ -1650,7 +1736,7 @@ void GPENCIL_OT_blank_frame_add(wmOperatorType *ot) static bool gp_actframe_delete_poll(bContext *C) { bGPdata *gpd = ED_gpencil_data_get_active(C); - bGPDlayer *gpl = BKE_gpencil_layer_getactive(gpd); + bGPDlayer *gpl = BKE_gpencil_layer_active_get(gpd); /* only if there's an active layer with an active frame */ return (gpl && gpl->actframe); @@ -1659,7 +1745,7 @@ static bool gp_actframe_delete_poll(bContext *C) static bool gp_annotation_actframe_delete_poll(bContext *C) { bGPdata *gpd = ED_annotation_data_get_active(C); - bGPDlayer *gpl = BKE_gpencil_layer_getactive(gpd); + bGPDlayer *gpl = BKE_gpencil_layer_active_get(gpd); /* only if there's an active layer with an active frame */ return (gpl && gpl->actframe); @@ -1673,11 +1759,11 @@ static int gp_actframe_delete_exec(bContext *C, wmOperator *op) bGPdata *gpd = (!is_annotation) ? ED_gpencil_data_get_active(C) : ED_annotation_data_get_active(C); - bGPDlayer *gpl = BKE_gpencil_layer_getactive(gpd); + bGPDlayer *gpl = BKE_gpencil_layer_active_get(gpd); Scene *scene = CTX_data_scene(C); - bGPDframe *gpf = BKE_gpencil_layer_getframe(gpl, CFRA, GP_GETFRAME_USE_PREV); + bGPDframe *gpf = BKE_gpencil_layer_frame_get(gpl, CFRA, GP_GETFRAME_USE_PREV); /* if there's no existing Grease-Pencil data there, add some */ if (gpd == NULL) { @@ -1690,7 +1776,7 @@ static int gp_actframe_delete_exec(bContext *C, wmOperator *op) } /* delete it... */ - BKE_gpencil_layer_delframe(gpl, gpf); + BKE_gpencil_layer_frame_delete(gpl, gpf); /* notifiers */ DEG_id_tag_update(&gpd->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY); @@ -1747,14 +1833,14 @@ static int gp_actframe_delete_all_exec(bContext *C, wmOperator *op) CTX_DATA_BEGIN (C, bGPDlayer *, gpl, editable_gpencil_layers) { /* try to get the "active" frame - but only if it actually occurs on this frame */ - bGPDframe *gpf = BKE_gpencil_layer_getframe(gpl, CFRA, GP_GETFRAME_USE_PREV); + bGPDframe *gpf = BKE_gpencil_layer_frame_get(gpl, CFRA, GP_GETFRAME_USE_PREV); if (gpf == NULL) { continue; } /* delete it... */ - BKE_gpencil_layer_delframe(gpl, gpf); + BKE_gpencil_layer_frame_delete(gpl, gpf); /* we successfully modified something */ success = true; @@ -1821,15 +1907,13 @@ static int gp_delete_selected_strokes(bContext *C) for (bGPDframe *gpf = init_gpf; gpf; gpf = gpf->next) { if ((gpf == gpl->actframe) || ((gpf->flag & GP_FRAME_SELECT) && (is_multiedit))) { - bGPDstroke *gps, *gpsn; if (gpf == NULL) { continue; } /* simply delete strokes which are selected */ - for (gps = gpf->strokes.first; gps; gps = gpsn) { - gpsn = gps->next; + LISTBASE_FOREACH_MUTABLE (bGPDstroke *, gps, &gpf->strokes) { /* skip strokes that are invalid for current view */ if (ED_gpencil_stroke_can_use(C, gps) == false) { @@ -2060,9 +2144,8 @@ static int gp_dissolve_selected_points(bContext *C, eGP_DissolveMode mode) gps->dvert = new_dvert; gps->totpoints = tot; - /* triangles cache needs to be recalculated */ - gps->flag |= GP_STROKE_RECALC_GEOMETRY; - gps->tot_triangles = 0; + /* Calc geometry data. */ + BKE_gpencil_stroke_geometry_update(gps); /* deselect the stroke, since none of its selected points will still be selected */ gps->flag &= ~GP_STROKE_SELECT; @@ -2104,7 +2187,7 @@ static void gp_stroke_join_islands(bGPDframe *gpf, bGPDstroke *gps_first, bGPDst const int totpoints = gps_first->totpoints + gps_last->totpoints; /* create new stroke */ - bGPDstroke *join_stroke = MEM_dupallocN(gps_first); + bGPDstroke *join_stroke = BKE_gpencil_stroke_duplicate(gps_first, false); join_stroke->points = MEM_callocN(sizeof(bGPDspoint) * totpoints, __func__); join_stroke->totpoints = totpoints; @@ -2132,6 +2215,7 @@ static void gp_stroke_join_islands(bGPDframe *gpf, bGPDstroke *gps_first, bGPDst pt_final->strength = pt->strength; pt_final->time = delta; pt_final->flag = pt->flag; + copy_v4_v4(pt_final->vert_color, pt->vert_color); /* retiming with fixed time interval (we cannot determine real time) */ delta += 0.01f; @@ -2170,6 +2254,8 @@ static void gp_stroke_join_islands(bGPDframe *gpf, bGPDstroke *gps_first, bGPDst /* add new stroke at head */ BLI_addhead(&gpf->strokes, join_stroke); + /* Calc geometry data. */ + BKE_gpencil_stroke_geometry_update(join_stroke); /* remove first stroke */ BLI_remlink(&gpf->strokes, gps_first); @@ -2246,18 +2332,14 @@ void gp_stroke_delete_tagged_points(bGPDframe *gpf, /* Create each new stroke... */ for (idx = 0; idx < num_islands; idx++) { tGPDeleteIsland *island = &islands[idx]; - new_stroke = MEM_dupallocN(gps); + new_stroke = BKE_gpencil_stroke_duplicate(gps, false); /* if cyclic and first stroke, save to join later */ if ((is_cyclic) && (gps_first == NULL)) { gps_first = new_stroke; } - /* initialize triangle memory - to be calculated on next redraw */ - new_stroke->triangles = NULL; - new_stroke->flag |= GP_STROKE_RECALC_GEOMETRY; new_stroke->flag &= ~GP_STROKE_CYCLIC; - new_stroke->tot_triangles = 0; /* Compute new buffer size (+ 1 needed as the endpoint index is "inclusive") */ new_stroke->totpoints = island->end_idx - island->start_idx + 1; @@ -2320,6 +2402,9 @@ void gp_stroke_delete_tagged_points(bGPDframe *gpf, BKE_gpencil_free_stroke(new_stroke); } else { + /* Calc geometry data. */ + BKE_gpencil_stroke_geometry_update(new_stroke); + if (next_stroke) { BLI_insertlinkbefore(&gpf->strokes, next_stroke, new_stroke); } @@ -2355,15 +2440,13 @@ static int gp_delete_selected_points(bContext *C) for (bGPDframe *gpf = init_gpf; gpf; gpf = gpf->next) { if ((gpf == gpl->actframe) || ((gpf->flag & GP_FRAME_SELECT) && (is_multiedit))) { - bGPDstroke *gps, *gpsn; if (gpf == NULL) { continue; } /* simply delete strokes which are selected */ - for (gps = gpf->strokes.first; gps; gps = gpsn) { - gpsn = gps->next; + LISTBASE_FOREACH_MUTABLE (bGPDstroke *, gps, &gpf->strokes) { /* skip strokes that are invalid for current view */ if (ED_gpencil_stroke_can_use(C, gps) == false) { @@ -2379,7 +2462,7 @@ static int gp_delete_selected_points(bContext *C) gps->flag &= ~GP_STROKE_SELECT; /* delete unwanted points by splitting stroke into several smaller ones */ - gp_stroke_delete_tagged_points(gpf, gps, gpsn, GP_SPOINT_SELECT, false, 0); + gp_stroke_delete_tagged_points(gpf, gps, gps->next, GP_SPOINT_SELECT, false, 0); changed = true; } @@ -2532,16 +2615,16 @@ static int gp_snap_to_grid(bContext *C, wmOperator *UNUSED(op)) Object *obact = CTX_data_active_object(C); const float gridf = ED_view3d_grid_view_scale(scene, v3d, rv3d, NULL); - for (bGPDlayer *gpl = gpd->layers.first; gpl; gpl = gpl->next) { + LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd->layers) { /* only editable and visible layers are considered */ - if (gpencil_layer_is_editable(gpl) && (gpl->actframe != NULL)) { + if (BKE_gpencil_layer_is_editable(gpl) && (gpl->actframe != NULL)) { bGPDframe *gpf = gpl->actframe; float diff_mat[4][4]; /* calculate difference matrix object */ - ED_gpencil_parent_location(depsgraph, obact, gpd, gpl, diff_mat); + BKE_gpencil_parent_matrix_get(depsgraph, obact, gpl, diff_mat); - for (bGPDstroke *gps = gpf->strokes.first; gps; gps = gps->next) { + LISTBASE_FOREACH (bGPDstroke *, gps, &gpf->strokes) { bGPDspoint *pt; int i; @@ -2568,7 +2651,7 @@ static int gp_snap_to_grid(bContext *C, wmOperator *UNUSED(op)) /* return data */ copy_v3_v3(&pt->x, fpt); - gp_apply_parent_point(depsgraph, obact, gpd, gpl, pt); + gp_apply_parent_point(depsgraph, obact, gpl, pt); } } } @@ -2609,16 +2692,16 @@ static int gp_snap_to_cursor(bContext *C, wmOperator *op) const bool use_offset = RNA_boolean_get(op->ptr, "use_offset"); const float *cursor_global = scene->cursor.location; - for (bGPDlayer *gpl = gpd->layers.first; gpl; gpl = gpl->next) { + LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd->layers) { /* only editable and visible layers are considered */ - if (gpencil_layer_is_editable(gpl) && (gpl->actframe != NULL)) { + if (BKE_gpencil_layer_is_editable(gpl) && (gpl->actframe != NULL)) { bGPDframe *gpf = gpl->actframe; float diff_mat[4][4]; /* calculate difference matrix */ - ED_gpencil_parent_location(depsgraph, obact, gpd, gpl, diff_mat); + BKE_gpencil_parent_matrix_get(depsgraph, obact, gpl, diff_mat); - for (bGPDstroke *gps = gpf->strokes.first; gps; gps = gps->next) { + LISTBASE_FOREACH (bGPDstroke *, gps, &gpf->strokes) { bGPDspoint *pt; int i; @@ -2652,7 +2735,7 @@ static int gp_snap_to_cursor(bContext *C, wmOperator *op) for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) { if (pt->flag & GP_SPOINT_SELECT) { copy_v3_v3(&pt->x, cursor_global); - gp_apply_parent_point(depsgraph, obact, gpd, gpl, pt); + gp_apply_parent_point(depsgraph, obact, gpl, pt); } } } @@ -2707,16 +2790,16 @@ static int gp_snap_cursor_to_sel(bContext *C, wmOperator *UNUSED(op)) INIT_MINMAX(min, max); /* calculate midpoints from selected points */ - for (bGPDlayer *gpl = gpd->layers.first; gpl; gpl = gpl->next) { + LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd->layers) { /* only editable and visible layers are considered */ - if (gpencil_layer_is_editable(gpl) && (gpl->actframe != NULL)) { + if (BKE_gpencil_layer_is_editable(gpl) && (gpl->actframe != NULL)) { bGPDframe *gpf = gpl->actframe; float diff_mat[4][4]; /* calculate difference matrix */ - ED_gpencil_parent_location(depsgraph, obact, gpd, gpl, diff_mat); + BKE_gpencil_parent_matrix_get(depsgraph, obact, gpl, diff_mat); - for (bGPDstroke *gps = gpf->strokes.first; gps; gps = gps->next) { + LISTBASE_FOREACH (bGPDstroke *, gps, &gpf->strokes) { bGPDspoint *pt; int i; @@ -2783,7 +2866,7 @@ void GPENCIL_OT_snap_cursor_to_selected(wmOperatorType *ot) static int gp_stroke_apply_thickness_exec(bContext *C, wmOperator *UNUSED(op)) { bGPdata *gpd = ED_gpencil_data_get_active(C); - bGPDlayer *gpl = BKE_gpencil_layer_getactive(gpd); + bGPDlayer *gpl = BKE_gpencil_layer_active_get(gpd); /* sanity checks */ if (ELEM(NULL, gpd, gpl, gpl->frames.first)) { @@ -2791,8 +2874,8 @@ static int gp_stroke_apply_thickness_exec(bContext *C, wmOperator *UNUSED(op)) } /* loop all strokes */ - for (bGPDframe *gpf = gpl->frames.first; gpf; gpf = gpf->next) { - for (bGPDstroke *gps = gpf->strokes.first; gps; gps = gps->next) { + LISTBASE_FOREACH (bGPDframe *, gpf, &gpl->frames) { + LISTBASE_FOREACH (bGPDstroke *, gps, &gpf->strokes) { /* Apply thickness */ if ((gps->thickness == 0) && (gpl->line_change == 0)) { gps->thickness = gpl->thickness; @@ -2866,8 +2949,8 @@ static int gp_stroke_cyclical_set_exec(bContext *C, wmOperator *op) continue; } /* skip hidden or locked colors */ - if (!gp_style || (gp_style->flag & GP_STYLE_COLOR_HIDE) || - (gp_style->flag & GP_STYLE_COLOR_LOCKED)) { + if (!gp_style || (gp_style->flag & GP_MATERIAL_HIDE) || + (gp_style->flag & GP_MATERIAL_LOCKED)) { continue; } @@ -2891,7 +2974,7 @@ static int gp_stroke_cyclical_set_exec(bContext *C, wmOperator *op) /* Create new geometry. */ if ((gps->flag & GP_STROKE_CYCLIC) && (geometry)) { - BKE_gpencil_close_stroke(gps); + BKE_gpencil_stroke_close(gps); } } @@ -2980,8 +3063,8 @@ static int gp_stroke_caps_set_exec(bContext *C, wmOperator *op) continue; } /* skip hidden or locked colors */ - if (!gp_style || (gp_style->flag & GP_STYLE_COLOR_HIDE) || - (gp_style->flag & GP_STYLE_COLOR_LOCKED)) { + if (!gp_style || (gp_style->flag & GP_MATERIAL_HIDE) || + (gp_style->flag & GP_MATERIAL_LOCKED)) { continue; } @@ -3061,6 +3144,7 @@ static void gpencil_flip_stroke(bGPDstroke *gps) pt.pressure = point->pressure; pt.strength = point->strength; pt.time = point->time; + copy_v4_v4(pt.vert_color, point->vert_color); /* replace first point with last point */ point2 = &gps->points[end]; @@ -3071,6 +3155,7 @@ static void gpencil_flip_stroke(bGPDstroke *gps) point->pressure = point2->pressure; point->strength = point2->strength; point->time = point2->time; + copy_v4_v4(point->vert_color, point2->vert_color); /* replace last point with first saved before */ point = &gps->points[end]; @@ -3081,6 +3166,7 @@ static void gpencil_flip_stroke(bGPDstroke *gps) point->pressure = pt.pressure; point->strength = pt.strength; point->time = pt.time; + copy_v4_v4(point->vert_color, pt.vert_color); end--; } @@ -3111,6 +3197,7 @@ static void gpencil_stroke_copy_point(bGPDstroke *gps, newpoint->pressure = pressure; newpoint->strength = strength; newpoint->time = point->time + deltatime; + copy_v4_v4(newpoint->vert_color, point->vert_color); if (gps->dvert != NULL) { MDeformVert *dvert = &gps->dvert[idx]; @@ -3184,8 +3271,7 @@ static void gpencil_stroke_join_strokes(bGPDstroke *gps_a, static int gp_stroke_join_exec(bContext *C, wmOperator *op) { bGPdata *gpd = ED_gpencil_data_get_active(C); - bGPDlayer *activegpl = BKE_gpencil_layer_getactive(gpd); - bGPDstroke *gps, *gpsn; + bGPDlayer *activegpl = BKE_gpencil_layer_active_get(gpd); Object *ob = CTX_data_active_object(C); bGPDframe *gpf_a = NULL; @@ -3215,8 +3301,7 @@ static int gp_stroke_join_exec(bContext *C, wmOperator *op) continue; } - for (gps = gpf->strokes.first; gps; gps = gpsn) { - gpsn = gps->next; + LISTBASE_FOREACH_MUTABLE (bGPDstroke *, gps, &gpf->strokes) { if (gps->flag & GP_STROKE_SELECT) { /* skip strokes that are invalid for current view */ if (ED_gpencil_stroke_can_use(C, gps) == false) { @@ -3241,15 +3326,7 @@ static int gp_stroke_join_exec(bContext *C, wmOperator *op) /* create a new stroke if was not created before (only created if something to join) */ if (new_stroke == NULL) { - new_stroke = MEM_dupallocN(stroke_a); - new_stroke->points = MEM_dupallocN(stroke_a->points); - if (stroke_a->dvert != NULL) { - new_stroke->dvert = MEM_dupallocN(stroke_a->dvert); - BKE_gpencil_stroke_weights_duplicate(stroke_a, new_stroke); - } - new_stroke->triangles = NULL; - new_stroke->tot_triangles = 0; - new_stroke->flag |= GP_STROKE_RECALC_GEOMETRY; + new_stroke = BKE_gpencil_stroke_duplicate(stroke_a, true); /* if new, set current color */ if (type == GP_STROKE_JOINCOPY) { @@ -3263,6 +3340,9 @@ static int gp_stroke_join_exec(bContext *C, wmOperator *op) /* if join only, delete old strokes */ if (type == GP_STROKE_JOIN) { if (stroke_a) { + /* Calc geometry data. */ + BKE_gpencil_stroke_geometry_update(new_stroke); + BLI_insertlinkbefore(&gpf_a->strokes, stroke_a, new_stroke); BLI_remlink(&gpf->strokes, stroke_a); BKE_gpencil_free_stroke(stroke_a); @@ -3287,6 +3367,8 @@ static int gp_stroke_join_exec(bContext *C, wmOperator *op) if (activegpl->actframe == NULL) { activegpl->actframe = BKE_gpencil_frame_addnew(activegpl, gpf_a->framenum); } + /* Calc geometry data. */ + BKE_gpencil_stroke_geometry_update(new_stroke); BLI_addtail(&activegpl->actframe->strokes, new_stroke); } @@ -3347,7 +3429,7 @@ static int gp_stroke_flip_exec(bContext *C, wmOperator *UNUSED(op)) continue; } - for (bGPDstroke *gps = gpf->strokes.first; gps; gps = gps->next) { + LISTBASE_FOREACH (bGPDstroke *, gps, &gpf->strokes) { if (gps->flag & GP_STROKE_SELECT) { /* skip strokes that are invalid for current view */ if (ED_gpencil_stroke_can_use(C, gps) == false) { @@ -3460,7 +3542,7 @@ static int gp_strokes_reproject_exec(bContext *C, wmOperator *op) GP_REPROJECT_TOP, GP_REPROJECT_CURSOR)) { if (mode != GP_REPROJECT_CURSOR) { - ED_gp_get_drawing_reference(scene, ob, gpl, ts->gpencil_v3d_align, origin); + ED_gpencil_drawing_reference_get(scene, ob, gpl, ts->gpencil_v3d_align, origin); } else { copy_v3_v3(origin, scene->cursor.location); @@ -3495,7 +3577,7 @@ static int gp_strokes_reproject_exec(bContext *C, wmOperator *op) copy_v3_v3(&pt->x, &pt2.x); /* apply parent again */ - gp_apply_parent_point(depsgraph, ob, gpd, gpl, pt); + gp_apply_parent_point(depsgraph, ob, gpl, pt); } /* Project screen-space back to 3D space (from current perspective) * so that all points have been treated the same way. */ @@ -3602,6 +3684,43 @@ void GPENCIL_OT_reproject(wmOperatorType *ot) ot->srna, "type", reproject_type, GP_REPROJECT_VIEW, "Projection Type", ""); } +static int gp_recalc_geometry_exec(bContext *C, wmOperator *UNUSED(op)) +{ + Object *ob = CTX_data_active_object(C); + if ((ob == NULL) || (ob->type != OB_GPENCIL)) { + return OPERATOR_CANCELLED; + } + + bGPdata *gpd = (bGPdata *)ob->data; + LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd->layers) { + LISTBASE_FOREACH (bGPDframe *, gpf, &gpl->frames) { + LISTBASE_FOREACH (bGPDstroke *, gps, &gpf->strokes) { + BKE_gpencil_stroke_geometry_update(gps); + } + } + } + /* update changed data */ + DEG_id_tag_update(&gpd->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY); + WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL); + return OPERATOR_FINISHED; +} + +void GPENCIL_OT_recalc_geometry(wmOperatorType *ot) +{ + + /* identifiers */ + ot->name = "Recalculate internal geometry"; + ot->idname = "GPENCIL_OT_recalc_geometry"; + ot->description = "Update all internal geometry data"; + + /* callbacks */ + ot->exec = gp_recalc_geometry_exec; + ot->poll = gp_active_layer_poll; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; +} + /* ******************* Stroke subdivide ************************** */ /* helper to smooth */ static void gp_smooth_stroke(bContext *C, wmOperator *op) @@ -3629,19 +3748,19 @@ static void gp_smooth_stroke(bContext *C, wmOperator *op) /* perform smoothing */ if (smooth_position) { - BKE_gpencil_smooth_stroke(gps, i, factor); + BKE_gpencil_stroke_smooth(gps, i, factor); } if (smooth_strength) { - BKE_gpencil_smooth_stroke_strength(gps, i, factor); + BKE_gpencil_stroke_smooth_strength(gps, i, factor); } if (smooth_thickness) { /* thickness need to repeat process several times */ for (int r2 = 0; r2 < r * 20; r2++) { - BKE_gpencil_smooth_stroke_thickness(gps, i, factor); + BKE_gpencil_stroke_smooth_thickness(gps, i, factor); } } if (smooth_uv) { - BKE_gpencil_smooth_stroke_uv(gps, i, factor); + BKE_gpencil_stroke_smooth_uv(gps, i, factor); } } } @@ -3710,7 +3829,6 @@ static int gp_stroke_subdivide_exec(bContext *C, wmOperator *op) if (gps->dvert != NULL) { gps->dvert = MEM_recallocN(gps->dvert, sizeof(*gps->dvert) * gps->totpoints); } - gps->flag |= GP_STROKE_RECALC_GEOMETRY; /* loop and interpolate */ i2 = 0; @@ -3724,6 +3842,7 @@ static int gp_stroke_subdivide_exec(bContext *C, wmOperator *op) pt_final->strength = pt->strength; pt_final->time = pt->time; pt_final->flag = pt->flag; + copy_v4_v4(pt_final->vert_color, pt->vert_color); if (gps->dvert != NULL) { dvert = &temp_dverts[i]; @@ -3747,6 +3866,7 @@ static int gp_stroke_subdivide_exec(bContext *C, wmOperator *op) pt_final->pressure = interpf(pt->pressure, next->pressure, 0.5f); pt_final->strength = interpf(pt->strength, next->strength, 0.5f); CLAMP(pt_final->strength, GPENCIL_STRENGTH_MIN, 1.0f); + interp_v4_v4v4(pt_final->vert_color, pt->vert_color, next->vert_color, 0.5f); pt_final->time = interpf(pt->time, next->time, 0.5f); pt_final->flag |= GP_SPOINT_SELECT; @@ -3780,9 +3900,8 @@ static int gp_stroke_subdivide_exec(bContext *C, wmOperator *op) MEM_SAFE_FREE(temp_dverts); } - /* triangles cache needs to be recalculated */ - gps->flag |= GP_STROKE_RECALC_GEOMETRY; - gps->tot_triangles = 0; + /* Calc geometry data. */ + BKE_gpencil_stroke_geometry_update(gps); } } GP_EDITABLE_STROKES_END(gpstroke_iter); @@ -3850,7 +3969,7 @@ static int gp_stroke_simplify_exec(bContext *C, wmOperator *op) GP_EDITABLE_STROKES_BEGIN (gpstroke_iter, C, gpl, gps) { if (gps->flag & GP_STROKE_SELECT) { /* simplify stroke using Ramer-Douglas-Peucker algorithm */ - BKE_gpencil_simplify_stroke(gps, factor); + BKE_gpencil_stroke_simplify_adaptive(gps, factor); } } GP_EDITABLE_STROKES_END(gpstroke_iter); @@ -3899,7 +4018,7 @@ static int gp_stroke_simplify_fixed_exec(bContext *C, wmOperator *op) GP_EDITABLE_STROKES_BEGIN (gpstroke_iter, C, gpl, gps) { if (gps->flag & GP_STROKE_SELECT) { for (int i = 0; i < steps; i++) { - BKE_gpencil_simplify_fixed(gps); + BKE_gpencil_stroke_simplify_fixed(gps); } } } @@ -3949,7 +4068,7 @@ static int gp_stroke_sample_exec(bContext *C, wmOperator *op) /* Go through each editable + selected stroke */ GP_EDITABLE_STROKES_BEGIN (gpstroke_iter, C, gpl, gps) { if (gps->flag & GP_STROKE_SELECT) { - BKE_gpencil_sample_stroke(gps, length, true); + BKE_gpencil_stroke_sample(gps, length, true); } } GP_EDITABLE_STROKES_END(gpstroke_iter); @@ -4001,14 +4120,12 @@ static int gp_stroke_trim_exec(bContext *C, wmOperator *UNUSED(op)) for (bGPDframe *gpf = init_gpf; gpf; gpf = gpf->next) { if ((gpf == gpl->actframe) || ((gpf->flag & GP_FRAME_SELECT) && (is_multiedit))) { - bGPDstroke *gps, *gpsn; if (gpf == NULL) { continue; } - for (gps = gpf->strokes.first; gps; gps = gpsn) { - gpsn = gps->next; + LISTBASE_FOREACH_MUTABLE (bGPDstroke *, gps, &gpf->strokes) { /* skip strokes that are invalid for current view */ if (ED_gpencil_stroke_can_use(C, gps) == false) { @@ -4016,7 +4133,7 @@ static int gp_stroke_trim_exec(bContext *C, wmOperator *UNUSED(op)) } if (gps->flag & GP_STROKE_SELECT) { - BKE_gpencil_trim_stroke(gps); + BKE_gpencil_stroke_trim(gps); } } /* if not multiedit, exit loop*/ @@ -4066,7 +4183,7 @@ static int gp_stroke_separate_exec(bContext *C, wmOperator *op) Main *bmain = CTX_data_main(C); Scene *scene = CTX_data_scene(C); ViewLayer *view_layer = CTX_data_view_layer(C); - Base *base_old = CTX_data_active_base(C); + Base *base_prev = CTX_data_active_base(C); bGPdata *gpd_src = ED_gpencil_data_get_active(C); Object *ob = CTX_data_active_object(C); @@ -4096,7 +4213,7 @@ static int gp_stroke_separate_exec(bContext *C, wmOperator *op) /* Take into account user preferences for duplicating actions. */ short dupflag = (U.dupflag & USER_DUP_ACT); - base_new = ED_object_add_duplicate(bmain, scene, view_layer, base_old, dupflag); + base_new = ED_object_add_duplicate(bmain, scene, view_layer, base_prev, dupflag); ob_dst = base_new->object; ob_dst->mode = OB_MODE_OBJECT; /* create new grease pencil datablock */ @@ -4111,7 +4228,6 @@ static int gp_stroke_separate_exec(bContext *C, wmOperator *op) for (bGPDframe *gpf = init_gpf; gpf; gpf = gpf->next) { if ((gpf == gpl->actframe) || ((gpf->flag & GP_FRAME_SELECT) && (is_multiedit))) { - bGPDstroke *gps, *gpsn; if (gpf == NULL) { continue; @@ -4119,8 +4235,7 @@ static int gp_stroke_separate_exec(bContext *C, wmOperator *op) gpf_dst = NULL; - for (gps = gpf->strokes.first; gps; gps = gpsn) { - gpsn = gps->next; + LISTBASE_FOREACH_MUTABLE (bGPDstroke *, gps, &gpf->strokes) { /* skip strokes that are invalid for current view */ if (ED_gpencil_stroke_can_use(C, gps) == false) { @@ -4139,7 +4254,7 @@ static int gp_stroke_separate_exec(bContext *C, wmOperator *op) /* add frame if not created before */ if (gpf_dst == NULL) { - gpf_dst = BKE_gpencil_layer_getframe(gpl_dst, gpf->framenum, GP_GETFRAME_ADD_NEW); + gpf_dst = BKE_gpencil_layer_frame_get(gpl_dst, gpf->framenum, GP_GETFRAME_ADD_NEW); } /* add duplicate materials */ @@ -4152,7 +4267,7 @@ static int gp_stroke_separate_exec(bContext *C, wmOperator *op) /* selected points mode */ if (mode == GP_SEPARATE_POINT) { /* make copy of source stroke */ - bGPDstroke *gps_dst = BKE_gpencil_stroke_duplicate(gps); + bGPDstroke *gps_dst = BKE_gpencil_stroke_duplicate(gps, true); /* Reassign material. */ gps_dst->mat_nr = idx; @@ -4169,7 +4284,7 @@ static int gp_stroke_separate_exec(bContext *C, wmOperator *op) gp_stroke_delete_tagged_points(gpf_dst, gps_dst, NULL, GP_SPOINT_SELECT, false, 0); /* delete selected points from origin stroke */ - gp_stroke_delete_tagged_points(gpf, gps, gpsn, GP_SPOINT_SELECT, false, 0); + gp_stroke_delete_tagged_points(gpf, gps, gps->next, GP_SPOINT_SELECT, false, 0); } /* selected strokes mode */ else if (mode == GP_SEPARATE_STROKE) { @@ -4200,10 +4315,10 @@ static int gp_stroke_separate_exec(bContext *C, wmOperator *op) if (gpl) { /* try to set a new active layer in source datablock */ if (gpl->prev) { - BKE_gpencil_layer_setactive(gpd_src, gpl->prev); + BKE_gpencil_layer_active_set(gpd_src, gpl->prev); } else if (gpl->next) { - BKE_gpencil_layer_setactive(gpd_src, gpl->next); + BKE_gpencil_layer_active_set(gpd_src, gpl->next); } /* unlink from source datablock */ BLI_remlink(&gpd_src->layers, gpl); @@ -4212,8 +4327,8 @@ static int gp_stroke_separate_exec(bContext *C, wmOperator *op) BLI_addtail(&gpd_dst->layers, gpl); /* add duplicate materials */ - for (bGPDframe *gpf = gpl->frames.first; gpf; gpf = gpf->next) { - for (bGPDstroke *gps = gpf->strokes.first; gps; gps = gps->next) { + LISTBASE_FOREACH (bGPDframe *, gpf, &gpl->frames) { + LISTBASE_FOREACH (bGPDstroke *, gps, &gpf->strokes) { /* skip strokes that are invalid for current view */ if (ED_gpencil_stroke_can_use(C, gps) == false) { continue; @@ -4227,8 +4342,8 @@ static int gp_stroke_separate_exec(bContext *C, wmOperator *op) /* Ensure destination object has one active layer. */ if (gpd_dst->layers.first != NULL) { - if (BKE_gpencil_layer_getactive(gpd_dst) == NULL) { - BKE_gpencil_layer_setactive(gpd_dst, gpd_dst->layers.first); + if (BKE_gpencil_layer_active_get(gpd_dst) == NULL) { + BKE_gpencil_layer_active_set(gpd_dst, gpd_dst->layers.first); } } @@ -4288,14 +4403,12 @@ static int gp_stroke_split_exec(bContext *C, wmOperator *UNUSED(op)) for (bGPDframe *gpf = init_gpf; gpf; gpf = gpf->next) { if ((gpf == gpl->actframe) || ((gpf->flag & GP_FRAME_SELECT) && (is_multiedit))) { - bGPDstroke *gps, *gpsn; if (gpf == NULL) { continue; } - for (gps = gpf->strokes.first; gps; gps = gpsn) { - gpsn = gps->next; + LISTBASE_FOREACH_MUTABLE (bGPDstroke *, gps, &gpf->strokes) { /* skip strokes that are invalid for current view */ if (ED_gpencil_stroke_can_use(C, gps) == false) { @@ -4308,7 +4421,7 @@ static int gp_stroke_split_exec(bContext *C, wmOperator *UNUSED(op)) /* split selected strokes */ if (gps->flag & GP_STROKE_SELECT) { /* make copy of source stroke */ - bGPDstroke *gps_dst = BKE_gpencil_stroke_duplicate(gps); + bGPDstroke *gps_dst = BKE_gpencil_stroke_duplicate(gps, true); /* link to same frame */ BLI_addtail(&gpf->strokes, gps_dst); @@ -4322,11 +4435,11 @@ static int gp_stroke_split_exec(bContext *C, wmOperator *UNUSED(op)) gp_stroke_delete_tagged_points(gpf, gps_dst, NULL, GP_SPOINT_SELECT, true, 0); /* delete selected points from origin stroke */ - gp_stroke_delete_tagged_points(gpf, gps, gpsn, GP_SPOINT_SELECT, false, 0); + gp_stroke_delete_tagged_points(gpf, gps, gps->next, GP_SPOINT_SELECT, false, 0); } } /* select again tagged points */ - for (gps = gpf->strokes.first; gps; gps = gps->next) { + LISTBASE_FOREACH (bGPDstroke *, gps, &gpf->strokes) { bGPDspoint *ptn = gps->points; for (int i2 = 0; i2 < gps->totpoints; i2++, ptn++) { if (ptn->flag & GP_SPOINT_TAG) { @@ -4566,8 +4679,7 @@ static int gpencil_cutter_lasso_select(bContext *C, GP_EDITABLE_STROKES_END(gpstroke_iter); /* dissolve selected points */ - bGPDstroke *gpsn; - for (bGPDlayer *gpl = gpd->layers.first; gpl; gpl = gpl->next) { + LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd->layers) { if (gpl->flag & GP_LAYER_LOCKED) { continue; } @@ -4576,8 +4688,7 @@ static int gpencil_cutter_lasso_select(bContext *C, if (gpf == NULL) { continue; } - for (bGPDstroke *gps = gpf->strokes.first; gps; gps = gpsn) { - gpsn = gps->next; + LISTBASE_FOREACH_MUTABLE (bGPDstroke *, gps, &gpf->strokes) { if (gps->flag & GP_STROKE_SELECT) { gpencil_cutter_dissolve(gpl, gps); } @@ -4662,11 +4773,11 @@ bool ED_object_gpencil_exit(struct Main *bmain, Object *ob) bGPdata *gpd = (bGPdata *)ob->data; gpd->flag &= ~(GP_DATA_STROKE_PAINTMODE | GP_DATA_STROKE_EDITMODE | GP_DATA_STROKE_SCULPTMODE | - GP_DATA_STROKE_WEIGHTMODE); + GP_DATA_STROKE_WEIGHTMODE | GP_DATA_STROKE_VERTEXMODE); ob->restore_mode = ob->mode; ob->mode &= ~(OB_MODE_PAINT_GPENCIL | OB_MODE_EDIT_GPENCIL | OB_MODE_SCULPT_GPENCIL | - OB_MODE_WEIGHT_GPENCIL); + OB_MODE_WEIGHT_GPENCIL | OB_MODE_VERTEX_GPENCIL); /* Inform all CoW versions that we changed the mode. */ DEG_id_tag_update_ex(bmain, &ob->id, ID_RECALC_COPY_ON_WRITE); @@ -4687,7 +4798,7 @@ static bool gp_merge_by_distance_poll(bContext *C) return false; } - bGPDlayer *gpl = BKE_gpencil_layer_getactive(gpd); + bGPDlayer *gpl = BKE_gpencil_layer_active_get(gpd); return ((gpl != NULL) && (ob->mode == OB_MODE_EDIT_GPENCIL)); } @@ -4707,7 +4818,7 @@ static int gp_merge_by_distance_exec(bContext *C, wmOperator *op) /* Go through each editable selected stroke */ GP_EDITABLE_STROKES_BEGIN (gpstroke_iter, C, gpl, gps) { if (gps->flag & GP_STROKE_SELECT) { - BKE_gpencil_merge_distance_stroke(gpf_, gps, threshold, unselected); + BKE_gpencil_stroke_merge_distance(gpf_, gps, threshold, unselected); } } GP_EDITABLE_STROKES_END(gpstroke_iter); diff --git a/source/blender/editors/gpencil/gpencil_fill.c b/source/blender/editors/gpencil/gpencil_fill.c index d76ab85ad31..c928c1e933a 100644 --- a/source/blender/editors/gpencil/gpencil_fill.c +++ b/source/blender/editors/gpencil/gpencil_fill.c @@ -134,6 +134,9 @@ typedef struct tGPDfill { /* scaling factor */ short fill_factor; + /* Frame to use. */ + int active_cfra; + /** number of elements currently in cache */ short sbuffer_used; /** temporary points */ @@ -228,7 +231,6 @@ static void gp_draw_datablock(tGPDfill *tgpf, const float ink[4]) Object *ob = tgpf->ob; bGPdata *gpd = tgpf->gpd; - int cfra_eval = (int)DEG_get_ctime(tgpf->depsgraph); tGPDdraw tgpw; tgpw.rv3d = tgpf->rv3d; @@ -245,9 +247,9 @@ static void gp_draw_datablock(tGPDfill *tgpf, const float ink[4]) GPU_blend(true); - for (bGPDlayer *gpl = gpd->layers.first; gpl; gpl = gpl->next) { + LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd->layers) { /* calculate parent position */ - ED_gpencil_parent_location(tgpw.depsgraph, ob, gpd, gpl, tgpw.diff_mat); + BKE_gpencil_parent_matrix_get(tgpw.depsgraph, ob, gpl, tgpw.diff_mat); /* do not draw layer if hidden */ if (gpl->flag & GP_LAYER_HIDE) { @@ -256,25 +258,25 @@ static void gp_draw_datablock(tGPDfill *tgpf, const float ink[4]) /* if active layer and no keyframe, create a new one */ if (gpl == tgpf->gpl) { - if ((gpl->actframe == NULL) || (gpl->actframe->framenum != cfra_eval)) { - BKE_gpencil_layer_getframe(gpl, cfra_eval, GP_GETFRAME_ADD_NEW); + if ((gpl->actframe == NULL) || (gpl->actframe->framenum != tgpf->active_cfra)) { + BKE_gpencil_layer_frame_get(gpl, tgpf->active_cfra, GP_GETFRAME_ADD_NEW); } } /* get frame to draw */ - bGPDframe *gpf = BKE_gpencil_layer_getframe(gpl, cfra_eval, GP_GETFRAME_USE_PREV); + bGPDframe *gpf = BKE_gpencil_layer_frame_get(gpl, tgpf->active_cfra, GP_GETFRAME_USE_PREV); if (gpf == NULL) { continue; } - for (bGPDstroke *gps = gpf->strokes.first; gps; gps = gps->next) { + LISTBASE_FOREACH (bGPDstroke *, gps, &gpf->strokes) { /* check if stroke can be drawn */ if ((gps->points == NULL) || (gps->totpoints < 2)) { continue; } /* check if the color is visible */ MaterialGPencilStyle *gp_style = BKE_gpencil_material_settings(ob, gps->mat_nr + 1); - if ((gp_style == NULL) || (gp_style->flag & GP_STYLE_COLOR_HIDE)) { + if ((gp_style == NULL) || (gp_style->flag & GP_MATERIAL_HIDE)) { continue; } @@ -291,7 +293,7 @@ static void gp_draw_datablock(tGPDfill *tgpf, const float ink[4]) tgpw.onion = true; tgpw.custonion = true; - bool textured_stroke = (gp_style->stroke_style == GP_STYLE_STROKE_STYLE_TEXTURE); + bool textured_stroke = (gp_style->stroke_style == GP_MATERIAL_STROKE_STYLE_TEXTURE); /* normal strokes */ if (((tgpf->fill_draw_mode == GP_FILL_DMODE_STROKE) || @@ -666,6 +668,62 @@ static void gpencil_boundaryfill_area(tGPDfill *tgpf) BLI_stack_free(stack); } +/* Check if there are some pixel not filled with green. If no points, means nothing to fill. */ +static bool gpencil_check_borders(tGPDfill *tgpf) +{ + ImBuf *ibuf; + void *lock; + ibuf = BKE_image_acquire_ibuf(tgpf->ima, NULL, &lock); + int idx; + int pixel = 0; + float color[4]; + bool found = false; + + /* horizontal lines */ + for (idx = 0; idx < ibuf->x; idx++) { + /* bottom line */ + get_pixel(ibuf, idx, color); + if (color[1] != 1.0f) { + found = true; + break; + } + /* top line */ + pixel = idx + (ibuf->x * (ibuf->y - 1)); + get_pixel(ibuf, pixel, color); + if (color[1] != 1.0f) { + found = true; + break; + } + } + if (!found) { + /* vertical lines */ + for (idx = 0; idx < ibuf->y; idx++) { + /* left line */ + get_pixel(ibuf, ibuf->x * idx, color); + if (color[1] != 1.0f) { + found = true; + break; + } + /* right line */ + pixel = ibuf->x * idx + (ibuf->x - 1); + get_pixel(ibuf, pixel, color); + if (color[1] != 1.0f) { + found = true; + break; + } + } + } + + /* release ibuf */ + if (ibuf) { + BKE_image_release_ibuf(tgpf->ima, ibuf, lock); + } + + tgpf->ima->id.tag |= LIB_TAG_DOIT; + + return found; +} + /* clean external border of image to avoid infinite loops */ static void gpencil_clean_borders(tGPDfill *tgpf) { @@ -1006,8 +1064,6 @@ static void gpencil_points_from_stack(tGPDfill *tgpf) /* create a grease pencil stroke using points in buffer */ static void gpencil_stroke_from_buffer(tGPDfill *tgpf) { - const int cfra_eval = (int)DEG_get_ctime(tgpf->depsgraph); - ToolSettings *ts = tgpf->scene->toolsettings; const char *align_flag = &ts->gpencil_v3d_align; const bool is_depth = (bool)(*align_flag & (GP_PROJECT_DEPTH_VIEW | GP_PROJECT_DEPTH_STROKE)); @@ -1026,16 +1082,23 @@ static void gpencil_stroke_from_buffer(tGPDfill *tgpf) return; } - /* get frame or create a new one */ - tgpf->gpf = BKE_gpencil_layer_getframe(tgpf->gpl, cfra_eval, GP_GETFRAME_ADD_NEW); + /* Get frame or create a new one. */ + tgpf->gpf = BKE_gpencil_layer_frame_get(tgpf->gpl, tgpf->active_cfra, GP_GETFRAME_ADD_NEW); + + /* Set frame as selected. */ + tgpf->gpf->flag |= GP_FRAME_SELECT; /* create new stroke */ bGPDstroke *gps = MEM_callocN(sizeof(bGPDstroke), "bGPDstroke"); gps->thickness = brush->size; - gps->gradient_f = brush->gpencil_settings->gradient_f; - copy_v2_v2(gps->gradient_s, brush->gpencil_settings->gradient_s); + gps->fill_opacity_fac = 1.0f; + gps->hardeness = brush->gpencil_settings->hardeness; + copy_v2_v2(gps->aspect_ratio, brush->gpencil_settings->aspect_ratio); gps->inittime = 0.0f; + /* Apply the vertex color to fill. */ + ED_gpencil_fill_vertex_color_set(ts, brush, gps); + /* the polygon must be closed, so enabled cyclic */ gps->flag |= GP_STROKE_CYCLIC; gps->flag |= GP_STROKE_3DSPACE; @@ -1054,11 +1117,6 @@ static void gpencil_stroke_from_buffer(tGPDfill *tgpf) gps->totpoints = tgpf->sbuffer_used; gps->points = MEM_callocN(sizeof(bGPDspoint) * tgpf->sbuffer_used, "gp_stroke_points"); - /* initialize triangle memory to dummy data */ - gps->tot_triangles = 0; - gps->triangles = NULL; - gps->flag |= GP_STROKE_RECALC_GEOMETRY; - /* add stroke to frame */ if ((ts->gpencil_flags & GP_TOOL_FLAG_PAINT_ONBACK) || (tgpf->on_back == true)) { BLI_addhead(&tgpf->gpf->strokes, gps); @@ -1093,6 +1151,9 @@ static void gpencil_stroke_from_buffer(tGPDfill *tgpf) pt->strength = 1.0f; pt->time = 0.0f; + /* Apply the vertex color to point. */ + ED_gpencil_point_vertex_color_set(ts, brush, pt); + if ((ts->gpencil_flags & GP_TOOL_FLAG_CREATE_WEIGHTS) && (have_weight)) { MDeformWeight *dw = BKE_defvert_ensure_index(dvert, def_nr); if (dw) { @@ -1115,7 +1176,7 @@ static void gpencil_stroke_from_buffer(tGPDfill *tgpf) float smoothfac = 1.0f; for (int r = 0; r < 1; r++) { for (int i = 0; i < gps->totpoints; i++) { - BKE_gpencil_smooth_stroke(gps, i, smoothfac - reduce); + BKE_gpencil_stroke_smooth(gps, i, smoothfac - reduce); } reduce += 0.25f; // reduce the factor } @@ -1124,7 +1185,8 @@ static void gpencil_stroke_from_buffer(tGPDfill *tgpf) if ((tgpf->lock_axis > GP_LOCKAXIS_VIEW) && ((ts->gpencil_v3d_align & GP_PROJECT_DEPTH_VIEW) == 0)) { float origin[3]; - ED_gp_get_drawing_reference(tgpf->scene, tgpf->ob, tgpf->gpl, ts->gpencil_v3d_align, origin); + ED_gpencil_drawing_reference_get( + tgpf->scene, tgpf->ob, tgpf->gpl, ts->gpencil_v3d_align, origin); ED_gp_project_stroke_to_plane( tgpf->scene, tgpf->ob, tgpf->rv3d, gps, origin, tgpf->lock_axis - 1); } @@ -1132,7 +1194,7 @@ static void gpencil_stroke_from_buffer(tGPDfill *tgpf) /* if parented change position relative to parent object */ for (int a = 0; a < tgpf->sbuffer_used; a++) { pt = &gps->points[a]; - gp_apply_parent_point(tgpf->depsgraph, tgpf->ob, tgpf->gpd, tgpf->gpl, pt); + gp_apply_parent_point(tgpf->depsgraph, tgpf->ob, tgpf->gpl, pt); } /* if camera view, reproject flat to view to avoid perspective effect */ @@ -1142,14 +1204,17 @@ static void gpencil_stroke_from_buffer(tGPDfill *tgpf) /* simplify stroke */ for (int b = 0; b < tgpf->fill_simplylvl; b++) { - BKE_gpencil_simplify_fixed(gps); + BKE_gpencil_stroke_simplify_fixed(gps); } + + /* Calc geometry data. */ + BKE_gpencil_stroke_geometry_update(gps); } /* ----------------------- */ /* Drawing */ /* Helper: Draw status message while the user is running the operator */ -static void gpencil_fill_status_indicators(bContext *C, tGPDfill *UNUSED(tgpf)) +static void gpencil_fill_status_indicators(bContext *C) { const char *status_str = TIP_("Fill: ESC/RMB cancel, LMB Fill, Shift Draw on Back"); ED_workspace_status_text(C, status_str); @@ -1228,10 +1293,11 @@ static tGPDfill *gp_session_init_fill(bContext *C, wmOperator *UNUSED(op)) /* set GP datablock */ tgpf->gpd = gpd; - tgpf->gpl = BKE_gpencil_layer_getactive(gpd); + tgpf->gpl = BKE_gpencil_layer_active_get(gpd); if (tgpf->gpl == NULL) { tgpf->gpl = BKE_gpencil_layer_addnew(tgpf->gpd, DATA_("GP_Layer"), true); } + tgpf->lock_axis = ts->gp_sculpt.lock_axis; tgpf->oldkey = -1; @@ -1339,7 +1405,7 @@ static int gpencil_fill_init(bContext *C, wmOperator *op) tGPDfill *tgpf; /* cannot paint in locked layer */ bGPdata *gpd = CTX_data_gpencil_data(C); - bGPDlayer *gpl = BKE_gpencil_layer_getactive(gpd); + bGPDlayer *gpl = BKE_gpencil_layer_active_get(gpd); if ((gpl) && (gpl->flag & GP_LAYER_LOCKED)) { return 0; } @@ -1401,7 +1467,7 @@ static int gpencil_fill_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSE WM_cursor_modal_set(CTX_wm_window(C), WM_CURSOR_PAINT_BRUSH); - gpencil_fill_status_indicators(C, tgpf); + gpencil_fill_status_indicators(C); DEG_id_tag_update(&tgpf->gpd->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY); WM_event_add_notifier(C, NC_GPENCIL | NA_EDITED, NULL); @@ -1416,6 +1482,7 @@ static int gpencil_fill_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSE static int gpencil_fill_modal(bContext *C, wmOperator *op, const wmEvent *event) { tGPDfill *tgpf = op->customdata; + Scene *scene = tgpf->scene; int estate = OPERATOR_PASS_THROUGH; /* default exit state - pass through */ @@ -1426,7 +1493,7 @@ static int gpencil_fill_modal(bContext *C, wmOperator *op, const wmEvent *event) break; case LEFTMOUSE: tgpf->on_back = RNA_boolean_get(op->ptr, "on_back"); - /* first time the event is not enabled to show help lines */ + /* first time the event is not enabled to show help lines. */ if ((tgpf->oldkey != -1) || ((tgpf->flag & GP_BRUSH_FILL_SHOW_HELPLINES) == 0)) { ARegion *region = BKE_area_find_region_xy( CTX_wm_area(C), RGN_TYPE_ANY, event->x, event->y); @@ -1437,42 +1504,55 @@ static int gpencil_fill_modal(bContext *C, wmOperator *op, const wmEvent *event) in_bounds = BLI_rcti_isect_pt(®ion->winrct, event->x, event->y); if ((in_bounds) && (region->regiontype == RGN_TYPE_WINDOW)) { - /* TODO GPXX: Verify the mouse click is right for any window size */ tgpf->center[0] = event->mval[0]; tgpf->center[1] = event->mval[1]; + /* Set active frame as current for filling. */ + tgpf->active_cfra = CFRA; + /* render screen to temp image */ if (gp_render_offscreen(tgpf)) { /* apply boundary fill */ gpencil_boundaryfill_area(tgpf); - /* clean borders to avoid infinite loops */ - gpencil_clean_borders(tgpf); + /* Check if detected some border to fill. */ + if (gpencil_check_borders(tgpf)) { - /* analyze outline */ - gpencil_get_outline_points(tgpf); + /* clean borders to avoid infinite loops */ + gpencil_clean_borders(tgpf); - /* create array of points from stack */ - gpencil_points_from_stack(tgpf); + /* analyze outline */ + gpencil_get_outline_points(tgpf); - /* create z-depth array for reproject */ - gpencil_get_depth_array(tgpf); + /* create array of points from stack */ + gpencil_points_from_stack(tgpf); - /* create stroke and reproject */ - gpencil_stroke_from_buffer(tgpf); - } + /* create z-depth array for reproject */ + gpencil_get_depth_array(tgpf); - /* restore size */ - tgpf->region->winx = (short)tgpf->bwinx; - tgpf->region->winy = (short)tgpf->bwiny; - tgpf->region->winrct = tgpf->brect; + /* create stroke and reproject */ + gpencil_stroke_from_buffer(tgpf); + } + else { + BKE_report(op->reports, RPT_ERROR, "Fill canceled. No edges detected"); + } + } /* free temp stack data */ if (tgpf->stack) { BLI_stack_free(tgpf->stack); } + /* Free memory. */ + MEM_SAFE_FREE(tgpf->sbuffer); + MEM_SAFE_FREE(tgpf->depth_arr); + + /* restore size */ + tgpf->region->winx = (short)tgpf->bwinx; + tgpf->region->winy = (short)tgpf->bwiny; + tgpf->region->winrct = tgpf->brect; + /* push undo data */ gpencil_undo_push(tgpf->gpd); diff --git a/source/blender/editors/gpencil/gpencil_intern.h b/source/blender/editors/gpencil/gpencil_intern.h index 077b5b88118..2d36e426835 100644 --- a/source/blender/editors/gpencil/gpencil_intern.h +++ b/source/blender/editors/gpencil/gpencil_intern.h @@ -111,6 +111,8 @@ typedef struct tGPDinterpolate_layer { } tGPDinterpolate_layer; typedef struct tGPDinterpolate { + /** Current depsgraph from context */ + struct Depsgraph *depsgraph; /** current scene from context */ struct Scene *scene; /** area where painting originated */ @@ -138,10 +140,6 @@ typedef struct tGPDinterpolate { int flag; NumInput num; /* numeric input */ - /** handle for drawing strokes while operator is running 3d stuff */ - void *draw_handle_3d; - /** handle for drawing strokes while operator is running screen stuff */ - void *draw_handle_screen; } tGPDinterpolate; /* Temporary primitive operation data */ @@ -155,6 +153,8 @@ typedef struct tGPDprimitive { struct Scene *scene; /** current active gp object */ struct Object *ob; + /** current evaluated gp object */ + struct Object *ob_eval; /** area where painting originated */ struct ScrArea *sa; /** region where painting originated */ @@ -166,7 +166,7 @@ typedef struct tGPDprimitive { /** current GP datablock */ struct bGPdata *gpd; /** current material */ - struct Material *mat; + struct Material *material; /** current brush */ struct Brush *brush; @@ -233,10 +233,6 @@ typedef struct tGPDprimitive { } tGPDprimitive; /* Modal Operator Drawing Callbacks ------------------------ */ - -void ED_gp_draw_interpolation(const struct bContext *C, - struct tGPDinterpolate *tgpi, - const int type); void ED_gp_draw_fill(struct tGPDdraw *tgpw); /* ***************************************************** */ @@ -284,7 +280,6 @@ void gp_point_to_parent_space(const bGPDspoint *pt, const float diff_mat[4][4], */ void gp_apply_parent(struct Depsgraph *depsgraph, struct Object *obact, - bGPdata *gpd, bGPDlayer *gpl, bGPDstroke *gps); /** @@ -292,7 +287,6 @@ void gp_apply_parent(struct Depsgraph *depsgraph, */ void gp_apply_parent_point(struct Depsgraph *depsgraph, struct Object *obact, - bGPdata *gpd, bGPDlayer *gpl, bGPDspoint *pt); @@ -342,7 +336,6 @@ void gp_stroke_delete_tagged_points(bGPDframe *gpf, int gp_delete_selected_point_wrap(bContext *C); void gp_subdivide_stroke(bGPDstroke *gps, const int subdivide); -void gp_randomize_stroke(bGPDstroke *gps, Brush *brush, struct RNG *rng); /* Layers Enums -------------------------------------- */ @@ -367,6 +360,14 @@ void GPENCIL_OT_annotate(struct wmOperatorType *ot); void GPENCIL_OT_draw(struct wmOperatorType *ot); void GPENCIL_OT_fill(struct wmOperatorType *ot); +/* Vertex Paint. */ +void GPENCIL_OT_vertex_paint(struct wmOperatorType *ot); +void GPENCIL_OT_vertex_color_brightness_contrast(struct wmOperatorType *ot); +void GPENCIL_OT_vertex_color_hsv(struct wmOperatorType *ot); +void GPENCIL_OT_vertex_color_invert(struct wmOperatorType *ot); +void GPENCIL_OT_vertex_color_levels(struct wmOperatorType *ot); +void GPENCIL_OT_vertex_color_set(struct wmOperatorType *ot); + /* Guides ----------------------- */ void GPENCIL_OT_guide_rotate(struct wmOperatorType *ot); @@ -390,6 +391,7 @@ void GPENCIL_OT_selectmode_toggle(struct wmOperatorType *ot); void GPENCIL_OT_paintmode_toggle(struct wmOperatorType *ot); void GPENCIL_OT_sculptmode_toggle(struct wmOperatorType *ot); void GPENCIL_OT_weightmode_toggle(struct wmOperatorType *ot); +void GPENCIL_OT_vertexmode_toggle(struct wmOperatorType *ot); void GPENCIL_OT_selection_opacity_toggle(struct wmOperatorType *ot); void GPENCIL_OT_select(struct wmOperatorType *ot); @@ -405,6 +407,7 @@ void GPENCIL_OT_select_less(struct wmOperatorType *ot); void GPENCIL_OT_select_first(struct wmOperatorType *ot); void GPENCIL_OT_select_last(struct wmOperatorType *ot); void GPENCIL_OT_select_alternate(struct wmOperatorType *ot); +void GPENCIL_OT_select_color(struct wmOperatorType *ot); void GPENCIL_OT_duplicate(struct wmOperatorType *ot); void GPENCIL_OT_delete(struct wmOperatorType *ot); @@ -415,16 +418,19 @@ void GPENCIL_OT_extrude(struct wmOperatorType *ot); void GPENCIL_OT_move_to_layer(struct wmOperatorType *ot); void GPENCIL_OT_layer_change(struct wmOperatorType *ot); +void GPENCIL_OT_layer_active(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_reproject(struct wmOperatorType *ot); +void GPENCIL_OT_recalc_geometry(struct wmOperatorType *ot); /* stroke sculpting -- */ void GPENCIL_OT_sculpt_paint(struct wmOperatorType *ot); +void GPENCIL_OT_weight_paint(struct wmOperatorType *ot); /* buttons editing --- */ @@ -440,6 +446,9 @@ void GPENCIL_OT_layer_annotation_move(struct wmOperatorType *ot); void GPENCIL_OT_layer_duplicate(struct wmOperatorType *ot); void GPENCIL_OT_layer_duplicate_object(struct wmOperatorType *ot); +void GPENCIL_OT_layer_mask_add(struct wmOperatorType *ot); +void GPENCIL_OT_layer_mask_remove(struct wmOperatorType *ot); + void GPENCIL_OT_hide(struct wmOperatorType *ot); void GPENCIL_OT_reveal(struct wmOperatorType *ot); @@ -459,6 +468,7 @@ void GPENCIL_OT_frame_clean_fill(struct wmOperatorType *ot); void GPENCIL_OT_frame_clean_loose(struct wmOperatorType *ot); void GPENCIL_OT_convert(struct wmOperatorType *ot); +void GPENCIL_OT_image_to_grease_pencil(struct wmOperatorType *ot); enum { GP_STROKE_JOIN = -1, @@ -498,8 +508,16 @@ void GPENCIL_OT_stroke_merge(struct wmOperatorType *ot); void GPENCIL_OT_stroke_cutter(struct wmOperatorType *ot); void GPENCIL_OT_stroke_trim(struct wmOperatorType *ot); void GPENCIL_OT_stroke_merge_by_distance(struct wmOperatorType *ot); +void GPENCIL_OT_stroke_merge_material(struct wmOperatorType *ot); -void GPENCIL_OT_brush_presets_create(struct wmOperatorType *ot); +void GPENCIL_OT_material_to_vertex_color(struct wmOperatorType *ot); +void GPENCIL_OT_extract_palette_vertex(struct wmOperatorType *ot); + +void GPENCIL_OT_transform_fill(struct wmOperatorType *ot); +void GPENCIL_OT_reset_transform_fill(struct wmOperatorType *ot); + +void GPENCIL_OT_brush_reset(struct wmOperatorType *ot); +void GPENCIL_OT_brush_reset_all(struct wmOperatorType *ot); /* undo stack ---------- */ @@ -617,7 +635,7 @@ struct GP_EditableStrokes_Iter { bGPDframe *init_gpf_ = (is_multiedit_) ? gpl->frames.first : gpl->actframe; \ for (bGPDframe *gpf_ = init_gpf_; gpf_; gpf_ = gpf_->next) { \ if ((gpf_ == gpl->actframe) || ((gpf_->flag & GP_FRAME_SELECT) && is_multiedit_)) { \ - ED_gpencil_parent_location(depsgraph_, obact_, gpd_, gpl, gpstroke_iter.diff_mat); \ + BKE_gpencil_parent_matrix_get(depsgraph_, obact_, gpl, gpstroke_iter.diff_mat); \ invert_m4_m4(gpstroke_iter.inverse_diff_mat, gpstroke_iter.diff_mat); \ /* loop over strokes */ \ bGPDstroke *gpsn_; \ @@ -643,10 +661,6 @@ struct GP_EditableStrokes_Iter { } \ (void)0 -#define GPENCIL_ANY_SCULPT_MASK(flag) \ - ((flag & (GP_SCULPT_MASK_SELECTMODE_POINT | GP_SCULPT_MASK_SELECTMODE_STROKE | \ - GP_SCULPT_MASK_SELECTMODE_SEGMENT))) - /** * Iterate over all editable strokes using evaluated data in the current context, * stopping on each usable layer + stroke pair (i.e. gpl and gps) @@ -662,26 +676,21 @@ struct GP_EditableStrokes_Iter { struct GP_EditableStrokes_Iter gpstroke_iter = {{{0}}}; \ Depsgraph *depsgraph_ = CTX_data_ensure_evaluated_depsgraph(C); \ Object *obact_ = CTX_data_active_object(C); \ - Object *obeval_ = DEG_get_evaluated_object(depsgraph_, obact_); \ - bGPdata *gpd_ = CTX_data_gpencil_data(C); \ + Object *ob_eval_ = (Object *)DEG_get_evaluated_id(depsgraph_, &obact_->id); \ + bGPdata *gpd_ = (bGPdata *)ob_eval_->data; \ const bool is_multiedit_ = (bool)GPENCIL_MULTIEDIT_SESSIONS_ON(gpd_); \ - int idx_eval = 0; \ - for (bGPDlayer *gpl = gpd->layers.first; gpl; gpl = gpl->next) { \ - if (gpencil_layer_is_editable(gpl)) { \ + LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd_->layers) { \ + if (BKE_gpencil_layer_is_editable(gpl)) { \ bGPDframe *init_gpf_ = gpl->actframe; \ if (is_multiedit_) { \ init_gpf_ = gpl->frames.first; \ } \ for (bGPDframe *gpf_ = init_gpf_; gpf_; gpf_ = gpf_->next) { \ if ((gpf_ == gpl->actframe) || ((gpf_->flag & GP_FRAME_SELECT) && is_multiedit_)) { \ - ED_gpencil_parent_location(depsgraph_, obact_, gpd_, gpl, gpstroke_iter.diff_mat); \ + BKE_gpencil_parent_matrix_get(depsgraph_, obact_, gpl, gpstroke_iter.diff_mat); \ invert_m4_m4(gpstroke_iter.inverse_diff_mat, gpstroke_iter.diff_mat); \ - /* get evaluated frame with modifiers applied */ \ - bGPDframe *gpf_eval_ = (!is_multiedit_) ? \ - &obeval_->runtime.gpencil_evaluated_frames[idx_eval] : \ - gpf_; \ /* loop over strokes */ \ - for (bGPDstroke *gps = gpf_eval_->strokes.first; gps; gps = gps->next) { \ + for (bGPDstroke *gps = gpf_->strokes.first; gps; gps = gps->next) { \ /* skip strokes that are invalid for current view */ \ if (ED_gpencil_stroke_can_use(C, gps) == false) \ continue; \ @@ -698,7 +707,6 @@ struct GP_EditableStrokes_Iter { } \ } \ } \ - idx_eval++; \ } \ } \ (void)0 diff --git a/source/blender/editors/gpencil/gpencil_interpolate.c b/source/blender/editors/gpencil/gpencil_interpolate.c index 940fb85f91a..bd00a492035 100644 --- a/source/blender/editors/gpencil/gpencil_interpolate.c +++ b/source/blender/editors/gpencil/gpencil_interpolate.c @@ -115,31 +115,38 @@ static void gp_interpolate_update_points(const bGPDstroke *gps_from, pt->pressure = interpf(prev->pressure, next->pressure, 1.0f - factor); pt->strength = interpf(prev->strength, next->strength, 1.0f - factor); CLAMP(pt->strength, GPENCIL_STRENGTH_MIN, 1.0f); - - /* GPXX interpolate dverts */ -#if 0 - MDeformVert *dvert = &new_stroke->dvert[i]; - dvert->totweight = 0; - dvert->dw = NULL; -#endif } } /* ****************** Interpolate Interactive *********************** */ +/* Helper: free all temp strokes for display. */ +static void gp_interpolate_free_temp_strokes(bGPDframe *gpf) +{ + if (gpf == NULL) { + return; + } + LISTBASE_FOREACH_MUTABLE (bGPDstroke *, gps, &gpf->strokes) { + if (gps->flag & GP_STROKE_TAG) { + BLI_remlink(&gpf->strokes, gps); + BKE_gpencil_free_stroke(gps); + } + } +} /* Helper: Update all strokes interpolated */ static void gp_interpolate_update_strokes(bContext *C, tGPDinterpolate *tgpi) { bGPdata *gpd = tgpi->gpd; - tGPDinterpolate_layer *tgpil; const float shift = tgpi->shift; - for (tgpil = tgpi->ilayers.first; tgpil; tgpil = tgpil->next) { - bGPDstroke *new_stroke; + LISTBASE_FOREACH (tGPDinterpolate_layer *, tgpil, &tgpi->ilayers) { const float factor = tgpil->factor + shift; - for (new_stroke = tgpil->interFrame->strokes.first; new_stroke; - new_stroke = new_stroke->next) { + bGPDframe *gpf = tgpil->gpl->actframe; + /* Free temp strokes. */ + gp_interpolate_free_temp_strokes(gpf); + + LISTBASE_FOREACH (bGPDstroke *, new_stroke, &tgpil->interFrame->strokes) { bGPDstroke *gps_from, *gps_to; int stroke_idx; @@ -156,6 +163,13 @@ static void gp_interpolate_update_strokes(bContext *C, tGPDinterpolate *tgpi) /* update points position */ if ((gps_from) && (gps_to)) { gp_interpolate_update_points(gps_from, gps_to, new_stroke, factor); + + /* Add temp strokes. */ + if (gpf) { + bGPDstroke *gps_eval = BKE_gpencil_stroke_duplicate(new_stroke, true); + gps_eval->flag |= GP_STROKE_TAG; + BLI_addtail(&gpf->strokes, gps_eval); + } } } } @@ -172,19 +186,18 @@ static bool gp_interpolate_check_todo(bContext *C, bGPdata *gpd) eGP_Interpolate_SettingsFlag flag = ts->gp_interpolate.flag; /* get layers */ - for (bGPDlayer *gpl = gpd->layers.first; gpl; gpl = gpl->next) { + LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd->layers) { /* all layers or only active */ if (!(flag & GP_TOOLFLAG_INTERPOLATE_ALL_LAYERS) && !(gpl->flag & GP_LAYER_ACTIVE)) { continue; } /* only editable and visible layers are considered */ - if (!gpencil_layer_is_editable(gpl) || (gpl->actframe == NULL)) { + if (!BKE_gpencil_layer_is_editable(gpl) || (gpl->actframe == NULL)) { continue; } /* read strokes */ - for (bGPDstroke *gps_from = gpl->actframe->strokes.first; gps_from; - gps_from = gps_from->next) { + LISTBASE_FOREACH (bGPDstroke *, gps_from, &gpl->actframe->strokes) { bGPDstroke *gps_to; int fFrame; @@ -232,7 +245,7 @@ static void gp_interpolate_set_points(bContext *C, tGPDinterpolate *tgpi) tgpi->high_limit = 2.0f - tgpi->init_factor; /* set layers */ - for (bGPDlayer *gpl = gpd->layers.first; gpl; gpl = gpl->next) { + LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd->layers) { tGPDinterpolate_layer *tgpil; /* all layers or only active */ @@ -240,7 +253,7 @@ static void gp_interpolate_set_points(bContext *C, tGPDinterpolate *tgpi) continue; } /* only editable and visible layers are considered */ - if (!gpencil_layer_is_editable(gpl) || (gpl->actframe == NULL)) { + if (!BKE_gpencil_layer_is_editable(gpl) || (gpl->actframe == NULL)) { continue; } @@ -262,8 +275,7 @@ static void gp_interpolate_set_points(bContext *C, tGPDinterpolate *tgpi) (tgpil->nextFrame->framenum - tgpil->prevFrame->framenum + 1); /* create new strokes data with interpolated points reading original stroke */ - for (bGPDstroke *gps_from = tgpil->prevFrame->strokes.first; gps_from; - gps_from = gps_from->next) { + LISTBASE_FOREACH (bGPDstroke *, gps_from, &tgpil->prevFrame->strokes) { bGPDstroke *gps_to; int fFrame; @@ -293,7 +305,7 @@ static void gp_interpolate_set_points(bContext *C, tGPDinterpolate *tgpi) } /* create new stroke */ - new_stroke = BKE_gpencil_stroke_duplicate(gps_from); + new_stroke = BKE_gpencil_stroke_duplicate(gps_from, true); if (valid) { /* if destination stroke is smaller, resize new_stroke to size of gps_to stroke */ @@ -305,8 +317,6 @@ static void gp_interpolate_set_points(bContext *C, tGPDinterpolate *tgpi) sizeof(*new_stroke->dvert) * gps_to->totpoints); } new_stroke->totpoints = gps_to->totpoints; - new_stroke->tot_triangles = 0; - new_stroke->flag |= GP_STROKE_RECALC_GEOMETRY; } /* update points position */ gp_interpolate_update_points(gps_from, gps_to, new_stroke, tgpil->factor); @@ -318,37 +328,16 @@ static void gp_interpolate_set_points(bContext *C, tGPDinterpolate *tgpi) if (new_stroke->dvert != NULL) { new_stroke->dvert = MEM_recallocN(new_stroke->dvert, sizeof(*new_stroke->dvert)); } - new_stroke->tot_triangles = 0; - new_stroke->triangles = MEM_recallocN(new_stroke->triangles, - sizeof(*new_stroke->triangles)); - new_stroke->flag |= GP_STROKE_RECALC_GEOMETRY; } + /* Calc geometry data. */ + BKE_gpencil_stroke_geometry_update(new_stroke); /* add to strokes */ BLI_addtail(&tgpil->interFrame->strokes, new_stroke); } } } -/* ----------------------- */ -/* Drawing Callbacks */ - -/* Drawing callback for modal operator in screen mode */ -static void gpencil_interpolate_draw_screen(const struct bContext *C, - ARegion *UNUSED(region), - void *arg) -{ - tGPDinterpolate *tgpi = (tGPDinterpolate *)arg; - ED_gp_draw_interpolation(C, tgpi, REGION_DRAW_POST_PIXEL); -} - -/* Drawing callback for modal operator in 3d mode */ -static void gpencil_interpolate_draw_3d(const bContext *C, ARegion *UNUSED(region), void *arg) -{ - tGPDinterpolate *tgpi = (tGPDinterpolate *)arg; - ED_gp_draw_interpolation(C, tgpi, REGION_DRAW_POST_VIEW); -} - /* ----------------------- */ /* Helper: calculate shift based on position of mouse (we only use x-axis for now. @@ -415,25 +404,23 @@ static void gpencil_interpolate_update(bContext *C, wmOperator *op, tGPDinterpol static void gpencil_interpolate_exit(bContext *C, wmOperator *op) { tGPDinterpolate *tgpi = op->customdata; - tGPDinterpolate_layer *tgpil; bGPdata *gpd = tgpi->gpd; /* don't assume that operator data exists at all */ if (tgpi) { - /* remove drawing handler */ - if (tgpi->draw_handle_screen) { - ED_region_draw_cb_exit(tgpi->region->type, tgpi->draw_handle_screen); - } - if (tgpi->draw_handle_3d) { - ED_region_draw_cb_exit(tgpi->region->type, tgpi->draw_handle_3d); - } - /* clear status message area */ ED_area_status_text(tgpi->sa, NULL); ED_workspace_status_text(C, NULL); + /* Clear any temp stroke. */ + LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd->layers) { + LISTBASE_FOREACH (bGPDframe *, gpf, &gpl->frames) { + gp_interpolate_free_temp_strokes(gpf); + } + } + /* finally, free memory used by temp data */ - for (tgpil = tgpi->ilayers.first; tgpil; tgpil = tgpil->next) { + LISTBASE_FOREACH (tGPDinterpolate_layer *, tgpil, &tgpi->ilayers) { BKE_gpencil_free_strokes(tgpil->interFrame); MEM_freeN(tgpil->interFrame); } @@ -455,6 +442,7 @@ static bool gp_interpolate_set_init_values(bContext *C, wmOperator *op, tGPDinte bGPdata *gpd = CTX_data_gpencil_data(C); /* set current scene and window */ + tgpi->depsgraph = CTX_data_ensure_evaluated_depsgraph(C); tgpi->scene = CTX_data_scene(C); tgpi->sa = CTX_wm_area(C); tgpi->region = CTX_wm_region(C); @@ -550,15 +538,6 @@ static int gpencil_interpolate_invoke(bContext *C, wmOperator *op, const wmEvent tgpi = op->customdata; } - /* Enable custom drawing handlers - * It needs 2 handlers because strokes can in 3d space and screen space - * and each handler use different coord system - */ - tgpi->draw_handle_screen = ED_region_draw_cb_activate( - tgpi->region->type, gpencil_interpolate_draw_screen, tgpi, REGION_DRAW_POST_PIXEL); - tgpi->draw_handle_3d = ED_region_draw_cb_activate( - tgpi->region->type, gpencil_interpolate_draw_3d, tgpi, REGION_DRAW_POST_VIEW); - /* set cursor to indicate modal */ WM_cursor_modal_set(win, WM_CURSOR_EW_SCROLL); @@ -579,8 +558,7 @@ static int gpencil_interpolate_modal(bContext *C, wmOperator *op, const wmEvent tGPDinterpolate *tgpi = op->customdata; wmWindow *win = CTX_wm_window(C); bGPDframe *gpf_dst; - bGPDstroke *gps_src, *gps_dst; - tGPDinterpolate_layer *tgpil; + bGPDstroke *gps_dst; const bool has_numinput = hasNumInput(&tgpi->num); switch (event->type) { @@ -593,26 +571,22 @@ static int gpencil_interpolate_modal(bContext *C, wmOperator *op, const wmEvent WM_cursor_modal_restore(win); /* insert keyframes as required... */ - for (tgpil = tgpi->ilayers.first; tgpil; tgpil = tgpil->next) { - gpf_dst = BKE_gpencil_layer_getframe(tgpil->gpl, tgpi->cframe, GP_GETFRAME_ADD_NEW); + LISTBASE_FOREACH (tGPDinterpolate_layer *, tgpil, &tgpi->ilayers) { + gpf_dst = BKE_gpencil_layer_frame_get(tgpil->gpl, tgpi->cframe, GP_GETFRAME_ADD_NEW); gpf_dst->key_type = BEZT_KEYTYPE_BREAKDOWN; /* copy strokes */ BLI_listbase_clear(&gpf_dst->strokes); - for (gps_src = tgpil->interFrame->strokes.first; gps_src; gps_src = gps_src->next) { + LISTBASE_FOREACH (bGPDstroke *, gps_src, &tgpil->interFrame->strokes) { if (gps_src->totpoints == 0) { continue; } /* make copy of source stroke, then adjust pointer to points too */ - gps_dst = MEM_dupallocN(gps_src); - gps_dst->points = MEM_dupallocN(gps_src->points); - if (gps_src->dvert != NULL) { - gps_dst->dvert = MEM_dupallocN(gps_src->dvert); - BKE_gpencil_stroke_weights_duplicate(gps_src, gps_dst); - } - gps_dst->triangles = MEM_dupallocN(gps_src->triangles); - gps_dst->flag |= GP_STROKE_RECALC_GEOMETRY; + gps_dst = BKE_gpencil_stroke_duplicate(gps_src, true); + /* Calc geometry data. */ + BKE_gpencil_stroke_geometry_update(gps_dst); + BLI_addtail(&gpf_dst->strokes, gps_dst); } } @@ -973,7 +947,7 @@ static int gpencil_interpolate_seq_exec(bContext *C, wmOperator *op) } /* loop all layer to check if need interpolation */ - for (bGPDlayer *gpl = gpd->layers.first; gpl; gpl = gpl->next) { + LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd->layers) { bGPDframe *prevFrame, *nextFrame; bGPDstroke *gps_from, *gps_to; int cframe, fFrame; @@ -983,7 +957,7 @@ static int gpencil_interpolate_seq_exec(bContext *C, wmOperator *op) continue; } /* only editable and visible layers are considered */ - if (!gpencil_layer_is_editable(gpl) || (gpl->actframe == NULL)) { + if (!BKE_gpencil_layer_is_editable(gpl) || (gpl->actframe == NULL)) { continue; } @@ -1017,7 +991,6 @@ static int gpencil_interpolate_seq_exec(bContext *C, wmOperator *op) /* create new strokes data with interpolated points reading original stroke */ for (gps_from = prevFrame->strokes.first; gps_from; gps_from = gps_from->next) { - bGPDstroke *new_stroke = NULL; /* only selected */ if ((flag & GP_TOOLFLAG_INTERPOLATE_ONLY_SELECTED) && @@ -1042,12 +1015,12 @@ static int gpencil_interpolate_seq_exec(bContext *C, wmOperator *op) /* create a new frame if needed */ if (interFrame == NULL) { - interFrame = BKE_gpencil_layer_getframe(gpl, cframe, GP_GETFRAME_ADD_NEW); + interFrame = BKE_gpencil_layer_frame_get(gpl, cframe, GP_GETFRAME_ADD_NEW); interFrame->key_type = BEZT_KEYTYPE_BREAKDOWN; } /* create new stroke */ - new_stroke = BKE_gpencil_stroke_duplicate(gps_from); + bGPDstroke *new_stroke = BKE_gpencil_stroke_duplicate(gps_from, true); /* if destination stroke is smaller, resize new_stroke to size of gps_to stroke */ if (gps_from->totpoints > gps_to->totpoints) { @@ -1064,14 +1037,16 @@ static int gpencil_interpolate_seq_exec(bContext *C, wmOperator *op) new_stroke->dvert = MEM_recallocN(new_stroke->dvert, sizeof(*new_stroke->dvert) * gps_to->totpoints); } + new_stroke->totpoints = gps_to->totpoints; - new_stroke->tot_triangles = 0; - new_stroke->flag |= GP_STROKE_RECALC_GEOMETRY; } /* update points position */ gp_interpolate_update_points(gps_from, gps_to, new_stroke, factor); + /* Calc geometry data. */ + BKE_gpencil_stroke_geometry_update(new_stroke); + /* add to strokes */ BLI_addtail(&interFrame->strokes, new_stroke); } diff --git a/source/blender/editors/gpencil/gpencil_merge.c b/source/blender/editors/gpencil/gpencil_merge.c index 91339709162..9a7ad8d7220 100644 --- a/source/blender/editors/gpencil/gpencil_merge.c +++ b/source/blender/editors/gpencil/gpencil_merge.c @@ -37,6 +37,7 @@ #include "BKE_gpencil.h" #include "BKE_main.h" #include "BKE_material.h" +#include "BKE_report.h" #include "WM_api.h" #include "WM_types.h" @@ -60,6 +61,7 @@ typedef struct tGPencilPointCache { float x, y, z; float pressure; float strength; + float vert_color[4]; } tGPencilPointCache; /* helper function to sort points */ @@ -93,6 +95,7 @@ static void gpencil_insert_points_to_stroke(bGPDstroke *gps, pt_dst->uv_fac = 1.0f; pt_dst->uv_rot = 0; pt_dst->flag |= GP_SPOINT_SELECT; + copy_v4_v4(pt_dst->vert_color, point_elem->vert_color); } } @@ -113,7 +116,7 @@ static bGPDstroke *gpencil_prepare_stroke(bContext *C, wmOperator *op, int totpo /* if not exist, create a new one */ if ((paint->brush == NULL) || (paint->brush->gpencil_settings == NULL)) { /* create new brushes */ - BKE_brush_gpencil_presets(bmain, ts); + BKE_brush_gpencil_paint_presets(bmain, ts); } Brush *brush = paint->brush; @@ -125,25 +128,21 @@ static bGPDstroke *gpencil_prepare_stroke(bContext *C, wmOperator *op, int totpo else { add_frame_mode = GP_GETFRAME_ADD_NEW; } - bGPDframe *gpf = BKE_gpencil_layer_getframe(gpl, CFRA, add_frame_mode); + bGPDframe *gpf = BKE_gpencil_layer_frame_get(gpl, CFRA, add_frame_mode); /* stroke */ bGPDstroke *gps = MEM_callocN(sizeof(bGPDstroke), "gp_stroke"); gps->totpoints = totpoints; gps->inittime = 0.0f; gps->thickness = brush->size; - gps->gradient_f = brush->gpencil_settings->gradient_f; - copy_v2_v2(gps->gradient_s, brush->gpencil_settings->gradient_s); + gps->hardeness = brush->gpencil_settings->hardeness; + copy_v2_v2(gps->aspect_ratio, brush->gpencil_settings->aspect_ratio); gps->flag |= GP_STROKE_SELECT; gps->flag |= GP_STROKE_3DSPACE; gps->mat_nr = ob->actcol - 1; /* allocate memory for points */ gps->points = MEM_callocN(sizeof(bGPDspoint) * totpoints, "gp_stroke_points"); - /* initialize triangle memory to dummy data */ - gps->tot_triangles = 0; - gps->triangles = NULL; - gps->flag |= GP_STROKE_RECALC_GEOMETRY; if (cyclic) { gps->flag |= GP_STROKE_CYCLIC; @@ -181,17 +180,14 @@ static void gpencil_get_elements_len(bContext *C, int *totstrokes, int *totpoint static void gpencil_dissolve_points(bContext *C) { - bGPDstroke *gps, *gpsn; - CTX_DATA_BEGIN (C, bGPDlayer *, gpl, editable_gpencil_layers) { bGPDframe *gpf = gpl->actframe; if (gpf == NULL) { continue; } - for (gps = gpf->strokes.first; gps; gps = gpsn) { - gpsn = gps->next; - gp_stroke_delete_tagged_points(gpf, gps, gpsn, GP_SPOINT_TAG, false, 0); + LISTBASE_FOREACH_MUTABLE (bGPDstroke *, gps, &gpf->strokes) { + gp_stroke_delete_tagged_points(gpf, gps, gps->next, GP_SPOINT_TAG, false, 0); } } CTX_DATA_END; @@ -224,7 +220,7 @@ static void gpencil_calc_points_factor(bContext *C, if (gpf == NULL) { continue; } - for (bGPDstroke *gps = gpf->strokes.first; gps; gps = gps->next) { + LISTBASE_FOREACH (bGPDstroke *, gps, &gpf->strokes) { if (gps->flag & GP_STROKE_SELECT) { for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) { if (clear_stroke) { @@ -239,6 +235,7 @@ static void gpencil_calc_points_factor(bContext *C, copy_v3_v3(&pt2->x, &pt->x); pt2->pressure = pt->pressure; pt2->strength = pt->strength; + copy_v4_v4(pt2->vert_color, pt->vert_color); pt->flag &= ~GP_SPOINT_SELECT; if (clear_point) { pt->flag |= GP_SPOINT_TAG; @@ -288,6 +285,7 @@ static void gpencil_calc_points_factor(bContext *C, copy_v3_v3(&sort_pt->x, &pt2->x); sort_pt->pressure = pt2->pressure; sort_pt->strength = pt2->strength; + copy_v4_v4(sort_pt->vert_color, pt2->vert_color); sort_pt->gps = gps_array[i]; @@ -336,6 +334,7 @@ static int gpencil_insert_to_array(tGPencilPointCache *src_array, dst_elem->pressure = src_elem->pressure; dst_elem->strength = src_elem->strength; dst_elem->factor = src_elem->factor; + copy_v4_v4(dst_elem->vert_color, src_elem->vert_color); } return last; @@ -458,7 +457,7 @@ static bool gp_strokes_merge_poll(bContext *C) /* check hidden or locked materials */ MaterialGPencilStyle *gp_style = ma->gp_style; - if ((gp_style->flag & GP_STYLE_COLOR_HIDE) || (gp_style->flag & GP_STYLE_COLOR_LOCKED)) { + if ((gp_style->flag & GP_MATERIAL_HIDE) || (gp_style->flag & GP_MATERIAL_LOCKED)) { return false; } @@ -569,3 +568,100 @@ void GPENCIL_OT_stroke_merge(wmOperatorType *ot) RNA_def_boolean(ot->srna, "clear_point", 0, "Dissolve Points", "Dissolve old selected points"); RNA_def_boolean(ot->srna, "clear_stroke", 0, "Delete Strokes", "Delete old selected strokes"); } + +/* Merge similar materials. */ +static bool gp_stroke_merge_material_poll(bContext *C) +{ + /* only supported with grease pencil objects */ + Object *ob = CTX_data_active_object(C); + if ((ob == NULL) || (ob->type != OB_GPENCIL)) { + return false; + } + + return true; +} + +static int gp_stroke_merge_material_exec(bContext *C, wmOperator *op) +{ + Object *ob = CTX_data_active_object(C); + bGPdata *gpd = (bGPdata *)ob->data; + const float hue_threshold = RNA_float_get(op->ptr, "hue_threshold"); + const float sat_threshold = RNA_float_get(op->ptr, "sat_threshold"); + const float val_threshold = RNA_float_get(op->ptr, "val_threshold"); + + /* Review materials. */ + GHash *mat_table = BLI_ghash_int_new(__func__); + + short *totcol = BKE_object_material_len_p(ob); + if (totcol == 0) { + return OPERATOR_CANCELLED; + } + + bool changed = BKE_gpencil_merge_materials_table_get( + ob, hue_threshold, sat_threshold, val_threshold, mat_table); + + int removed = BLI_ghash_len(mat_table); + + /* Update stroke material index. */ + if (changed) { + CTX_DATA_BEGIN (C, bGPDlayer *, gpl, editable_gpencil_layers) { + LISTBASE_FOREACH (bGPDframe *, gpf, &gpl->frames) { + LISTBASE_FOREACH (bGPDstroke *, gps, &gpf->strokes) { + if (ED_gpencil_stroke_can_use(C, gps) == false) { + continue; + } + if (ED_gpencil_stroke_color_use(ob, gpl, gps) == false) { + continue; + } + + if (BLI_ghash_haskey(mat_table, POINTER_FROM_INT(gps->mat_nr))) { + int *idx = BLI_ghash_lookup(mat_table, POINTER_FROM_INT(gps->mat_nr)); + gps->mat_nr = POINTER_AS_INT(idx); + } + } + } + } + CTX_DATA_END; + } + + /* Free hash memory. */ + BLI_ghash_free(mat_table, NULL, NULL); + + /* notifiers */ + if (changed) { + BKE_reportf(op->reports, RPT_INFO, "Merged %d materiales of %d", removed, *totcol); + DEG_id_tag_update(&gpd->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY); + WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL); + } + else { + BKE_report(op->reports, RPT_INFO, "Nothing to merge"); + } + return OPERATOR_FINISHED; +} + +void GPENCIL_OT_stroke_merge_material(wmOperatorType *ot) +{ + PropertyRNA *prop; + + /* identifiers */ + ot->name = "Merge Grease Pencil Materials"; + ot->idname = "GPENCIL_OT_stroke_merge_material"; + ot->description = "Replace materials in strokes merging similar"; + + /* api callbacks */ + ot->exec = gp_stroke_merge_material_exec; + ot->poll = gp_stroke_merge_material_poll; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + /* properties */ + prop = RNA_def_float( + ot->srna, "hue_threshold", 0.001f, 0.0f, 1.0f, "Hue Threshold", "", 0.0f, 1.0f); + prop = RNA_def_float( + ot->srna, "sat_threshold", 0.001f, 0.0f, 1.0f, "Saturation Threshold", "", 0.0f, 1.0f); + prop = RNA_def_float( + ot->srna, "val_threshold", 0.001f, 0.0f, 1.0f, "Value Threshold", "", 0.0f, 1.0f); + /* avoid re-using last var */ + RNA_def_property_flag(prop, PROP_SKIP_SAVE); +} diff --git a/source/blender/editors/gpencil/gpencil_ops.c b/source/blender/editors/gpencil/gpencil_ops.c index 7a541cd8f03..275075c38db 100644 --- a/source/blender/editors/gpencil/gpencil_ops.c +++ b/source/blender/editors/gpencil/gpencil_ops.c @@ -95,6 +95,60 @@ static bool gp_stroke_paintmode_poll_with_tool(bContext *C, const char gpencil_t WM_toolsystem_active_tool_is_brush(C) && (brush->gpencil_tool == gpencil_tool)); } +static bool gp_stroke_vertexmode_poll_with_tool(bContext *C, const char gpencil_vertex_tool) +{ + bGPdata *gpd = CTX_data_gpencil_data(C); + if (!gpd) { + return false; + } + + ToolSettings *ts = CTX_data_tool_settings(C); + if (!ts || !ts->gp_vertexpaint) { + return false; + } + + Brush *brush = BKE_paint_brush(&ts->gp_vertexpaint->paint); + return ((gpd->flag & GP_DATA_STROKE_VERTEXMODE) && (brush && brush->gpencil_settings) && + WM_toolsystem_active_tool_is_brush(C) && + (brush->gpencil_vertex_tool == gpencil_vertex_tool)); +} + +static bool gp_stroke_sculptmode_poll_with_tool(bContext *C, const char gpencil_sculpt_tool) +{ + bGPdata *gpd = CTX_data_gpencil_data(C); + if (!gpd) { + return false; + } + + ToolSettings *ts = CTX_data_tool_settings(C); + if (!ts || !ts->gp_sculptpaint) { + return false; + } + + Brush *brush = BKE_paint_brush(&ts->gp_sculptpaint->paint); + return ((gpd->flag & GP_DATA_STROKE_SCULPTMODE) && (brush && brush->gpencil_settings) && + WM_toolsystem_active_tool_is_brush(C) && + (brush->gpencil_sculpt_tool == gpencil_sculpt_tool)); +} + +static bool gp_stroke_weightmode_poll_with_tool(bContext *C, const char gpencil_weight_tool) +{ + bGPdata *gpd = CTX_data_gpencil_data(C); + if (!gpd) { + return false; + } + + ToolSettings *ts = CTX_data_tool_settings(C); + if (!ts || !ts->gp_weightpaint) { + return false; + } + + Brush *brush = BKE_paint_brush(&ts->gp_weightpaint->paint); + return ((gpd->flag & GP_DATA_STROKE_WEIGHTMODE) && (brush && brush->gpencil_settings) && + WM_toolsystem_active_tool_is_brush(C) && + (brush->gpencil_weight_tool == gpencil_weight_tool)); +} + /* Poll callback for stroke painting (draw brush) */ static bool gp_stroke_paintmode_draw_poll(bContext *C) { @@ -113,6 +167,12 @@ static bool gp_stroke_paintmode_fill_poll(bContext *C) return gp_stroke_paintmode_poll_with_tool(C, GPAINT_TOOL_FILL); } +/* Poll callback for stroke painting (tint) */ +static bool gp_stroke_paintmode_tint_poll(bContext *C) +{ + return gp_stroke_paintmode_poll_with_tool(C, GPAINT_TOOL_TINT); +} + /* Poll callback for stroke sculpting mode */ static bool gp_stroke_sculptmode_poll(bContext *C) { @@ -125,9 +185,8 @@ static bool gp_stroke_sculptmode_poll(bContext *C) return ((gpd) && (gpd->flag & GP_DATA_STROKE_EDITMODE)); } else { - /* weight paint is a submode of sculpt */ if ((ob) && (ob->type == OB_GPENCIL)) { - return GPENCIL_SCULPT_OR_WEIGHT_MODE(gpd); + return GPENCIL_SCULPT_MODE(gpd); } } @@ -141,12 +200,113 @@ static bool gp_stroke_weightmode_poll(bContext *C) Object *ob = CTX_data_active_object(C); if ((ob) && (ob->type == OB_GPENCIL)) { - return (gpd && (gpd->flag & GP_DATA_STROKE_WEIGHTMODE)); + return GPENCIL_WEIGHT_MODE(gpd); } return 0; } +/* Poll callback for stroke vertex paint mode */ +static bool gp_stroke_vertexmode_poll(bContext *C) +{ + bGPdata *gpd = CTX_data_gpencil_data(C); + Object *ob = CTX_data_active_object(C); + + if ((ob) && (ob->type == OB_GPENCIL)) { + return (gpd && (gpd->flag & GP_DATA_STROKE_VERTEXMODE)); + } + + return 0; +} + +/* Poll callback for vertex painting (draw) */ +static bool gp_stroke_vertexmode_draw_poll(bContext *C) +{ + return gp_stroke_vertexmode_poll_with_tool(C, GPVERTEX_TOOL_DRAW); +} + +/* Poll callback for vertex painting (blur) */ +static bool gp_stroke_vertexmode_blur_poll(bContext *C) +{ + return gp_stroke_vertexmode_poll_with_tool(C, GPVERTEX_TOOL_BLUR); +} + +/* Poll callback for vertex painting (average) */ +static bool gp_stroke_vertexmode_average_poll(bContext *C) +{ + return gp_stroke_vertexmode_poll_with_tool(C, GPVERTEX_TOOL_AVERAGE); +} + +/* Poll callback for vertex painting (smear) */ +static bool gp_stroke_vertexmode_smear_poll(bContext *C) +{ + return gp_stroke_vertexmode_poll_with_tool(C, GPVERTEX_TOOL_SMEAR); +} + +/* Poll callback for vertex painting (replace) */ +static bool gp_stroke_vertexmode_replace_poll(bContext *C) +{ + return gp_stroke_vertexmode_poll_with_tool(C, GPVERTEX_TOOL_REPLACE); +} + +/* Poll callback for sculpt (Smooth) */ +static bool gp_stroke_sculptmode_smooth_poll(bContext *C) +{ + return gp_stroke_sculptmode_poll_with_tool(C, GPSCULPT_TOOL_SMOOTH); +} +/* Poll callback for sculpt (Thickness) */ +static bool gp_stroke_sculptmode_thickness_poll(bContext *C) +{ + return gp_stroke_sculptmode_poll_with_tool(C, GPSCULPT_TOOL_THICKNESS); +} + +/* Poll callback for sculpt (Strength) */ +static bool gp_stroke_sculptmode_strength_poll(bContext *C) +{ + return gp_stroke_sculptmode_poll_with_tool(C, GPSCULPT_TOOL_STRENGTH); +} + +/* Poll callback for sculpt (Grab) */ +static bool gp_stroke_sculptmode_grab_poll(bContext *C) +{ + return gp_stroke_sculptmode_poll_with_tool(C, GPSCULPT_TOOL_GRAB); +} + +/* Poll callback for sculpt (Push) */ +static bool gp_stroke_sculptmode_push_poll(bContext *C) +{ + return gp_stroke_sculptmode_poll_with_tool(C, GPSCULPT_TOOL_PUSH); +} + +/* Poll callback for sculpt (Twist) */ +static bool gp_stroke_sculptmode_twist_poll(bContext *C) +{ + return gp_stroke_sculptmode_poll_with_tool(C, GPSCULPT_TOOL_TWIST); +} + +/* Poll callback for sculpt (Pinch) */ +static bool gp_stroke_sculptmode_pinch_poll(bContext *C) +{ + return gp_stroke_sculptmode_poll_with_tool(C, GPSCULPT_TOOL_PINCH); +} +/* Poll callback for sculpt (Randomize) */ +static bool gp_stroke_sculptmode_randomize_poll(bContext *C) +{ + return gp_stroke_sculptmode_poll_with_tool(C, GPSCULPT_TOOL_RANDOMIZE); +} + +/* Poll callback for sculpt (Clone) */ +static bool gp_stroke_sculptmode_clone_poll(bContext *C) +{ + return gp_stroke_sculptmode_poll_with_tool(C, GPSCULPT_TOOL_CLONE); +} + +/* Poll callback for weight paint (Draw) */ +static bool gp_stroke_weightmode_draw_poll(bContext *C) +{ + return gp_stroke_weightmode_poll_with_tool(C, GPWEIGHT_TOOL_DRAW); +} + /* Stroke Editing Keymap - Only when editmode is enabled */ static void ed_keymap_gpencil_editing(wmKeyConfig *keyconf) { @@ -177,6 +337,13 @@ static void ed_keymap_gpencil_painting_fill(wmKeyConfig *keyconf) keymap->poll = gp_stroke_paintmode_fill_poll; } +/* keys for draw with a tint brush */ +static void ed_keymap_gpencil_painting_tint(wmKeyConfig *keyconf) +{ + wmKeyMap *keymap = WM_keymap_ensure(keyconf, "Grease Pencil Stroke Paint (Tint)", 0, 0); + keymap->poll = gp_stroke_paintmode_tint_poll; +} + /* Stroke Painting Keymap - Only when paintmode is enabled */ static void ed_keymap_gpencil_painting(wmKeyConfig *keyconf) { @@ -200,6 +367,106 @@ static void ed_keymap_gpencil_weightpainting(wmKeyConfig *keyconf) wmKeyMap *keymap = WM_keymap_ensure(keyconf, "Grease Pencil Stroke Weight Mode", 0, 0); keymap->poll = gp_stroke_weightmode_poll; } + +static void ed_keymap_gpencil_vertexpainting(wmKeyConfig *keyconf) +{ + /* set poll callback - so that this keymap only gets enabled when stroke vertex is enabled */ + wmKeyMap *keymap = WM_keymap_ensure(keyconf, "Grease Pencil Stroke Vertex Mode", 0, 0); + keymap->poll = gp_stroke_vertexmode_poll; +} + +/* keys for vertex with a draw brush */ +static void ed_keymap_gpencil_vertexpainting_draw(wmKeyConfig *keyconf) +{ + wmKeyMap *keymap = WM_keymap_ensure(keyconf, "Grease Pencil Stroke Vertex (Draw)", 0, 0); + keymap->poll = gp_stroke_vertexmode_draw_poll; +} + +/* keys for vertex with a blur brush */ +static void ed_keymap_gpencil_vertexpainting_blur(wmKeyConfig *keyconf) +{ + wmKeyMap *keymap = WM_keymap_ensure(keyconf, "Grease Pencil Stroke Vertex (Blur)", 0, 0); + keymap->poll = gp_stroke_vertexmode_blur_poll; +} +/* keys for vertex with a average brush */ +static void ed_keymap_gpencil_vertexpainting_average(wmKeyConfig *keyconf) +{ + wmKeyMap *keymap = WM_keymap_ensure(keyconf, "Grease Pencil Stroke Vertex (Average)", 0, 0); + keymap->poll = gp_stroke_vertexmode_average_poll; +} +/* keys for vertex with a smear brush */ +static void ed_keymap_gpencil_vertexpainting_smear(wmKeyConfig *keyconf) +{ + wmKeyMap *keymap = WM_keymap_ensure(keyconf, "Grease Pencil Stroke Vertex (Smear)", 0, 0); + keymap->poll = gp_stroke_vertexmode_smear_poll; +} +/* keys for vertex with a replace brush */ +static void ed_keymap_gpencil_vertexpainting_replace(wmKeyConfig *keyconf) +{ + wmKeyMap *keymap = WM_keymap_ensure(keyconf, "Grease Pencil Stroke Vertex (Replace)", 0, 0); + keymap->poll = gp_stroke_vertexmode_replace_poll; +} +/* keys for sculpt with a smooth brush */ +static void ed_keymap_gpencil_sculptpainting_smooth(wmKeyConfig *keyconf) +{ + wmKeyMap *keymap = WM_keymap_ensure(keyconf, "Grease Pencil Stroke Sculpt (Smooth)", 0, 0); + keymap->poll = gp_stroke_sculptmode_smooth_poll; +} +/* keys for sculpt with a thickness brush */ +static void ed_keymap_gpencil_sculptpainting_thickness(wmKeyConfig *keyconf) +{ + wmKeyMap *keymap = WM_keymap_ensure(keyconf, "Grease Pencil Stroke Sculpt (Thickness)", 0, 0); + keymap->poll = gp_stroke_sculptmode_thickness_poll; +} +/* keys for sculpt with a strength brush */ +static void ed_keymap_gpencil_sculptpainting_strength(wmKeyConfig *keyconf) +{ + wmKeyMap *keymap = WM_keymap_ensure(keyconf, "Grease Pencil Stroke Sculpt (Strength)", 0, 0); + keymap->poll = gp_stroke_sculptmode_strength_poll; +} +/* keys for sculpt with a grab brush */ +static void ed_keymap_gpencil_sculptpainting_grab(wmKeyConfig *keyconf) +{ + wmKeyMap *keymap = WM_keymap_ensure(keyconf, "Grease Pencil Stroke Sculpt (Grab)", 0, 0); + keymap->poll = gp_stroke_sculptmode_grab_poll; +} +/* keys for sculpt with a push brush */ +static void ed_keymap_gpencil_sculptpainting_push(wmKeyConfig *keyconf) +{ + wmKeyMap *keymap = WM_keymap_ensure(keyconf, "Grease Pencil Stroke Sculpt (Push)", 0, 0); + keymap->poll = gp_stroke_sculptmode_push_poll; +} +/* keys for sculpt with a twist brush */ +static void ed_keymap_gpencil_sculptpainting_twist(wmKeyConfig *keyconf) +{ + wmKeyMap *keymap = WM_keymap_ensure(keyconf, "Grease Pencil Stroke Sculpt (Twist)", 0, 0); + keymap->poll = gp_stroke_sculptmode_twist_poll; +} +/* keys for sculpt with a pinch brush */ +static void ed_keymap_gpencil_sculptpainting_pinch(wmKeyConfig *keyconf) +{ + wmKeyMap *keymap = WM_keymap_ensure(keyconf, "Grease Pencil Stroke Sculpt (Pinch)", 0, 0); + keymap->poll = gp_stroke_sculptmode_pinch_poll; +} +/* keys for sculpt with a randomize brush */ +static void ed_keymap_gpencil_sculptpainting_randomize(wmKeyConfig *keyconf) +{ + wmKeyMap *keymap = WM_keymap_ensure(keyconf, "Grease Pencil Stroke Sculpt (Randomize)", 0, 0); + keymap->poll = gp_stroke_sculptmode_randomize_poll; +} +/* keys for sculpt with a clone brush */ +static void ed_keymap_gpencil_sculptpainting_clone(wmKeyConfig *keyconf) +{ + wmKeyMap *keymap = WM_keymap_ensure(keyconf, "Grease Pencil Stroke Sculpt (Clone)", 0, 0); + keymap->poll = gp_stroke_sculptmode_clone_poll; +} +/* keys for weight with a draw brush */ +static void ed_keymap_gpencil_weightpainting_draw(wmKeyConfig *keyconf) +{ + wmKeyMap *keymap = WM_keymap_ensure(keyconf, "Grease Pencil Stroke Weight (Draw)", 0, 0); + keymap->poll = gp_stroke_weightmode_draw_poll; +} + /* ==================== */ void ED_keymap_gpencil(wmKeyConfig *keyconf) @@ -210,8 +477,25 @@ void ED_keymap_gpencil(wmKeyConfig *keyconf) ed_keymap_gpencil_painting_draw(keyconf); ed_keymap_gpencil_painting_erase(keyconf); ed_keymap_gpencil_painting_fill(keyconf); + ed_keymap_gpencil_painting_tint(keyconf); ed_keymap_gpencil_sculpting(keyconf); + ed_keymap_gpencil_sculptpainting_smooth(keyconf); + ed_keymap_gpencil_sculptpainting_thickness(keyconf); + ed_keymap_gpencil_sculptpainting_strength(keyconf); + ed_keymap_gpencil_sculptpainting_grab(keyconf); + ed_keymap_gpencil_sculptpainting_push(keyconf); + ed_keymap_gpencil_sculptpainting_twist(keyconf); + ed_keymap_gpencil_sculptpainting_pinch(keyconf); + ed_keymap_gpencil_sculptpainting_randomize(keyconf); + ed_keymap_gpencil_sculptpainting_clone(keyconf); ed_keymap_gpencil_weightpainting(keyconf); + ed_keymap_gpencil_weightpainting_draw(keyconf); + ed_keymap_gpencil_vertexpainting(keyconf); + ed_keymap_gpencil_vertexpainting_draw(keyconf); + ed_keymap_gpencil_vertexpainting_blur(keyconf); + ed_keymap_gpencil_vertexpainting_average(keyconf); + ed_keymap_gpencil_vertexpainting_smear(keyconf); + ed_keymap_gpencil_vertexpainting_replace(keyconf); } /* ****************************************** */ @@ -226,6 +510,12 @@ void ED_operatortypes_gpencil(void) WM_operatortype_append(GPENCIL_OT_draw); WM_operatortype_append(GPENCIL_OT_fill); + WM_operatortype_append(GPENCIL_OT_vertex_paint); + WM_operatortype_append(GPENCIL_OT_vertex_color_brightness_contrast); + WM_operatortype_append(GPENCIL_OT_vertex_color_hsv); + WM_operatortype_append(GPENCIL_OT_vertex_color_invert); + WM_operatortype_append(GPENCIL_OT_vertex_color_levels); + WM_operatortype_append(GPENCIL_OT_vertex_color_set); /* Guides ----------------------- */ @@ -238,6 +528,7 @@ void ED_operatortypes_gpencil(void) WM_operatortype_append(GPENCIL_OT_paintmode_toggle); WM_operatortype_append(GPENCIL_OT_sculptmode_toggle); WM_operatortype_append(GPENCIL_OT_weightmode_toggle); + WM_operatortype_append(GPENCIL_OT_vertexmode_toggle); WM_operatortype_append(GPENCIL_OT_selection_opacity_toggle); WM_operatortype_append(GPENCIL_OT_select); @@ -253,6 +544,7 @@ void ED_operatortypes_gpencil(void) WM_operatortype_append(GPENCIL_OT_select_first); WM_operatortype_append(GPENCIL_OT_select_last); WM_operatortype_append(GPENCIL_OT_select_alternate); + WM_operatortype_append(GPENCIL_OT_select_color); WM_operatortype_append(GPENCIL_OT_duplicate); WM_operatortype_append(GPENCIL_OT_delete); @@ -263,6 +555,7 @@ void ED_operatortypes_gpencil(void) WM_operatortype_append(GPENCIL_OT_move_to_layer); WM_operatortype_append(GPENCIL_OT_layer_change); + WM_operatortype_append(GPENCIL_OT_layer_active); WM_operatortype_append(GPENCIL_OT_set_active_material); @@ -271,8 +564,10 @@ void ED_operatortypes_gpencil(void) WM_operatortype_append(GPENCIL_OT_snap_cursor_to_selected); WM_operatortype_append(GPENCIL_OT_reproject); + WM_operatortype_append(GPENCIL_OT_recalc_geometry); WM_operatortype_append(GPENCIL_OT_sculpt_paint); + WM_operatortype_append(GPENCIL_OT_weight_paint); /* Editing (Buttons) ------------ */ @@ -288,6 +583,9 @@ void ED_operatortypes_gpencil(void) WM_operatortype_append(GPENCIL_OT_layer_duplicate); WM_operatortype_append(GPENCIL_OT_layer_duplicate_object); + WM_operatortype_append(GPENCIL_OT_layer_mask_add); + WM_operatortype_append(GPENCIL_OT_layer_mask_remove); + WM_operatortype_append(GPENCIL_OT_hide); WM_operatortype_append(GPENCIL_OT_reveal); WM_operatortype_append(GPENCIL_OT_lock_all); @@ -306,6 +604,8 @@ void ED_operatortypes_gpencil(void) WM_operatortype_append(GPENCIL_OT_convert); + WM_operatortype_append(GPENCIL_OT_image_to_grease_pencil); + WM_operatortype_append(GPENCIL_OT_stroke_arrange); WM_operatortype_append(GPENCIL_OT_stroke_change_color); WM_operatortype_append(GPENCIL_OT_stroke_lock_color); @@ -325,8 +625,16 @@ void ED_operatortypes_gpencil(void) WM_operatortype_append(GPENCIL_OT_stroke_cutter); WM_operatortype_append(GPENCIL_OT_stroke_trim); WM_operatortype_append(GPENCIL_OT_stroke_merge_by_distance); + WM_operatortype_append(GPENCIL_OT_stroke_merge_material); + + WM_operatortype_append(GPENCIL_OT_material_to_vertex_color); + WM_operatortype_append(GPENCIL_OT_extract_palette_vertex); + + WM_operatortype_append(GPENCIL_OT_transform_fill); + WM_operatortype_append(GPENCIL_OT_reset_transform_fill); - WM_operatortype_append(GPENCIL_OT_brush_presets_create); + WM_operatortype_append(GPENCIL_OT_brush_reset); + WM_operatortype_append(GPENCIL_OT_brush_reset_all); /* vertex groups */ WM_operatortype_append(GPENCIL_OT_vertex_group_assign); diff --git a/source/blender/editors/gpencil/gpencil_ops_versioning.c b/source/blender/editors/gpencil/gpencil_ops_versioning.c index 3d56cb0fcb1..e5b9d902210 100644 --- a/source/blender/editors/gpencil/gpencil_ops_versioning.c +++ b/source/blender/editors/gpencil/gpencil_ops_versioning.c @@ -130,7 +130,6 @@ static int gpencil_convert_old_files_exec(bContext *C, wmOperator *op) copy_v4_v4(gp_style->fill_rgba, palcolor->fill); /* set basic settings */ - gp_style->pattern_gridsize = 0.1f; gp_style->gradient_radius = 0.5f; ARRAY_SET_ITEMS(gp_style->mix_rgba, 1.0f, 1.0f, 1.0f, 0.2f); ARRAY_SET_ITEMS(gp_style->gradient_scale, 1.0f, 1.0f); @@ -138,13 +137,13 @@ static int gpencil_convert_old_files_exec(bContext *C, wmOperator *op) gp_style->texture_opacity = 1.0f; gp_style->texture_pixsize = 100.0f; - gp_style->flag |= GP_STYLE_STROKE_SHOW; - gp_style->flag |= GP_STYLE_FILL_SHOW; + gp_style->flag |= GP_MATERIAL_STROKE_SHOW; + gp_style->flag |= GP_MATERIAL_FILL_SHOW; /* fix strokes */ - for (bGPDlayer *gpl = gpd->layers.first; gpl; gpl = gpl->next) { - for (bGPDframe *gpf = gpl->frames.first; gpf; gpf = gpf->next) { - for (bGPDstroke *gps = gpf->strokes.first; gps; gps = gps->next) { + LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd->layers) { + LISTBASE_FOREACH (bGPDframe *, gpf, &gpl->frames) { + LISTBASE_FOREACH (bGPDstroke *, gps, &gpf->strokes) { if ((gps->colorname[0] != '\0') && (STREQ(gps->colorname, palcolor->info))) { gps->mat_nr = ob->totcol - 1; gps->colorname[0] = '\0'; @@ -174,7 +173,7 @@ static int gpencil_convert_old_files_exec(bContext *C, wmOperator *op) for (bGPDpalettecolor *palcolor = palette->colors.first; palcolor; palcolor = palcolor->next) { /* fix layers */ - for (bGPDlayer *gpl = gpd->layers.first; gpl; gpl = gpl->next) { + LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd->layers) { /* unlock/unhide layer */ gpl->flag &= ~GP_LAYER_LOCKED; gpl->flag &= ~GP_LAYER_HIDE; @@ -182,8 +181,8 @@ static int gpencil_convert_old_files_exec(bContext *C, wmOperator *op) gpl->opacity = 1.0f; /* disable tint */ gpl->tintcolor[3] = 0.0f; - for (bGPDframe *gpf = gpl->frames.first; gpf; gpf = gpf->next) { - for (bGPDstroke *gps = gpf->strokes.first; gps; gps = gps->next) { + LISTBASE_FOREACH (bGPDframe *, gpf, &gpl->frames) { + LISTBASE_FOREACH (bGPDstroke *, gps, &gpf->strokes) { if ((gps->colorname[0] != '\0') && (STREQ(gps->colorname, palcolor->info))) { /* copy color settings */ copy_v4_v4(gpl->color, palcolor->color); diff --git a/source/blender/editors/gpencil/gpencil_paint.c b/source/blender/editors/gpencil/gpencil_paint.c index afc0e66a8a6..fea589746c4 100644 --- a/source/blender/editors/gpencil/gpencil_paint.c +++ b/source/blender/editors/gpencil/gpencil_paint.c @@ -149,6 +149,8 @@ typedef struct tGPsdata { /** current object. */ Object *ob; + /** Obeject eval. */ + Object *ob_eval; /** window where painting originated. */ wmWindow *win; /** area where painting originated. */ @@ -215,6 +217,8 @@ typedef struct tGPsdata { float imat[4][4]; float mat[4][4]; + float diff_mat[4][4]; + /** custom color - hack for enforcing a particular color for track/mask editing. */ float custom_color[4]; @@ -270,11 +274,6 @@ static void gp_update_cache(bGPdata *gpd) } } -static bool gp_stroke_added_check(tGPsdata *p) -{ - return (p->gpf && p->gpf->strokes.last && p->flags & GP_PAINTFLAG_STROKEADDED); -} - static void gp_stroke_added_enable(tGPsdata *p) { BLI_assert(p->gpf->strokes.last != NULL); @@ -353,7 +352,7 @@ static void gp_get_3d_reference(tGPsdata *p, float vec[3]) if (p->ownerPtr.type == &RNA_Object) { ob = (Object *)p->ownerPtr.data; } - ED_gp_get_drawing_reference(p->scene, ob, p->gpl, *p->align_flag, vec); + ED_gpencil_drawing_reference_get(p->scene, ob, p->gpl, *p->align_flag, vec); } /* Stroke Editing ---------------------------- */ @@ -483,49 +482,26 @@ static void gp_stroke_convertcoords(tGPsdata *p, const float mval[2], float out[ } } -/* apply jitter to stroke */ -static void gp_brush_jitter(bGPdata *gpd, - Brush *brush, - tGPspoint *pt, - const float mval[2], - const float pressure, - float r_mval[2], - RNG *rng) +/* Apply jitter to stroke point. */ +static void gp_brush_jitter(bGPdata *gpd, tGPspoint *pt, const float amplitude) { - float tmp_pressure = pressure; - if (brush->gpencil_settings->draw_jitter > 0.0f) { - float curvef = BKE_curvemapping_evaluateF(brush->gpencil_settings->curve_jitter, 0, pressure); - tmp_pressure = curvef * brush->gpencil_settings->draw_sensitivity; - } - /* exponential value */ - const float exfactor = (brush->gpencil_settings->draw_jitter + 2.0f) * - (brush->gpencil_settings->draw_jitter + 2.0f); - const float fac = BLI_rng_get_float(rng) * exfactor * tmp_pressure; - /* Jitter is applied perpendicular to the mouse movement vector (2D space) */ - float mvec[2], svec[2]; - /* mouse movement in ints -> floats */ + /* Jitter is applied perpendicular to the mouse movement vector (2D space). */ + float mvec[2]; + /* Mouse movement in ints -> floats. */ if (gpd->runtime.sbuffer_used > 1) { - mvec[0] = (mval[0] - (pt - 1)->x); - mvec[1] = (mval[1] - (pt - 1)->y); + tGPspoint *pt_prev = pt - 1; + sub_v2_v2v2(mvec, &pt->x, &pt_prev->x); normalize_v2(mvec); } else { mvec[0] = 0.0f; mvec[1] = 0.0f; } - /* rotate mvec by 90 degrees... */ - svec[0] = -mvec[1]; - svec[1] = mvec[0]; - /* scale the displacement by the random, and apply */ - if (BLI_rng_get_float(rng) > 0.5f) { - mul_v2_fl(svec, -fac); - } - else { - mul_v2_fl(svec, fac); - } - - r_mval[0] = mval[0] + svec[0]; - r_mval[1] = mval[1] + svec[1]; + /* Rotate mvec by 90 degrees... */ + SWAP(float, mvec[0], mvec[1]); + mvec[0] -= mvec[0]; + /* Scale by displacement amount, and apply. */ + madd_v2_v2fl(&pt->x, mvec, amplitude); } /* apply pressure change depending of the angle of the stroke to simulate a pen with shape */ @@ -581,6 +557,7 @@ static void gp_brush_angle(bGPdata *gpd, Brush *brush, tGPspoint *pt, const floa static void gp_smooth_buffer(tGPsdata *p, float inf, int idx) { bGPdata *gpd = p->gpd; + GP_Sculpt_Guide *guide = &p->scene->toolsettings->gp_sculpt.guide; const short num_points = gpd->runtime.sbuffer_used; /* Do nothing if not enough points to smooth out */ @@ -628,9 +605,12 @@ static void gp_smooth_buffer(tGPsdata *p, float inf, int idx) strength += ptd->strength * average_fac; } - /* Based on influence factor, blend between original and optimal smoothed coordinate. */ - interp_v2_v2v2(c, c, sco, inf); - copy_v2_v2(&ptc->x, c); + /* Based on influence factor, blend between original and optimal smoothed coordinate but not + * for Guide mode. */ + if (!guide->use_guide) { + interp_v2_v2v2(c, c, sco, inf); + copy_v2_v2(&ptc->x, c); + } /* Interpolate pressure. */ ptc->pressure = interpf(ptc->pressure, pressure, inf); /* Interpolate strength. */ @@ -661,140 +641,59 @@ static void gp_smooth_segment(bGPdata *gpd, const float inf, int from_idx, int t tGPspoint *ptd = &points[i]; float sco[2] = {0.0f}; + float pressure = 0.0f; + float strength = 0.0f; /* Compute smoothed coordinate by taking the ones nearby */ if (pta) { madd_v2_v2fl(sco, &pta->x, average_fac); + pressure += pta->pressure * average_fac; + strength += pta->strength * average_fac; } else { madd_v2_v2fl(sco, &ptc->x, average_fac); + pressure += ptc->pressure * average_fac; + strength += ptc->strength * average_fac; } if (ptb) { madd_v2_v2fl(sco, &ptb->x, average_fac); + pressure += ptb->pressure * average_fac; + strength += ptb->strength * average_fac; } else { madd_v2_v2fl(sco, &ptc->x, average_fac); + pressure += ptc->pressure * average_fac; + strength += ptc->strength * average_fac; } madd_v2_v2fl(sco, &ptc->x, average_fac); + pressure += ptc->pressure * average_fac; + strength += ptc->strength * average_fac; madd_v2_v2fl(sco, &ptd->x, average_fac); + pressure += ptd->pressure * average_fac; + strength += ptd->strength * average_fac; /* Based on influence factor, blend between original and optimal smoothed coordinate. */ interp_v2_v2v2(&ptc->x, &ptc->x, sco, inf); - } -} - -/* Smooth all the sections created with fake events to avoid abrupt transitions. - * - * As the fake events add points between two real events, this produces a straight line, but if - * there is 3 or more real points that used fakes, the stroke is not smooth and produces abrupt - * angles. - * This function reads these segments and finds the real points and smooth with the surrounding - * points. */ -static void gp_smooth_fake_segments(tGPsdata *p) -{ - bGPdata *gpd = p->gpd; - Brush *brush = p->brush; - if (brush->gpencil_settings->input_samples < 2) { - return; - } - - tGPspoint *points = (tGPspoint *)gpd->runtime.sbuffer; - tGPspoint *pt = NULL; - /* Index where segment starts. */ - int from_idx = 0; - /* Index where segment ends. */ - int to_idx = 0; - - bool doit = false; - /* Loop all points except the extremes. */ - for (int i = 1; i < gpd->runtime.sbuffer_used - 1; i++) { - pt = &points[i]; - bool is_fake = (bool)(pt->tflag & GP_TPOINT_FAKE); - to_idx = i; - - /* Detect fake points in the stroke. */ - if ((!doit) && (is_fake)) { - from_idx = i; - doit = true; - } - /* If detect control point after fake points, select a segment with same length in both sides, - * except if it is more than stroke length. */ - if ((doit) && (!is_fake)) { - if (i + (i - from_idx) < gpd->runtime.sbuffer_used - 1) { - to_idx = i + (i - from_idx); - /* Smooth this segments (need loop to get cumulative smooth). */ - for (int r = 0; r < 5; r++) { - gp_smooth_segment(gpd, 0.1f, from_idx, to_idx); - } - } - else { - break; - } - /* Reset to new segments. */ - from_idx = i; - doit = false; - } - } -} - -/* Smooth the section added with fake events when pen moves very fast. */ -static void gp_smooth_fake_events(tGPsdata *p, int size_before, int size_after) -{ - bGPdata *gpd = p->gpd; - const short totpoints = size_after - size_before - 1; - /* Do nothing if not enough data to smooth out. */ - if (totpoints < 1) { - return; - } - - /* Back two points to get smoother effect. */ - size_before -= 2; - CLAMP_MIN(size_before, 1); - - tGPspoint *points = (tGPspoint *)gpd->runtime.sbuffer; - /* Extreme points. */ - const tGPspoint *pta = &points[size_before - 1]; - const tGPspoint *ptb = &points[size_after - 1]; - tGPspoint *pt1, *pt2; - int i; - - /* Get total length of the segment to smooth. */ - float totlen = 0.0f; - for (i = size_before; i < size_after; i++) { - pt1 = &points[i - 1]; - pt2 = &points[i]; - totlen += len_v2v2(&pt1->x, &pt2->x); - } - /* Smooth interpolating the position of the points. */ - float pointlen = 0.0f; - for (i = size_before; i < size_after - 1; i++) { - pt1 = &points[i - 1]; - pt2 = &points[i]; - pointlen += len_v2v2(&pt1->x, &pt2->x); - pt2->pressure = interpf(ptb->pressure, pta->pressure, pointlen / totlen); - pt2->strength = interpf(ptb->strength, pta->strength, pointlen / totlen); + /* Interpolate pressure. */ + ptc->pressure = interpf(ptc->pressure, pressure, inf); + /* Interpolate strength. */ + ptc->strength = interpf(ptc->strength, strength, inf); } } /* add current stroke-point to buffer (returns whether point was successfully added) */ -static short gp_stroke_addpoint( - tGPsdata *p, const float mval[2], float pressure, double curtime, bool is_fake) +static short gp_stroke_addpoint(tGPsdata *p, const float mval[2], float pressure, double curtime) { bGPdata *gpd = p->gpd; Brush *brush = p->brush; + BrushGpencilSettings *brush_settings = p->brush->gpencil_settings; tGPspoint *pt; - ToolSettings *ts = p->scene->toolsettings; Object *obact = (Object *)p->ownerPtr.data; - Depsgraph *depsgraph = p->depsgraph; RegionView3D *rv3d = p->region->regiondata; - View3D *v3d = p->sa->spacedata.first; - MaterialGPencilStyle *gp_style = p->material->gp_style; - const int def_nr = obact->actdef - 1; - const bool have_weight = (bool)BLI_findlink(&obact->defbase, def_nr); /* check painting mode */ if (p->paintmode == GP_PAINTMODE_DRAW_STRAIGHT) { @@ -843,111 +742,73 @@ static short gp_stroke_addpoint( return GP_STROKEADD_INVALID; } + /* Set vertex colors for buffer. */ + ED_gpencil_sbuffer_vertex_color_set( + p->depsgraph, p->ob, p->scene->toolsettings, p->brush, p->material); + /* get pointer to destination point */ pt = ((tGPspoint *)(gpd->runtime.sbuffer) + gpd->runtime.sbuffer_used); - /* Set if point was created by fake events. */ - if (is_fake) { - pt->tflag |= GP_TPOINT_FAKE; - } - else { - pt->tflag &= ~GP_TPOINT_FAKE; - } - /* store settings */ + pt->strength = brush_settings->draw_strength; + pt->pressure = 1.0f; + pt->uv_rot = 0.0f; + copy_v2_v2(&pt->x, mval); + /* pressure */ - if (brush->gpencil_settings->flag & GP_BRUSH_USE_PRESSURE) { - float curvef = BKE_curvemapping_evaluateF( - brush->gpencil_settings->curve_sensitivity, 0, pressure); - pt->pressure = curvef * brush->gpencil_settings->draw_sensitivity; - } - else { - pt->pressure = 1.0f; + if (brush_settings->flag & GP_BRUSH_USE_PRESSURE) { + pt->pressure *= BKE_curvemapping_evaluateF(brush_settings->curve_sensitivity, 0, pressure); } - /* Apply jitter to position */ - if ((brush->gpencil_settings->flag & GP_BRUSH_GROUP_RANDOM) && - (brush->gpencil_settings->draw_jitter > 0.0f)) { - float r_mval[2]; - const float jitpress = (brush->gpencil_settings->flag & GP_BRUSH_USE_JITTER_PRESSURE) ? - pressure : - 1.0f; - gp_brush_jitter(gpd, brush, pt, mval, jitpress, r_mval, p->rng); - copy_v2_v2(&pt->x, r_mval); - } - else { - copy_v2_v2(&pt->x, mval); + /* color strength */ + if (brush_settings->flag & GP_BRUSH_USE_STENGTH_PRESSURE) { + pt->strength *= BKE_curvemapping_evaluateF(brush_settings->curve_strength, 0, pressure); + CLAMP(pt->strength, GPENCIL_STRENGTH_MIN, 1.0f); } - /* apply randomness to pressure */ - if ((brush->gpencil_settings->flag & GP_BRUSH_GROUP_RANDOM) && - (brush->gpencil_settings->draw_random_press > 0.0f)) { - float curvef = BKE_curvemapping_evaluateF( - brush->gpencil_settings->curve_sensitivity, 0, pressure); - float tmp_pressure = curvef * brush->gpencil_settings->draw_sensitivity; - if (BLI_rng_get_float(p->rng) > 0.5f) { - pt->pressure -= tmp_pressure * brush->gpencil_settings->draw_random_press * - BLI_rng_get_float(p->rng); + + if (brush_settings->flag & GP_BRUSH_GROUP_RANDOM) { + /* Apply jitter to position */ + if (brush_settings->draw_jitter > 0.0f) { + float rand = BLI_rng_get_float(p->rng) * 2.0f - 1.0f; + float jitpress = 1.0f; + if (brush_settings->flag & GP_BRUSH_USE_JITTER_PRESSURE) { + jitpress = BKE_curvemapping_evaluateF(brush_settings->curve_jitter, 0, pressure); + } + /* FIXME the +2 means minimum jitter is 4 which is a bit strange for UX. */ + const float exp_factor = brush_settings->draw_jitter + 2.0f; + const float fac = rand * square_f(exp_factor) * jitpress; + gp_brush_jitter(gpd, pt, fac); } - else { - pt->pressure += tmp_pressure * brush->gpencil_settings->draw_random_press * - BLI_rng_get_float(p->rng); + /* apply randomness to pressure */ + if (brush_settings->draw_random_press > 0.0f) { + float rand = BLI_rng_get_float(p->rng) * 2.0f - 1.0f; + pt->pressure *= 1.0 + rand * 2.0 * brush_settings->draw_random_press; + CLAMP(pt->pressure, GPENCIL_STRENGTH_MIN, 1.0f); } - CLAMP(pt->pressure, GPENCIL_STRENGTH_MIN, 1.0f); - } - - /* apply randomness to uv texture rotation */ - if ((brush->gpencil_settings->flag & GP_BRUSH_GROUP_RANDOM) && - (brush->gpencil_settings->uv_random > 0.0f)) { - if (BLI_rng_get_float(p->rng) > 0.5f) { - pt->uv_rot = (BLI_rng_get_float(p->rng) * M_PI * -1) * brush->gpencil_settings->uv_random; + /* apply randomness to uv texture rotation */ + if (brush_settings->uv_random > 0.0f) { + float rand = BLI_rng_get_float(p->rng) * 2.0f - 1.0f; + pt->uv_rot += rand * M_PI * brush_settings->uv_random; + CLAMP(pt->uv_rot, -M_PI_2, M_PI_2); } - else { - pt->uv_rot = (BLI_rng_get_float(p->rng) * M_PI) * brush->gpencil_settings->uv_random; + /* apply randomness to color strength */ + if (brush_settings->draw_random_strength) { + float rand = BLI_rng_get_float(p->rng) * 2.0f - 1.0f; + pt->strength *= 1.0 + rand * brush_settings->draw_random_strength; + CLAMP(pt->strength, GPENCIL_STRENGTH_MIN, 1.0f); } - CLAMP(pt->uv_rot, -M_PI_2, M_PI_2); - } - else { - pt->uv_rot = 0.0f; } /* apply angle of stroke to brush size */ - if (brush->gpencil_settings->draw_angle_factor != 0.0f) { + if (brush_settings->draw_angle_factor != 0.0f) { gp_brush_angle(gpd, brush, pt, mval); } - /* color strength */ - if (brush->gpencil_settings->flag & GP_BRUSH_USE_STENGTH_PRESSURE) { - float curvef = BKE_curvemapping_evaluateF( - brush->gpencil_settings->curve_strength, 0, pressure); - float tmp_pressure = curvef * brush->gpencil_settings->draw_sensitivity; - - pt->strength = tmp_pressure * brush->gpencil_settings->draw_strength; - } - else { - pt->strength = brush->gpencil_settings->draw_strength; - } - CLAMP(pt->strength, GPENCIL_STRENGTH_MIN, 1.0f); - - /* apply randomness to color strength */ - if ((brush->gpencil_settings->flag & GP_BRUSH_GROUP_RANDOM) && - (brush->gpencil_settings->draw_random_strength > 0.0f)) { - if (BLI_rng_get_float(p->rng) > 0.5f) { - pt->strength -= pt->strength * brush->gpencil_settings->draw_random_strength * - BLI_rng_get_float(p->rng); - } - else { - pt->strength += pt->strength * brush->gpencil_settings->draw_random_strength * - BLI_rng_get_float(p->rng); - } - CLAMP(pt->strength, GPENCIL_STRENGTH_MIN, 1.0f); - } - /* point time */ pt->time = (float)(curtime - p->inittime); /* point uv (only 3d view) */ if ((p->sa->spacetype == SPACE_VIEW3D) && (gpd->runtime.sbuffer_used > 0)) { - float pixsize = gp_style->texture_pixsize / 1000000.0f; tGPspoint *ptb = (tGPspoint *)gpd->runtime.sbuffer + gpd->runtime.sbuffer_used - 1; bGPDspoint spt, spt2; @@ -961,11 +822,8 @@ static short gp_stroke_addpoint( /* reproject previous */ ED_gpencil_tpoint_to_point(p->region, origin, ptb, &spt2); ED_gp_project_point_to_plane(p->scene, obact, rv3d, origin, p->lock_axis - 1, &spt2); - p->totpixlen += len_v3v3(&spt.x, &spt2.x) / pixsize; + p->totpixlen += len_v3v3(&spt.x, &spt2.x); pt->uv_fac = p->totpixlen; - if ((gp_style) && (gp_style->sima)) { - pt->uv_fac /= gp_style->sima->gen_x; - } } else { p->totpixlen = 0.0f; @@ -975,7 +833,7 @@ static short gp_stroke_addpoint( /* increment counters */ gpd->runtime.sbuffer_used++; - /* smooth while drawing previous points with a reduction factor for previous */ + /* Smooth while drawing previous points with a reduction factor for previous. */ if (brush->gpencil_settings->active_smooth > 0.0f) { for (int s = 0; s < 3; s++) { gp_smooth_buffer(p, @@ -984,100 +842,11 @@ static short gp_stroke_addpoint( } } - return GP_STROKEADD_NORMAL; - } - else if (p->paintmode == GP_PAINTMODE_DRAW_POLY) { - - /* enable special flag for drawing engine */ - gpd->flag |= GP_DATA_STROKE_POLYGON; - - bGPDlayer *gpl = BKE_gpencil_layer_getactive(gpd); - /* get pointer to destination point */ - pt = (tGPspoint *)(gpd->runtime.sbuffer); - - /* store settings */ - copy_v2_v2(&pt->x, mval); - /* T44932 - Pressure vals are unreliable, so ignore for now */ - pt->pressure = 1.0f; - pt->strength = 1.0f; - pt->time = (float)(curtime - p->inittime); - - /* if there's stroke for this poly line session add (or replace last) point - * to stroke. This allows to draw lines more interactively (see new segment - * during mouse slide, e.g.) - */ - if (gp_stroke_added_check(p)) { - bGPDstroke *gps = p->gpf->strokes.last; - bGPDspoint *pts; - MDeformVert *dvert = NULL; - - /* First time point is adding to temporary buffer (need to allocate new point in stroke) */ - if (gpd->runtime.sbuffer_used == 0) { - gps->points = MEM_reallocN(gps->points, sizeof(bGPDspoint) * (gps->totpoints + 1)); - if (gps->dvert != NULL) { - gps->dvert = MEM_reallocN(gps->dvert, sizeof(MDeformVert) * (gps->totpoints + 1)); - } - gps->totpoints++; - } - - pts = &gps->points[gps->totpoints - 1]; - if (gps->dvert != NULL) { - dvert = &gps->dvert[gps->totpoints - 1]; - } - /* special case for poly lines: normally, - * depth is needed only when creating new stroke from buffer, - * but poly lines are converting to stroke instantly, - * so initialize depth buffer before converting coordinates - */ - if (gpencil_project_check(p)) { - view3d_region_operator_needs_opengl(p->win, p->region); - ED_view3d_autodist_init(p->depsgraph, - p->region, - v3d, - (ts->gpencil_v3d_align & GP_PROJECT_DEPTH_STROKE) ? 1 : 0); - } - - /* convert screen-coordinates to appropriate coordinates (and store them) */ - gp_stroke_convertcoords(p, &pt->x, &pts->x, NULL); - /* reproject to plane (only in 3d space) */ - gp_reproject_toplane(p, gps); - /* if parented change position relative to parent object */ - gp_apply_parent_point(depsgraph, obact, gpd, gpl, pts); - /* copy pressure and time */ - pts->pressure = pt->pressure; - pts->strength = pt->strength; - pts->time = pt->time; - pts->uv_fac = pt->uv_fac; - pts->uv_rot = pt->uv_rot; - - if ((ts->gpencil_flags & GP_TOOL_FLAG_CREATE_WEIGHTS) && (have_weight)) { - BKE_gpencil_dvert_ensure(gps); - MDeformWeight *dw = BKE_defvert_ensure_index(dvert, def_nr); - if (dw) { - dw->weight = ts->vgroup_weight; - } - } - else { - if (dvert != NULL) { - dvert->totweight = 0; - dvert->dw = NULL; - } - } - - /* force fill recalc */ - gps->flag |= GP_STROKE_RECALC_GEOMETRY; - /* drawing batch cache is dirty now */ - gp_update_cache(p->gpd); - } - - /* increment counters */ - if (gpd->runtime.sbuffer_used == 0) { - gpd->runtime.sbuffer_used++; - } + /* Update evaluated data. */ + ED_gpencil_sbuffer_update_eval(gpd, p->ob_eval); return GP_STROKEADD_NORMAL; } - /* return invalid state for now... */ return GP_STROKEADD_INVALID; } @@ -1143,42 +912,25 @@ static void gp_stroke_newfrombuffer(tGPsdata *p) return; } - /* special case for poly line -- for already added stroke during session - * coordinates are getting added to stroke immediately to allow more - * interactive behavior - */ - if (p->paintmode == GP_PAINTMODE_DRAW_POLY) { - /* be sure to hide any lazy cursor */ - ED_gpencil_toggle_brush_cursor(p->C, true, NULL); - - if (gp_stroke_added_check(p)) { - return; - } - } - /* allocate memory for a new stroke */ gps = MEM_callocN(sizeof(bGPDstroke), "gp_stroke"); /* copy appropriate settings for stroke */ gps->totpoints = totelem; gps->thickness = brush->size; - gps->gradient_f = brush->gpencil_settings->gradient_f; - copy_v2_v2(gps->gradient_s, brush->gpencil_settings->gradient_s); + gps->fill_opacity_fac = 1.0f; + gps->hardeness = brush->gpencil_settings->hardeness; + copy_v2_v2(gps->aspect_ratio, brush->gpencil_settings->aspect_ratio); gps->flag = gpd->runtime.sbuffer_sflag; gps->inittime = p->inittime; - - /* enable recalculation flag by default (only used if hq fill) */ - gps->flag |= GP_STROKE_RECALC_GEOMETRY; + gps->uv_scale = 1.0f; /* allocate enough memory for a continuous array for storage points */ const int subdivide = brush->gpencil_settings->draw_subdivide; gps->points = MEM_callocN(sizeof(bGPDspoint) * gps->totpoints, "gp_stroke_points"); + gps->dvert = NULL; - /* initialize triangle memory to dummy data */ - gps->triangles = NULL; - gps->flag |= GP_STROKE_RECALC_GEOMETRY; - gps->tot_triangles = 0; /* drawing batch cache is dirty now */ gp_update_cache(p->gpd); /* set pointer to first non-initialized point */ @@ -1187,6 +939,9 @@ static void gp_stroke_newfrombuffer(tGPsdata *p) dvert = gps->dvert + (gps->totpoints - totelem); } + /* Apply the vertex color to fill. */ + ED_gpencil_fill_vertex_color_set(ts, brush, gps); + /* copy points from the buffer to the stroke */ if (p->paintmode == GP_PAINTMODE_DRAW_STRAIGHT) { /* straight lines only -> only endpoints */ @@ -1201,6 +956,9 @@ static void gp_stroke_newfrombuffer(tGPsdata *p) pt->strength = ptc->strength; CLAMP(pt->strength, GPENCIL_STRENGTH_MIN, 1.0f); pt->time = ptc->time; + /* Apply the vertex color to point. */ + ED_gpencil_point_vertex_color_set(ts, brush, pt); + pt++; if ((ts->gpencil_flags & GP_TOOL_FLAG_CREATE_WEIGHTS) && (have_weight)) { @@ -1231,6 +989,8 @@ static void gp_stroke_newfrombuffer(tGPsdata *p) pt->strength = ptc->strength; CLAMP(pt->strength, GPENCIL_STRENGTH_MIN, 1.0f); pt->time = ptc->time; + /* Apply the vertex color to point. */ + ED_gpencil_point_vertex_color_set(ts, brush, pt); if ((ts->gpencil_flags & GP_TOOL_FLAG_CREATE_WEIGHTS) && (have_weight)) { BKE_gpencil_dvert_ensure(gps); @@ -1252,7 +1012,7 @@ static void gp_stroke_newfrombuffer(tGPsdata *p) pt = gps->points; for (i = 0; i < gps->totpoints; i++, pt++) { /* if parented change position relative to parent object */ - gp_apply_parent_point(depsgraph, obact, gpd, gpl, pt); + gp_apply_parent_point(depsgraph, obact, gpl, pt); } /* if camera view, reproject flat to view to avoid perspective effect */ @@ -1260,40 +1020,6 @@ static void gp_stroke_newfrombuffer(tGPsdata *p) ED_gpencil_project_stroke_to_view(p->C, p->gpl, gps); } } - else if (p->paintmode == GP_PAINTMODE_DRAW_POLY) { - /* first point */ - ptc = gpd->runtime.sbuffer; - - /* convert screen-coordinates to appropriate coordinates (and store them) */ - gp_stroke_convertcoords(p, &ptc->x, &pt->x, NULL); - /* reproject to plane (only in 3d space) */ - gp_reproject_toplane(p, gps); - /* if parented change position relative to parent object */ - gp_apply_parent_point(depsgraph, obact, gpd, gpl, pt); - /* if camera view, reproject flat to view to avoid perspective effect */ - if (is_camera) { - ED_gpencil_project_stroke_to_view(p->C, p->gpl, gps); - } - /* copy pressure and time */ - pt->pressure = ptc->pressure; - pt->strength = ptc->strength; - CLAMP(pt->strength, GPENCIL_STRENGTH_MIN, 1.0f); - pt->time = ptc->time; - - if ((ts->gpencil_flags & GP_TOOL_FLAG_CREATE_WEIGHTS) && (have_weight)) { - BKE_gpencil_dvert_ensure(gps); - MDeformWeight *dw = BKE_defvert_ensure_index(dvert, def_nr); - if (dw) { - dw->weight = ts->vgroup_weight; - } - } - else { - if (dvert != NULL) { - dvert->totweight = 0; - dvert->dw = NULL; - } - } - } else { float *depth_arr = NULL; @@ -1370,9 +1096,6 @@ static void gp_stroke_newfrombuffer(tGPsdata *p) } } - /* Smooth any point created with fake events when the mouse/pen move very fast. */ - gp_smooth_fake_segments(p); - pt = gps->points; dvert = gps->dvert; @@ -1389,6 +1112,8 @@ static void gp_stroke_newfrombuffer(tGPsdata *p) pt->time = ptc->time; pt->uv_fac = ptc->uv_fac; pt->uv_rot = ptc->uv_rot; + /* Apply the vertex color to point. */ + ED_gpencil_point_vertex_color_set(ts, brush, pt); if (dvert != NULL) { dvert->totweight = 0; @@ -1401,11 +1126,6 @@ static void gp_stroke_newfrombuffer(tGPsdata *p) if ((brush->gpencil_settings->flag & GP_BRUSH_GROUP_SETTINGS) && (subdivide > 0)) { gp_subdivide_stroke(gps, subdivide); } - /* apply randomness to stroke */ - if ((brush->gpencil_settings->flag & GP_BRUSH_GROUP_RANDOM) && - (brush->gpencil_settings->draw_random_sub > 0.0f)) { - gp_randomize_stroke(gps, brush, p->rng); - } /* Smooth stroke after subdiv - only if there's something to do for each iteration, * the factor is reduced to get a better smoothing @@ -1415,8 +1135,8 @@ static void gp_stroke_newfrombuffer(tGPsdata *p) float reduce = 0.0f; for (int r = 0; r < brush->gpencil_settings->draw_smoothlvl; r++) { for (i = 0; i < gps->totpoints - 1; i++) { - BKE_gpencil_smooth_stroke(gps, i, brush->gpencil_settings->draw_smoothfac - reduce); - BKE_gpencil_smooth_stroke_strength(gps, i, brush->gpencil_settings->draw_smoothfac); + BKE_gpencil_stroke_smooth(gps, i, brush->gpencil_settings->draw_smoothfac - reduce); + BKE_gpencil_stroke_smooth_strength(gps, i, brush->gpencil_settings->draw_smoothfac); } reduce += 0.25f; /* reduce the factor */ } @@ -1425,23 +1145,13 @@ static void gp_stroke_newfrombuffer(tGPsdata *p) /* Simplify adaptive */ if ((brush->gpencil_settings->flag & GP_BRUSH_GROUP_SETTINGS) && (brush->gpencil_settings->simplify_f > 0.0f)) { - BKE_gpencil_simplify_stroke(gps, brush->gpencil_settings->simplify_f); - } - - /* smooth thickness */ - if ((brush->gpencil_settings->flag & GP_BRUSH_GROUP_SETTINGS) && - (brush->gpencil_settings->thick_smoothfac > 0.0f)) { - for (int r = 0; r < brush->gpencil_settings->thick_smoothlvl * 2; r++) { - for (i = 0; i < gps->totpoints - 1; i++) { - BKE_gpencil_smooth_stroke_thickness(gps, i, brush->gpencil_settings->thick_smoothfac); - } - } + BKE_gpencil_stroke_simplify_adaptive(gps, brush->gpencil_settings->simplify_f); } /* reproject to plane (only in 3d space) */ gp_reproject_toplane(p, gps); /* change position relative to parent object */ - gp_apply_parent(depsgraph, obact, gpd, gpl, gps); + gp_apply_parent(depsgraph, obact, gpl, gps); /* if camera view, reproject flat to view to avoid perspective effect */ if (is_camera) { ED_gpencil_project_stroke_to_view(p->C, p->gpl, gps); @@ -1463,15 +1173,11 @@ static void gp_stroke_newfrombuffer(tGPsdata *p) } } - /* calculate UVs along the stroke */ - ED_gpencil_calc_stroke_uv(obact, gps); - /* add stroke to frame, usually on tail of the listbase, but if on back is enabled the stroke * is added on listbase head because the drawing order is inverse and the head stroke is the * first to draw. This is very useful for artist when drawing the background. */ - if ((ts->gpencil_flags & GP_TOOL_FLAG_PAINT_ONBACK) && - (p->paintmode != GP_PAINTMODE_DRAW_POLY)) { + if (ts->gpencil_flags & GP_TOOL_FLAG_PAINT_ONBACK) { BLI_addhead(&p->gpf->strokes, gps); } else { @@ -1492,9 +1198,12 @@ static void gp_stroke_newfrombuffer(tGPsdata *p) /* post process stroke */ if ((p->brush->gpencil_settings->flag & GP_BRUSH_GROUP_SETTINGS) && p->brush->gpencil_settings->flag & GP_BRUSH_TRIM_STROKE) { - BKE_gpencil_trim_stroke(gps); + BKE_gpencil_stroke_trim(gps); } + /* Calc geometry data. */ + BKE_gpencil_stroke_geometry_update(gps); + gp_stroke_added_enable(p); } @@ -1540,7 +1249,7 @@ static bool gp_stroke_eraser_is_occluded(tGPsdata *p, float diff_mat[4][4]; /* calculate difference matrix if parent object */ - ED_gpencil_parent_location(p->depsgraph, obact, p->gpd, gpl, diff_mat); + BKE_gpencil_parent_matrix_get(p->depsgraph, obact, gpl, diff_mat); if (ED_view3d_autodist_simple(p->region, mval_i, mval_3d, 0, NULL)) { const float depth_mval = view3d_point_depth(rv3d, mval_3d); @@ -1642,9 +1351,7 @@ static void gp_stroke_soft_refine(bGPDstroke *gps) } /* 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 float mval[2], @@ -1652,21 +1359,15 @@ static void gp_stroke_eraser_dostroke(tGPsdata *p, const int radius, const rcti *rect) { - Depsgraph *depsgraph = p->depsgraph; - Object *obact = (Object *)p->ownerPtr.data; Brush *eraser = p->eraser; bGPDspoint *pt0, *pt1, *pt2; int pc0[2] = {0}; int pc1[2] = {0}; int pc2[2] = {0}; int i; - float diff_mat[4][4]; int mval_i[2]; round_v2i_v2fl(mval_i, mval); - /* calculate difference matrix */ - ED_gpencil_parent_location(depsgraph, obact, p->gpd, gpl, diff_mat); - if (gps->totpoints == 0) { /* just free stroke */ gp_free_stroke(p->gpd, gpf, gps); @@ -1675,7 +1376,7 @@ static void gp_stroke_eraser_dostroke(tGPsdata *p, /* only process if it hasn't been masked out... */ if (!(p->flags & GP_PAINTFLAG_SELECTMASK) || (gps->points->flag & GP_SPOINT_SELECT)) { bGPDspoint pt_temp; - gp_point_to_parent_space(gps->points, diff_mat, &pt_temp); + gp_point_to_parent_space(gps->points, p->diff_mat, &pt_temp); gp_point_to_xy(&p->gsc, gps, &pt_temp, &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])) { @@ -1699,7 +1400,7 @@ static void gp_stroke_eraser_dostroke(tGPsdata *p, /* get points to work with */ pt1 = gps->points + i; bGPDspoint npt; - gp_point_to_parent_space(pt1, diff_mat, &npt); + gp_point_to_parent_space(pt1, p->diff_mat, &npt); gp_point_to_xy(&p->gsc, gps, &npt, &pc1[0], &pc1[1]); /* do boundbox check first */ @@ -1751,7 +1452,7 @@ static void gp_stroke_eraser_dostroke(tGPsdata *p, bGPDspoint npt; if (pt0) { - gp_point_to_parent_space(pt0, diff_mat, &npt); + gp_point_to_parent_space(pt0, p->diff_mat, &npt); gp_point_to_xy(&p->gsc, gps, &npt, &pc0[0], &pc0[1]); } else { @@ -1759,10 +1460,10 @@ static void gp_stroke_eraser_dostroke(tGPsdata *p, copy_v2_v2_int(pc0, pc1); } - gp_point_to_parent_space(pt1, diff_mat, &npt); + gp_point_to_parent_space(pt1, p->diff_mat, &npt); gp_point_to_xy(&p->gsc, gps, &npt, &pc1[0], &pc1[1]); - gp_point_to_parent_space(pt2, diff_mat, &npt); + gp_point_to_parent_space(pt2, p->diff_mat, &npt); gp_point_to_xy(&p->gsc, gps, &npt, &pc2[0], &pc2[1]); /* Check that point segment of the boundbox of the eraser stroke */ @@ -1862,8 +1563,6 @@ static void gp_stroke_eraser_dostroke(tGPsdata *p, /* erase strokes which fall under the eraser strokes */ static void gp_stroke_doeraser(tGPsdata *p) { - bGPDlayer *gpl; - bGPDstroke *gps, *gpn; rcti rect; Brush *brush = p->brush; Brush *eraser = p->eraser; @@ -1903,29 +1602,36 @@ static void gp_stroke_doeraser(tGPsdata *p) * 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) { + LISTBASE_FOREACH (bGPDlayer *, gpl, &p->gpd->layers) { bGPDframe *gpf = gpl->actframe; /* only affect layer if it's editable (and visible) */ - if (gpencil_layer_is_editable(gpl) == false) { + if (BKE_gpencil_layer_is_editable(gpl) == false) { continue; } else if (gpf == NULL) { continue; } + /* calculate difference matrix */ + BKE_gpencil_parent_matrix_get(p->depsgraph, p->ob, gpl, p->diff_mat); /* loop over strokes, checking segments for intersections */ - for (gps = gpf->strokes.first; gps; gps = gpn) { - gpn = gps->next; + LISTBASE_FOREACH_MUTABLE (bGPDstroke *, gps, &gpf->strokes) { /* check if the color is editable */ if (ED_gpencil_stroke_color_use(p->ob, gpl, gps) == false) { continue; } + + /* Check if the stroke collide with mouse. */ + if (!ED_gpencil_stroke_check_collision(&p->gsc, gps, p->mval, calc_radius, p->diff_mat)) { + continue; + } + /* 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, calc_radius, &rect); + gp_stroke_eraser_dostroke(p, gpf, gps, p->mval, p->mvalo, calc_radius, &rect); } } } @@ -1964,7 +1670,7 @@ static Brush *gp_get_default_eraser(Main *bmain, ToolSettings *ts) { Brush *brush_dft = NULL; Paint *paint = &ts->gp_paint->paint; - Brush *brush_old = paint->brush; + Brush *brush_prev = paint->brush; for (Brush *brush = bmain->brushes.first; brush; brush = brush->id.next) { if (brush->gpencil_settings == NULL) { continue; @@ -1987,15 +1693,15 @@ static Brush *gp_get_default_eraser(Main *bmain, ToolSettings *ts) } /* create a new soft eraser brush */ else { - brush_dft = BKE_brush_add_gpencil(bmain, ts, "Soft Eraser"); + brush_dft = BKE_brush_add_gpencil(bmain, ts, "Soft Eraser", OB_MODE_PAINT_GPENCIL); brush_dft->size = 30.0f; - brush_dft->gpencil_settings->flag |= (GP_BRUSH_ENABLE_CURSOR | GP_BRUSH_DEFAULT_ERASER); + brush_dft->gpencil_settings->flag |= GP_BRUSH_DEFAULT_ERASER; brush_dft->gpencil_settings->icon_id = GP_BRUSH_ICON_ERASE_SOFT; brush_dft->gpencil_tool = GPAINT_TOOL_ERASE; brush_dft->gpencil_settings->eraser_mode = GP_BRUSH_ERASER_SOFT; /* reset current brush */ - BKE_paint_brush_set(paint, brush_old); + BKE_paint_brush_set(paint, brush_prev); return brush_dft; } @@ -2032,7 +1738,7 @@ static void gp_init_drawing_brush(bContext *C, tGPsdata *p) /* if not exist, create a new one */ if ((paint->brush == NULL) || (paint->brush->gpencil_settings == NULL)) { /* create new brushes */ - BKE_brush_gpencil_presets(bmain, ts); + BKE_brush_gpencil_paint_presets(bmain, ts); changed = true; } /* be sure curves are initializated */ @@ -2054,10 +1760,7 @@ static void gp_init_drawing_brush(bContext *C, tGPsdata *p) /* use radius of eraser */ p->radius = (short)p->eraser->size; - /* GPXX: Need this update to synchronize brush with draw manager. - * Maybe this update can be removed when the new tool system - * will be in place, but while, we need this to keep drawing working. - */ + /* Need this update to synchronize brush with draw manager. */ if (changed) { DEG_id_tag_update(&scene->id, ID_RECALC_COPY_ON_WRITE); } @@ -2069,33 +1772,11 @@ static void gp_init_colors(tGPsdata *p) bGPdata *gpd = p->gpd; Brush *brush = p->brush; - MaterialGPencilStyle *gp_style = NULL; - /* use brush material */ p->material = BKE_gpencil_object_material_ensure_from_active_input_brush(p->bmain, p->ob, brush); - /* assign color information to temp tGPsdata */ - gp_style = p->material->gp_style; - if (gp_style) { - - /* set colors */ - if (gp_style->flag & GP_STYLE_STROKE_SHOW) { - copy_v4_v4(gpd->runtime.scolor, gp_style->stroke_rgba); - } - else { - /* if no stroke, use fill */ - copy_v4_v4(gpd->runtime.scolor, gp_style->fill_rgba); - } - copy_v4_v4(gpd->runtime.sfill, gp_style->fill_rgba); - /* add some alpha to make easy the filling without hide strokes */ - if (gpd->runtime.sfill[3] > 0.8f) { - gpd->runtime.sfill[3] = 0.8f; - } - - gpd->runtime.mode = (short)gp_style->mode; - gpd->runtime.bstroke_style = gp_style->stroke_style; - gpd->runtime.bfill_style = gp_style->fill_style; - } + gpd->runtime.matid = BKE_object_material_slot_find_index(p->ob, p->material); + gpd->runtime.sbuffer_brush = brush; } /* (re)init new painting data */ @@ -2131,9 +1812,6 @@ static bool gp_session_initdata(bContext *C, wmOperator *op, tGPsdata *p) switch (curarea->spacetype) { /* supported views first */ case SPACE_VIEW3D: { - /* View3D *v3d = curarea->spacedata.first; */ - /* RegionView3D *rv3d = region->regiondata; */ - /* set current area * - must verify that region data is 3D-view (and not something else) */ @@ -2162,10 +1840,11 @@ static bool gp_session_initdata(bContext *C, wmOperator *op, tGPsdata *p) local_view_bits = v3d->local_view_uuid; } /* create new default object */ - obact = ED_gpencil_add_object(C, p->scene, cur, local_view_bits); + obact = ED_gpencil_add_object(C, cur, local_view_bits); } /* assign object after all checks to be sure we have one active */ p->ob = obact; + p->ob_eval = (Object *)DEG_get_evaluated_object(p->depsgraph, p->ob); break; } @@ -2204,21 +1883,14 @@ static bool gp_session_initdata(bContext *C, wmOperator *op, tGPsdata *p) gp_init_drawing_brush(C, p); /* setup active color */ - if (curarea->spacetype == SPACE_VIEW3D) { - /* region where paint was originated */ - p->gpd->runtime.ar = CTX_wm_region(C); - - /* NOTE: This is only done for 3D view, as Materials aren't used for - * annotations in 2D editors - */ - int totcol = p->ob->totcol; + /* region where paint was originated */ + p->gpd->runtime.ar = CTX_wm_region(C); + int totcol = p->ob->totcol; + gp_init_colors(p); - gp_init_colors(p); - - /* check whether the material was newly added */ - if (totcol != p->ob->totcol) { - WM_event_add_notifier(C, NC_SPACE | ND_SPACE_PROPERTIES, NULL); - } + /* check whether the material was newly added */ + if (totcol != p->ob->totcol) { + WM_event_add_notifier(C, NC_SPACE | ND_SPACE_PROPERTIES, NULL); } /* lock axis (in some modes, disable) */ @@ -2274,7 +1946,6 @@ static void gp_session_cleanup(tGPsdata *p) /* free stroke buffer */ if (gpd->runtime.sbuffer) { - /* printf("\t\tGP - free sbuffer\n"); */ MEM_SAFE_FREE(gpd->runtime.sbuffer); gpd->runtime.sbuffer = NULL; } @@ -2300,12 +1971,13 @@ static void gp_paint_initstroke(tGPsdata *p, eGPencil_PaintModes paintmode, Deps { Scene *scene = p->scene; ToolSettings *ts = scene->toolsettings; + bool changed = false; /* get active layer (or add a new one if non-existent) */ - p->gpl = BKE_gpencil_layer_getactive(p->gpd); + p->gpl = BKE_gpencil_layer_active_get(p->gpd); if (p->gpl == NULL) { p->gpl = BKE_gpencil_layer_addnew(p->gpd, DATA_("GP_Layer"), true); - + changed = true; if (p->custom_color[3]) { copy_v3_v3(p->gpl->color, p->custom_color); } @@ -2318,46 +1990,28 @@ static void gp_paint_initstroke(tGPsdata *p, eGPencil_PaintModes paintmode, Deps return; } - /* get active frame (add a new one if not matching frame) */ + /* Eraser mode: If no active strokes, just 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) - */ bool has_layer_to_erase = false; - for (bGPDlayer *gpl = p->gpd->layers.first; gpl; gpl = gpl->next) { + LISTBASE_FOREACH (bGPDlayer *, gpl, &p->gpd->layers) { /* Skip if layer not editable */ - if (gpencil_layer_is_editable(gpl) == false) { + if (BKE_gpencil_layer_is_editable(gpl) == false) { continue; } - /* Add a new frame if needed (and based off the active frame, - * as we need some existing strokes to erase) - * - * Note: We don't add a new frame if there's nothing there now, so - * -> If there are no frames at all, don't add one - * -> If there are no strokes in that frame, don't add a new empty frame - */ if (gpl->actframe && gpl->actframe->strokes.first) { - gpl->actframe = BKE_gpencil_layer_getframe(gpl, CFRA, GP_GETFRAME_ADD_COPY); has_layer_to_erase = true; + break; } - - /* 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 (has_layer_to_erase == false) { p->status = GP_STATUS_ERROR; return; } + /* Ensure this gets set... */ + p->gpf = p->gpl->actframe; } else { /* Drawing Modes - Add a new frame if needed on the active layer */ @@ -2370,7 +2024,7 @@ static void gp_paint_initstroke(tGPsdata *p, eGPencil_PaintModes paintmode, Deps add_frame_mode = GP_GETFRAME_ADD_NEW; } - p->gpf = BKE_gpencil_layer_getframe(p->gpl, CFRA, add_frame_mode); + p->gpf = BKE_gpencil_layer_frame_get(p->gpl, CFRA, add_frame_mode); if (p->gpf == NULL) { p->status = GP_STATUS_ERROR; @@ -2397,8 +2051,6 @@ static void gp_paint_initstroke(tGPsdata *p, eGPencil_PaintModes paintmode, Deps /* set special fill stroke mode */ if (p->disable_fill == true) { p->gpd->runtime.sbuffer_sflag |= GP_STROKE_NOFILL; - /* replace stroke color with fill color */ - copy_v4_v4(p->gpd->runtime.scolor, p->gpd->runtime.sfill); } /* set 'initial run' flag, which is only used to denote when a new stroke is starting */ @@ -2443,6 +2095,14 @@ static void gp_paint_initstroke(tGPsdata *p, eGPencil_PaintModes paintmode, Deps } } } + if (!changed) { + /* Copy the brush to avoid a full tag (very slow). */ + bGPdata *gpd_eval = (bGPdata *)p->ob_eval->data; + gpd_eval->runtime.sbuffer_brush = p->gpd->runtime.sbuffer_brush; + } + else { + gp_update_cache(p->gpd); + } } /* finish off a stroke (clears buffer, but doesn't finish the paint operation) */ @@ -2487,7 +2147,6 @@ static void gp_paint_cleanup(tGPsdata *p) p->gpf->flag &= ~GP_FRAME_PAINT; } } - /* ------------------------------- */ /* Helper callback for drawing the cursor itself */ @@ -2591,10 +2250,7 @@ static void gpencil_draw_exit(bContext *C, wmOperator *op) else { /* drawing batch cache is dirty now */ bGPdata *gpd = CTX_data_gpencil_data(C); - if (gpd) { - gpd->flag &= ~GP_DATA_STROKE_POLYGON; - gp_update_cache(gpd); - } + gp_update_cache(gpd); } /* clear undo stack */ @@ -2665,23 +2321,6 @@ static int gpencil_draw_init(bContext *C, wmOperator *op, const wmEvent *event) /* ------------------------------- */ -/* ensure that the correct cursor icon is set */ -static void gpencil_draw_cursor_set(tGPsdata *p) -{ - UNUSED_VARS(p); - return; - /* Disable while we get a better cursor handling for direct input devices (Cintiq/Ipad)*/ -#if 0 - Brush *brush = p->brush; - if ((p->paintmode == GP_PAINTMODE_ERASER) || (brush->gpencil_tool == GPAINT_TOOL_ERASE)) { - WM_cursor_modal_set(p->win, WM_CURSOR_CROSS); /* XXX need a better cursor */ - } - else { - WM_cursor_modal_set(p->win, WM_CURSOR_NONE); - } -#endif -} - /* update UI indicators of status, including cursor and header prints */ static void gpencil_draw_status_indicators(bContext *C, tGPsdata *p) { @@ -2724,13 +2363,6 @@ static void gpencil_draw_status_indicators(bContext *C, tGPsdata *p) } break; } - case GP_PAINTMODE_DRAW_POLY: { - ED_workspace_status_text( - C, - TIP_("Grease Pencil Poly Session: LMB click to place next stroke vertex | " - "Release Shift/ESC/Enter to end (or click outside this area)")); - break; - } default: /* unhandled future cases */ { ED_workspace_status_text( @@ -2753,88 +2385,6 @@ static void gpencil_draw_status_indicators(bContext *C, tGPsdata *p) /* ------------------------------- */ -/* create a new stroke point at the point indicated by the painting context */ -static void gpencil_draw_apply( - bContext *C, wmOperator *op, tGPsdata *p, Depsgraph *depsgraph, bool is_fake) -{ - bGPdata *gpd = p->gpd; - tGPspoint *pt = NULL; - - /* handle drawing/erasing -> test for erasing first */ - if (p->paintmode == GP_PAINTMODE_ERASER) { - /* do 'live' erasing now */ - gp_stroke_doeraser(p); - - /* store used values */ - copy_v2_v2(p->mvalo, p->mval); - p->opressure = p->pressure; - } - /* Only add current point to buffer if mouse moved - * (even though we got an event, it might be just noise). */ - else if (gp_stroke_filtermval(p, p->mval, p->mvalo)) { - - /* if lazy mouse, interpolate the last and current mouse positions */ - if (GPENCIL_LAZY_MODE(p->brush, p->shift)) { - float now_mouse[2]; - float last_mouse[2]; - copy_v2_v2(now_mouse, p->mval); - copy_v2_v2(last_mouse, p->mvalo); - interp_v2_v2v2(now_mouse, now_mouse, last_mouse, p->brush->smooth_stroke_factor); - copy_v2_v2(p->mval, now_mouse); - } - - /* try to add point */ - short ok = gp_stroke_addpoint(p, p->mval, p->pressure, p->curtime, is_fake); - - /* handle errors while adding point */ - if ((ok == GP_STROKEADD_FULL) || (ok == GP_STROKEADD_OVERFLOW)) { - /* finish off old stroke */ - gp_paint_strokeend(p); - /* And start a new one!!! Else, projection errors! */ - gp_paint_initstroke(p, p->paintmode, depsgraph); - - /* start a new stroke, starting from previous point */ - /* XXX Must manually reset inittime... */ - /* XXX We only need to reuse previous point if overflow! */ - if (ok == GP_STROKEADD_OVERFLOW) { - p->inittime = p->ocurtime; - gp_stroke_addpoint(p, p->mvalo, p->opressure, p->ocurtime, is_fake); - } - else { - p->inittime = p->curtime; - } - gp_stroke_addpoint(p, p->mval, p->pressure, p->curtime, is_fake); - } - else if (ok == GP_STROKEADD_INVALID) { - /* the painting operation cannot continue... */ - BKE_report(op->reports, RPT_ERROR, "Cannot paint stroke"); - p->status = GP_STATUS_ERROR; - - if (G.debug & G_DEBUG) { - printf("Error: Grease-Pencil Paint - Add Point Invalid\n"); - } - return; - } - - /* store used values */ - copy_v2_v2(p->mvalo, p->mval); - p->opressure = p->pressure; - p->ocurtime = p->curtime; - - pt = (tGPspoint *)gpd->runtime.sbuffer + gpd->runtime.sbuffer_used - 1; - if (p->paintmode != GP_PAINTMODE_ERASER) { - ED_gpencil_toggle_brush_cursor(C, true, &pt->x); - } - } - else if ((p->brush->gpencil_settings->flag & GP_BRUSH_STABILIZE_MOUSE_TEMP) && - (gpd->runtime.sbuffer_used > 0)) { - pt = (tGPspoint *)gpd->runtime.sbuffer + gpd->runtime.sbuffer_used - 1; - if (p->paintmode != GP_PAINTMODE_ERASER) { - ED_gpencil_toggle_brush_cursor(C, true, &pt->x); - } - } -} - /* Helper to rotate point around origin */ static void gp_rotate_v2_v2v2fl(float v[2], const float p[2], @@ -2956,66 +2506,151 @@ static void gpencil_speed_guide_init(tGPsdata *p, GP_Sculpt_Guide *guide) } /* apply speed guide */ -static void gpencil_speed_guide(tGPsdata *p, GP_Sculpt_Guide *guide) +static void gpencil_snap_to_guide(const tGPsdata *p, const GP_Sculpt_Guide *guide, float point[2]) { switch (guide->type) { default: case GP_GUIDE_CIRCULAR: { - dist_ensure_v2_v2fl(p->mval, p->guide.origin, p->guide.origin_distance); + dist_ensure_v2_v2fl(point, p->guide.origin, p->guide.origin_distance); break; } case GP_GUIDE_RADIAL: { if (guide->use_snapping && (guide->angle_snap > 0.0f)) { - closest_to_line_v2(p->mval, p->mval, p->guide.rot_point, p->guide.origin); + closest_to_line_v2(point, point, p->guide.rot_point, p->guide.origin); } else { - closest_to_line_v2(p->mval, p->mval, p->mvali, p->guide.origin); + closest_to_line_v2(point, point, p->mvali, p->guide.origin); } break; } case GP_GUIDE_PARALLEL: { - closest_to_line_v2(p->mval, p->mval, p->mvali, p->guide.rot_point); + closest_to_line_v2(point, point, p->mvali, p->guide.rot_point); if (guide->use_snapping && (guide->spacing > 0.0f)) { - gp_snap_to_rotated_grid_fl(p->mval, p->guide.origin, p->guide.spacing, guide->angle); + gp_snap_to_rotated_grid_fl(point, p->guide.origin, p->guide.spacing, guide->angle); } break; } case GP_GUIDE_ISO: { - closest_to_line_v2(p->mval, p->mval, p->mvali, p->guide.rot_point); + closest_to_line_v2(point, point, p->mvali, p->guide.rot_point); if (guide->use_snapping && (guide->spacing > 0.0f)) { - gp_snap_to_rotated_grid_fl(p->mval, p->guide.origin, p->guide.spacing, p->guide.rot_angle); + gp_snap_to_rotated_grid_fl(point, p->guide.origin, p->guide.spacing, p->guide.rot_angle); } break; } case GP_GUIDE_GRID: { if (guide->use_snapping && (guide->spacing > 0.0f)) { - closest_to_line_v2(p->mval, p->mval, p->mvali, p->guide.rot_point); + closest_to_line_v2(point, point, p->mvali, p->guide.rot_point); if (p->straight == STROKE_HORIZONTAL) { - p->mval[1] = gp_snap_to_grid_fl(p->mval[1], p->guide.origin[1], p->guide.spacing); + point[1] = gp_snap_to_grid_fl(point[1], p->guide.origin[1], p->guide.spacing); } else { - p->mval[0] = gp_snap_to_grid_fl(p->mval[0], p->guide.origin[0], p->guide.spacing); + point[0] = gp_snap_to_grid_fl(point[0], p->guide.origin[0], p->guide.spacing); } } else if (p->straight == STROKE_HORIZONTAL) { - p->mval[1] = p->mvali[1]; /* replace y */ + point[1] = p->mvali[1]; /* replace y */ } else { - p->mval[0] = p->mvali[0]; /* replace x */ + point[0] = p->mvali[0]; /* replace x */ } break; } } } +/* create a new stroke point at the point indicated by the painting context */ +static void gpencil_draw_apply(bContext *C, wmOperator *op, tGPsdata *p, Depsgraph *depsgraph) +{ + bGPdata *gpd = p->gpd; + tGPspoint *pt = NULL; + + /* handle drawing/erasing -> test for erasing first */ + if (p->paintmode == GP_PAINTMODE_ERASER) { + /* do 'live' erasing now */ + gp_stroke_doeraser(p); + + /* store used values */ + copy_v2_v2(p->mvalo, p->mval); + p->opressure = p->pressure; + } + /* Only add current point to buffer if mouse moved + * (even though we got an event, it might be just noise). */ + else if (gp_stroke_filtermval(p, p->mval, p->mvalo)) { + + /* if lazy mouse, interpolate the last and current mouse positions */ + if (GPENCIL_LAZY_MODE(p->brush, p->shift)) { + float now_mouse[2]; + float last_mouse[2]; + copy_v2_v2(now_mouse, p->mval); + copy_v2_v2(last_mouse, p->mvalo); + interp_v2_v2v2(now_mouse, now_mouse, last_mouse, p->brush->smooth_stroke_factor); + copy_v2_v2(p->mval, now_mouse); + + GP_Sculpt_Guide *guide = &p->scene->toolsettings->gp_sculpt.guide; + bool is_speed_guide = ((guide->use_guide) && + (p->brush && (p->brush->gpencil_tool == GPAINT_TOOL_DRAW))); + if (is_speed_guide) { + gpencil_snap_to_guide(p, guide, p->mval); + } + } + + /* try to add point */ + short ok = gp_stroke_addpoint(p, p->mval, p->pressure, p->curtime); + + /* handle errors while adding point */ + if ((ok == GP_STROKEADD_FULL) || (ok == GP_STROKEADD_OVERFLOW)) { + /* finish off old stroke */ + gp_paint_strokeend(p); + /* And start a new one!!! Else, projection errors! */ + gp_paint_initstroke(p, p->paintmode, depsgraph); + + /* start a new stroke, starting from previous point */ + /* XXX Must manually reset inittime... */ + /* XXX We only need to reuse previous point if overflow! */ + if (ok == GP_STROKEADD_OVERFLOW) { + p->inittime = p->ocurtime; + gp_stroke_addpoint(p, p->mvalo, p->opressure, p->ocurtime); + } + else { + p->inittime = p->curtime; + } + gp_stroke_addpoint(p, p->mval, p->pressure, p->curtime); + } + else if (ok == GP_STROKEADD_INVALID) { + /* the painting operation cannot continue... */ + BKE_report(op->reports, RPT_ERROR, "Cannot paint stroke"); + p->status = GP_STATUS_ERROR; + + if (G.debug & G_DEBUG) { + printf("Error: Grease-Pencil Paint - Add Point Invalid\n"); + } + return; + } + + /* store used values */ + copy_v2_v2(p->mvalo, p->mval); + p->opressure = p->pressure; + p->ocurtime = p->curtime; + + pt = (tGPspoint *)gpd->runtime.sbuffer + gpd->runtime.sbuffer_used - 1; + if (p->paintmode != GP_PAINTMODE_ERASER) { + ED_gpencil_toggle_brush_cursor(C, true, &pt->x); + } + } + else if ((p->brush->gpencil_settings->flag & GP_BRUSH_STABILIZE_MOUSE_TEMP) && + (gpd->runtime.sbuffer_used > 0)) { + pt = (tGPspoint *)gpd->runtime.sbuffer + gpd->runtime.sbuffer_used - 1; + if (p->paintmode != GP_PAINTMODE_ERASER) { + ED_gpencil_toggle_brush_cursor(C, true, &pt->x); + } + } +} + /* handle draw event */ static void gpencil_draw_apply_event(bContext *C, wmOperator *op, const wmEvent *event, - Depsgraph *depsgraph, - float x, - float y, - const bool is_fake) + Depsgraph *depsgraph) { tGPsdata *p = op->customdata; GP_Sculpt_Guide *guide = &p->scene->toolsettings->gp_sculpt.guide; @@ -3025,13 +2660,12 @@ static void gpencil_draw_apply_event(bContext *C, (p->brush && (p->brush->gpencil_tool == GPAINT_TOOL_DRAW))); /* convert from window-space to area-space mouse coordinates - * add any x,y override position for fake events + * add any x,y override position */ - p->mval[0] = (float)event->mval[0] - x; - p->mval[1] = (float)event->mval[1] - y; + copy_v2fl_v2i(p->mval, event->mval); p->shift = event->shift; - /* verify direction for straight lines */ + /* verify direction for straight lines and guides */ if ((is_speed_guide) || ((event->alt > 0) && (RNA_boolean_get(op->ptr, "disable_straight") == false))) { if (p->straight == 0) { @@ -3099,12 +2733,12 @@ static void gpencil_draw_apply_event(bContext *C, p->flags &= ~GP_PAINTFLAG_FIRSTRUN; /* set values */ - copy_v2_v2(p->mvalo, p->mval); p->opressure = p->pressure; p->inittime = p->ocurtime = p->curtime; p->straight = 0; /* save initial mouse */ + copy_v2_v2(p->mvalo, p->mval); copy_v2_v2(p->mvali, p->mval); if (is_speed_guide && !ELEM(p->paintmode, GP_PAINTMODE_ERASER, GP_PAINTMODE_SET_CP) && @@ -3120,7 +2754,7 @@ static void gpencil_draw_apply_event(bContext *C, } /* wait for vector then add initial point */ - if (is_speed_guide && p->flags & GP_PAINTFLAG_REQ_VECTOR) { + if (is_speed_guide && (p->flags & GP_PAINTFLAG_REQ_VECTOR)) { if (p->straight == 0) { return; } @@ -3154,23 +2788,13 @@ static void gpencil_draw_apply_event(bContext *C, p->mvali, (p->straight == STROKE_VERTICAL) ? M_PI_2 : 0.0f); } - - /* create fake events */ - float tmp[2]; - copy_v2_v2(tmp, p->mval); - gpencil_draw_apply_event(C, op, event, depsgraph, pt[0], pt[1], false); - if (len_v2v2(p->mval, p->mvalo)) { - sub_v2_v2v2(pt, p->mval, p->mvalo); - gpencil_draw_apply_event(C, op, event, depsgraph, pt[0], pt[1], false); - } - copy_v2_v2(p->mval, tmp); } /* check if stroke is straight or guided */ if ((p->paintmode != GP_PAINTMODE_ERASER) && ((p->straight) || (is_speed_guide))) { /* guided stroke */ if (is_speed_guide) { - gpencil_speed_guide(p, guide); + gpencil_snap_to_guide(p, guide, p->mval); } else if (p->straight == STROKE_HORIZONTAL) { p->mval[1] = p->mvali[1]; /* replace y */ @@ -3192,7 +2816,7 @@ static void gpencil_draw_apply_event(bContext *C, RNA_float_set(&itemptr, "time", p->curtime - p->inittime); /* apply the current latest drawing point */ - gpencil_draw_apply(C, op, p, depsgraph, is_fake); + gpencil_draw_apply(C, op, p, depsgraph); /* force refresh */ /* just active area for now, since doing whole screen is too slow */ @@ -3207,28 +2831,21 @@ static int gpencil_draw_exec(bContext *C, wmOperator *op) tGPsdata *p = NULL; Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C); - /* printf("GPencil - Starting Re-Drawing\n"); */ - /* try to initialize context data needed while drawing */ if (!gpencil_draw_init(C, op, NULL)) { MEM_SAFE_FREE(op->customdata); - /* printf("\tGP - no valid data\n"); */ return OPERATOR_CANCELLED; } else { p = op->customdata; } - /* printf("\tGP - Start redrawing stroke\n"); */ - /* loop over the stroke RNA elements recorded (i.e. progress of mouse movement), * setting the relevant values in context at each step, then applying */ RNA_BEGIN (op->ptr, itemptr, "stroke") { float mousef[2]; - /* printf("\t\tGP - stroke elem\n"); */ - /* get relevant data for this point from stroke */ RNA_float_get_array(&itemptr, "mouse", mousef); p->mval[0] = mousef[0]; @@ -3258,12 +2875,10 @@ static int gpencil_draw_exec(bContext *C, wmOperator *op) } /* apply this data as necessary now (as per usual) */ - gpencil_draw_apply(C, op, p, depsgraph, false); + gpencil_draw_apply(C, op, p, depsgraph); } RNA_END; - /* printf("\tGP - done\n"); */ - /* cleanup */ gpencil_draw_exit(C, op); @@ -3400,9 +3015,9 @@ static int gpencil_draw_invoke(bContext *C, wmOperator *op, const wmEvent *event else { /* don't erase empty frames */ bool has_layer_to_erase = false; - for (bGPDlayer *gpl = gpd->layers.first; gpl; gpl = gpl->next) { + LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd->layers) { /* Skip if layer not editable */ - if (gpencil_layer_is_editable(gpl)) { + if (BKE_gpencil_layer_is_editable(gpl)) { if (gpl->actframe && gpl->actframe->strokes.first) { has_layer_to_erase = true; break; @@ -3430,35 +3045,25 @@ static int gpencil_draw_invoke(bContext *C, wmOperator *op, const wmEvent *event } /* TODO: set any additional settings that we can take from the events? - * TODO? if tablet is erasing, force eraser to be on? */ - - /* if eraser is on, draw radial aid */ + * if eraser is on, draw radial aid */ if (p->paintmode == GP_PAINTMODE_ERASER) { gpencil_draw_toggle_eraser_cursor(C, p, true); } else { ED_gpencil_toggle_brush_cursor(C, true, NULL); } - /* set cursor - * NOTE: This may change later (i.e. intentionally via brush toggle, - * or unintentionally if the user scrolls outside the area)... - */ - gpencil_draw_cursor_set(p); /* only start drawing immediately if we're allowed to do so... */ if (RNA_boolean_get(op->ptr, "wait_for_input") == false) { /* hotkey invoked - start drawing */ - /* printf("\tGP - set first spot\n"); */ p->status = GP_STATUS_PAINTING; /* handle the initial drawing - i.e. for just doing a simple dot */ - gpencil_draw_apply_event( - C, op, event, CTX_data_ensure_evaluated_depsgraph(C), 0.0f, 0.0f, false); + gpencil_draw_apply_event(C, op, event, CTX_data_ensure_evaluated_depsgraph(C)); op->flag |= OP_IS_MODAL_CURSOR_REGION; } else { /* toolbar invoked - don't start drawing yet... */ - /* printf("\tGP - hotkey invoked... waiting for click-drag\n"); */ op->flag |= OP_IS_MODAL_CURSOR_REGION; } @@ -3510,12 +3115,7 @@ static tGPsdata *gpencil_stroke_begin(bContext *C, wmOperator *op) p->status = GP_STATUS_ERROR; } - /* printf("\t\tGP - start stroke\n"); */ - /* 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 */ - if (gp_session_initdata(C, op, p)) { gp_paint_initstroke(p, p->paintmode, CTX_data_depsgraph_pointer(C)); } @@ -3528,134 +3128,222 @@ static tGPsdata *gpencil_stroke_begin(bContext *C, wmOperator *op) return op->customdata; } -static void gpencil_stroke_end(wmOperator *op) +/* Add arc points between two mouse events using the previous segment to determine the vertice of + * the arc. + * /+ CTL + * / | + * / | + * PtA +...|...+ PtB + * / + * / + * + PtA - 1 + * / + * CTL is the vertice of the triangle created between PtA and PtB */ +static void gpencil_add_arc_points(tGPsdata *p, float mval[2], int segments) { - tGPsdata *p = op->customdata; + bGPdata *gpd = p->gpd; + if (gpd->runtime.sbuffer_used < 3) { + return; + } + + int idx_prev = gpd->runtime.sbuffer_used; + + /* Add space for new arc points. */ + gpd->runtime.sbuffer_used += segments - 1; + + /* Check if still room in buffer or add more. */ + gpd->runtime.sbuffer = ED_gpencil_sbuffer_ensure( + gpd->runtime.sbuffer, &gpd->runtime.sbuffer_size, &gpd->runtime.sbuffer_used, false); + + tGPspoint *points = (tGPspoint *)gpd->runtime.sbuffer; + tGPspoint *pt = NULL; + tGPspoint *pt_before = &points[idx_prev - 1]; /* current - 2 */ + tGPspoint *pt_prev = &points[idx_prev - 2]; /* previous */ + + /* Create two vectors, previous and half way of the actual to get the vertex of the triangle + * for arc curve. + */ + float v_prev[2], v_cur[2], v_half[2]; + sub_v2_v2v2(v_cur, mval, &pt_prev->x); + + sub_v2_v2v2(v_prev, &pt_prev->x, &pt_before->x); + interp_v2_v2v2(v_half, &pt_prev->x, mval, 0.5f); + sub_v2_v2(v_half, &pt_prev->x); + + /* If angle is too sharp undo all changes and return. */ + const float min_angle = DEG2RADF(120.0f); + float angle = angle_v2v2(v_prev, v_half); + if (angle < min_angle) { + gpd->runtime.sbuffer_used -= segments - 1; + return; + } + + /* Project the half vector to the previous vector and calculate the mid projected point. */ + float dot = dot_v2v2(v_prev, v_half); + float l = len_squared_v2(v_prev); + if (l > 0.0f) { + mul_v2_fl(v_prev, dot / l); + } - gp_paint_cleanup(p); + /* Calc the position of the control point. */ + float ctl[2]; + add_v2_v2v2(ctl, &pt_prev->x, v_prev); - gpencil_undo_push(p->gpd); + float step = M_PI_2 / (float)(segments + 1); + float a = step; - gp_session_cleanup(p); + float midpoint[2], start[2], end[2], cp1[2], corner[2]; + mid_v2_v2v2(midpoint, &pt_prev->x, mval); + copy_v2_v2(start, &pt_prev->x); + copy_v2_v2(end, mval); + copy_v2_v2(cp1, ctl); - p->status = GP_STATUS_IDLING; - op->flag |= OP_IS_MODAL_CURSOR_REGION; + corner[0] = midpoint[0] - (cp1[0] - midpoint[0]); + corner[1] = midpoint[1] - (cp1[1] - midpoint[1]); - p->gpd = NULL; - p->gpl = NULL; - p->gpf = NULL; + for (int i = 0; i < segments; i++) { + pt = &points[idx_prev + i - 1]; + pt->x = corner[0] + (end[0] - corner[0]) * sinf(a) + (start[0] - corner[0]) * cosf(a); + pt->y = corner[1] + (end[1] - corner[1]) * sinf(a) + (start[1] - corner[1]) * cosf(a); + + /* Set pressure and strength equals to previous. It will be smoothed later. */ + pt->pressure = pt_prev->pressure; + pt->strength = pt_prev->strength; + + a += step; + } } -/* Move last stroke in the listbase to the head - * to be drawn below all previous strokes in the layer. */ -static void gpencil_move_last_stroke_to_back(bContext *C) +static void gpencil_add_guide_points(const tGPsdata *p, + const GP_Sculpt_Guide *guide, + const float start[2], + const float end[2], + int segments) { - /* Move last stroke (the polygon) to head of the listbase stroke - * to draw on back of all previous strokes. */ - bGPdata *gpd = ED_gpencil_data_get_active(C); - bGPDlayer *gpl = BKE_gpencil_layer_getactive(gpd); - - /* sanity checks */ - if (ELEM(NULL, gpd, gpl, gpl->actframe)) { + bGPdata *gpd = p->gpd; + if ((gpd->runtime.sbuffer_used == 0)) { return; } - bGPDframe *gpf = gpl->actframe; - bGPDstroke *gps = gpf->strokes.last; - if (ELEM(NULL, gps)) { - return; + int idx_prev = gpd->runtime.sbuffer_used; + + /* Add space for new points. */ + gpd->runtime.sbuffer_used += segments - 1; + + /* Check if still room in buffer or add more. */ + gpd->runtime.sbuffer = ED_gpencil_sbuffer_ensure( + gpd->runtime.sbuffer, &gpd->runtime.sbuffer_size, &gpd->runtime.sbuffer_used, false); + + tGPspoint *points = (tGPspoint *)gpd->runtime.sbuffer; + tGPspoint *pt = NULL; + tGPspoint *pt_before = &points[idx_prev - 1]; + + /* Use arc sampling for circular guide */ + if (guide->type == GP_GUIDE_CIRCULAR) { + float cw = cross_tri_v2(start, p->guide.origin, end); + float angle = angle_v2v2v2(start, p->guide.origin, end); + + float step = angle / (float)(segments + 1); + if (cw < 0.0f) { + step = -step; + } + + float a = step; + + for (int i = 0; i < segments; i++) { + pt = &points[idx_prev + i - 1]; + + gp_rotate_v2_v2v2fl(&pt->x, start, p->guide.origin, -a); + gpencil_snap_to_guide(p, guide, &pt->x); + a += step; + + /* Set pressure and strength equals to previous. It will be smoothed later. */ + pt->pressure = pt_before->pressure; + pt->strength = pt_before->strength; + } } + else { + float step = 1.0f / (float)(segments + 1); + float a = step; + + for (int i = 0; i < segments; i++) { + pt = &points[idx_prev + i - 1]; + + interp_v2_v2v2(&pt->x, start, end, a); + gpencil_snap_to_guide(p, guide, &pt->x); + a += step; - BLI_remlink(&gpf->strokes, gps); - BLI_insertlinkbefore(&gpf->strokes, gpf->strokes.first, gps); + /* Set pressure and strength equals to previous. It will be smoothed later. */ + pt->pressure = pt_before->pressure; + pt->strength = pt_before->strength; + } + } } -/* Add fake events for missing mouse movements when the artist draw very fast */ -static bool gpencil_add_fake_events(bContext *C, wmOperator *op, const wmEvent *event, tGPsdata *p) +/* Add fake points for missing mouse movements when the artist draw very fast creating an arc + * with the vertice in the midle of the segment and using the angle of the previous segment. */ +static void gpencil_add_fake_points(const wmEvent *event, tGPsdata *p) { Brush *brush = p->brush; + /* Lazy mode do not use fake events. */ + if (GPENCIL_LAZY_MODE(brush, p->shift)) { + return; + } + GP_Sculpt_Guide *guide = &p->scene->toolsettings->gp_sculpt.guide; - Depsgraph *depsgraph = CTX_data_depsgraph_pointer(C); int input_samples = brush->gpencil_settings->input_samples; - bool added_events = false; - /* ensure sampling when using circular guide */ - if (guide->use_guide && (guide->type == GP_GUIDE_CIRCULAR)) { + bool is_speed_guide = ((guide->use_guide) && + (p->brush && (p->brush->gpencil_tool == GPAINT_TOOL_DRAW))); + + /* TODO: ensure sampling enough points when using circular guide, + but the arc must be around the center. (see if above to check other guides only) + */ + if (is_speed_guide && (guide->type == GP_GUIDE_CIRCULAR)) { input_samples = GP_MAX_INPUT_SAMPLES; } if (input_samples == 0) { - return added_events; + return; } - RegionView3D *rv3d = p->region->regiondata; - float defaultpixsize = rv3d->pixsize * 1000.0f; - int samples = (GP_MAX_INPUT_SAMPLES - input_samples + 1); - float thickness = (float)brush->size; + int samples = GP_MAX_INPUT_SAMPLES - input_samples + 1; - float pt[2], a[2], b[2]; - float vec[3]; - float scale = 1.0f; + float mouse_prv[2], mouse_cur[2]; + float min_dist = 4.0f * samples; - /* get pixel scale */ - gp_get_3d_reference(p, vec); - mul_m4_v3(rv3d->persmat, vec); - if (rv3d->is_persp) { - scale = vec[2] * defaultpixsize; - } - else { - scale = defaultpixsize; - } + copy_v2_v2(mouse_prv, p->mvalo); + copy_v2fl_v2i(mouse_cur, event->mval); - /* The thickness of the brush is reduced of thickness to get overlap dots */ - float dot_factor = 0.50f; - if (samples < 2) { - dot_factor = 0.05f; - } - else if (samples < 4) { - dot_factor = 0.10f; - } - else if (samples < 7) { - dot_factor = 0.3f; - } - else if (samples < 10) { - dot_factor = 0.4f; + /* get distance in pixels */ + float dist = len_v2v2(mouse_prv, mouse_cur); + + /* get distance for circular guide */ + if (is_speed_guide && (guide->type == GP_GUIDE_CIRCULAR)) { + float middle[2]; + gpencil_snap_to_guide(p, guide, mouse_prv); + gpencil_snap_to_guide(p, guide, mouse_cur); + mid_v2_v2v2(middle, mouse_cur, mouse_prv); + gpencil_snap_to_guide(p, guide, middle); + dist = len_v2v2(mouse_prv, middle) + len_v2v2(middle, mouse_cur); } - float factor = ((thickness * dot_factor) / scale) * samples; - copy_v2_v2(a, p->mvalo); - b[0] = (float)event->mval[0] + 1.0f; - b[1] = (float)event->mval[1] + 1.0f; + if ((dist > 3.0f) && (dist > min_dist)) { + int slices = (dist / min_dist) + 1; - /* get distance in pixels */ - float dist = len_v2v2(a, b); - - /* for very small distances, add a half way point */ - if (dist <= 2.0f) { - interp_v2_v2v2(pt, a, b, 0.5f); - sub_v2_v2v2(pt, b, pt); - /* create fake event */ - gpencil_draw_apply_event(C, op, event, depsgraph, pt[0], pt[1], true); - added_events = true; - } - else if (dist >= factor) { - int slices = 2 + (int)((dist - 1.0) / factor); - float n = 1.0f / slices; - for (int i = 1; i < slices; i++) { - interp_v2_v2v2(pt, a, b, n * i); - sub_v2_v2v2(pt, b, pt); - /* create fake event */ - gpencil_draw_apply_event(C, op, event, depsgraph, pt[0], pt[1], true); - added_events = true; - } - } - return added_events; + if (is_speed_guide) { + gpencil_add_guide_points(p, guide, mouse_prv, mouse_cur, slices); + } + else { + gpencil_add_arc_points(p, mouse_cur, slices); + } + } } /* events handling during interactive drawing part of operator */ static int gpencil_draw_modal(bContext *C, wmOperator *op, const wmEvent *event) { tGPsdata *p = op->customdata; - ToolSettings *ts = CTX_data_tool_settings(C); + // ToolSettings *ts = CTX_data_tool_settings(C); GP_Sculpt_Guide *guide = &p->scene->toolsettings->gp_sculpt.guide; /* default exit state - pass through to support MMB view nav, etc. */ @@ -3754,98 +3442,26 @@ static int gpencil_draw_modal(bContext *C, wmOperator *op, const wmEvent *event) } } - // printf("\tGP - handle modal event...\n"); - /* Exit painting mode (and/or end current stroke). * - * NOTE: cannot do RIGHTMOUSE (as is standard for canceling) - * as that would break polyline T32647. */ - /* if polyline and release shift must cancel */ - if ((ELEM(event->type, RETKEY, PADENTER, ESCKEY, SPACEKEY, EKEY)) || - ((p->paintmode == GP_PAINTMODE_DRAW_POLY) && (event->shift == 0))) { - /* exit() ends the current stroke before cleaning up */ - /* printf("\t\tGP - end of paint op + end of stroke\n"); */ - /* if drawing polygon and enable on back, must move stroke */ - if (ts) { - if ((ts->gpencil_flags & GP_TOOL_FLAG_PAINT_ONBACK) && - (p->paintmode == GP_PAINTMODE_DRAW_POLY)) { - if (p->flags & GP_PAINTFLAG_STROKEADDED) { - gpencil_move_last_stroke_to_back(C); - } - } - } + if (ELEM(event->type, RETKEY, PADENTER, ESCKEY, SPACEKEY, EKEY)) { p->status = GP_STATUS_DONE; estate = OPERATOR_FINISHED; } /* toggle painting mode upon mouse-button movement - * - LEFTMOUSE = standard drawing (all) / straight line drawing (all) / polyline (toolbox - * only) - * - RIGHTMOUSE = polyline (hotkey) / eraser (all) + * - LEFTMOUSE = standard drawing (all) / straight line drawing (all) + * - RIGHTMOUSE = eraser (all) * (Disabling RIGHTMOUSE case here results in bugs like [#32647]) * also making sure we have a valid event value, to not exit too early */ if (ELEM(event->type, LEFTMOUSE, RIGHTMOUSE) && (ELEM(event->val, KM_PRESS, KM_RELEASE))) { /* if painting, end stroke */ if (p->status == GP_STATUS_PAINTING) { - int sketch = 0; - - /* basically, this should be mouse-button up = end stroke - * BUT, polyline drawing is an exception -- all knots should be added during one session - */ - sketch |= (p->paintmode == GP_PAINTMODE_DRAW_POLY); - - if (sketch) { - /* end stroke only, and then wait to resume painting soon */ - /* printf("\t\tGP - end stroke only\n"); */ - gpencil_stroke_end(op); - - /* If eraser mode is on, turn it off after the stroke finishes - * NOTE: This just makes it nicer to work with drawing sessions - */ - if (p->paintmode == GP_PAINTMODE_ERASER) { - p->paintmode = RNA_enum_get(op->ptr, "mode"); - - /* if the original mode was *still* eraser, - * we'll let it say for now, since this gives - * users an opportunity to have visual feedback - * when adjusting eraser size - */ - if (p->paintmode != GP_PAINTMODE_ERASER) { - /* turn off cursor... - * NOTE: this should be enough for now - * Just hiding this makes it seem like - * you can paint again... - */ - gpencil_draw_toggle_eraser_cursor(C, p, false); - } - } - - /* we've just entered idling state, so this event was processed (but no others yet) */ - estate = OPERATOR_RUNNING_MODAL; - - /* stroke could be smoothed, send notifier to refresh screen */ - WM_event_add_notifier(C, NC_GPENCIL | NA_EDITED, NULL); - } - else { - /* printf("\t\tGP - end of stroke + op\n"); */ - /* if drawing polygon and enable on back, must move stroke */ - if (ts) { - if ((ts->gpencil_flags & GP_TOOL_FLAG_PAINT_ONBACK) && - (p->paintmode == GP_PAINTMODE_DRAW_POLY)) { - if (p->flags & GP_PAINTFLAG_STROKEADDED) { - gpencil_move_last_stroke_to_back(C); - } - } - } - /* drawing batch cache is dirty now */ - gp_update_cache(p->gpd); - - p->status = GP_STATUS_DONE; - estate = OPERATOR_FINISHED; - } + p->status = GP_STATUS_DONE; + estate = OPERATOR_FINISHED; } else if (event->val == KM_PRESS) { bool in_bounds = false; @@ -3929,15 +3545,6 @@ static int gpencil_draw_modal(bContext *C, wmOperator *op, const wmEvent *event) * NOTE: Don't enter this case if an error occurred while finding the * region (as above) */ - /* if drawing polygon and enable on back, must move stroke */ - if (ts) { - if ((ts->gpencil_flags & GP_TOOL_FLAG_PAINT_ONBACK) && - (p->paintmode == GP_PAINTMODE_DRAW_POLY)) { - if (p->flags & GP_PAINTFLAG_STROKEADDED) { - gpencil_move_last_stroke_to_back(C); - } - } - } p->status = GP_STATUS_DONE; estate = OPERATOR_FINISHED; } @@ -3954,25 +3561,24 @@ static int gpencil_draw_modal(bContext *C, wmOperator *op, const wmEvent *event) /* handle painting mouse-movements? */ if (ELEM(event->type, MOUSEMOVE, INBETWEEN_MOUSEMOVE) || (p->flags & GP_PAINTFLAG_FIRSTRUN)) { /* handle drawing event */ - /* printf("\t\tGP - add point\n"); */ + bool is_speed_guide = ((guide->use_guide) && + (p->brush && (p->brush->gpencil_tool == GPAINT_TOOL_DRAW))); int size_before = p->gpd->runtime.sbuffer_used; - bool added_events = false; - if (((p->flags & GP_PAINTFLAG_FIRSTRUN) == 0) && (p->paintmode != GP_PAINTMODE_ERASER)) { - added_events = gpencil_add_fake_events(C, op, event, p); + if (((p->flags & GP_PAINTFLAG_FIRSTRUN) == 0) && (p->paintmode != GP_PAINTMODE_ERASER) && + !(is_speed_guide && (p->flags & GP_PAINTFLAG_REQ_VECTOR))) { + gpencil_add_fake_points(event, p); } - gpencil_draw_apply_event(C, op, event, CTX_data_depsgraph_pointer(C), 0.0f, 0.0f, false); + gpencil_draw_apply_event(C, op, event, CTX_data_depsgraph_pointer(C)); int size_after = p->gpd->runtime.sbuffer_used; - /* Last point of the event is always real (not fake). */ - tGPspoint *points = (tGPspoint *)p->gpd->runtime.sbuffer; - tGPspoint *pt = &points[size_after - 1]; - pt->tflag &= ~GP_TPOINT_FAKE; - - /* Smooth the fake events to get smoother strokes, specially at ends. */ - if (added_events) { - gp_smooth_fake_events(p, size_before, size_after); + /* Smooth segments if some fake points were added (need loop to get cumulative smooth). + * the 0.15 value gets a good result in Windows and Linux. */ + if (!is_speed_guide && (size_after - size_before > 1)) { + for (int r = 0; r < 5; r++) { + gp_smooth_segment(p->gpd, 0.15f, size_before - 1, size_after - 1); + } } /* finish painting operation if anything went wrong just now */ @@ -3982,17 +3588,13 @@ static int gpencil_draw_modal(bContext *C, wmOperator *op, const wmEvent *event) } else { /* event handled, so just tag as running modal */ - /* printf("\t\t\t\tGP - add point handled!\n"); */ estate = OPERATOR_RUNNING_MODAL; } } /* eraser size */ else if ((p->paintmode == GP_PAINTMODE_ERASER) && ELEM(event->type, WHEELUPMOUSE, WHEELDOWNMOUSE, PADPLUSKEY, PADMINUS)) { - /* just resize the brush (local version) - * TODO: fix the hardcoded size jumps (set to make a visible difference) and hardcoded keys - */ - /* printf("\t\tGP - resize eraser\n"); */ + /* Just resize the brush (local version). */ switch (event->type) { case WHEELDOWNMOUSE: /* larger */ case PADPLUSKEY: @@ -4032,7 +3634,6 @@ static int gpencil_draw_modal(bContext *C, wmOperator *op, const wmEvent *event) else { /* update status indicators - cursor, header, etc. */ gpencil_draw_status_indicators(C, p); - gpencil_draw_cursor_set(p); /* cursor may have changed outside our control - T44084 */ } /* process last operations before exiting */ @@ -4056,12 +3657,6 @@ static int gpencil_draw_modal(bContext *C, wmOperator *op, const wmEvent *event) case OPERATOR_RUNNING_MODAL | OPERATOR_PASS_THROUGH: /* event doesn't need to be handled */ -#if 0 - printf("unhandled event -> %d (mmb? = %d | mmv? = %d)\n", - event->type, - event->type == MIDDLEMOUSE, - event->type == MOUSEMOVE); -#endif break; } @@ -4078,11 +3673,6 @@ static const EnumPropertyItem prop_gpencil_drawmodes[] = { 0, "Draw Straight Lines", "Draw straight line segment(s)"}, - {GP_PAINTMODE_DRAW_POLY, - "DRAW_POLY", - 0, - "Draw Poly Line", - "Click to place endpoints of straight line segments (connected)"}, {GP_PAINTMODE_ERASER, "ERASER", 0, "Eraser", "Erase Grease Pencil strokes"}, {0, NULL, 0, NULL, NULL}, }; @@ -4094,7 +3684,7 @@ void GPENCIL_OT_draw(wmOperatorType *ot) /* identifiers */ ot->name = "Grease Pencil Draw"; ot->idname = "GPENCIL_OT_draw"; - ot->description = "Draw a new stroke in the active Grease Pencil Object"; + ot->description = "Draw mouse_prv new stroke in the active Grease Pencil Object"; /* api callbacks */ ot->exec = gpencil_draw_exec; diff --git a/source/blender/editors/gpencil/gpencil_primitive.c b/source/blender/editors/gpencil/gpencil_primitive.c index ad54382dde7..96522d1750c 100644 --- a/source/blender/editors/gpencil/gpencil_primitive.c +++ b/source/blender/editors/gpencil/gpencil_primitive.c @@ -122,6 +122,10 @@ static void gp_session_validatebuffer(tGPDprimitive *p) gpd->runtime.sbuffer_sflag = 0; gpd->runtime.sbuffer_sflag |= GP_STROKE_3DSPACE; + /* Set vertex colors for buffer. */ + ED_gpencil_sbuffer_vertex_color_set( + p->depsgraph, p->ob, p->scene->toolsettings, p->brush, p->material); + if (ELEM(p->type, GP_STROKE_BOX, GP_STROKE_CIRCLE)) { gpd->runtime.sbuffer_sflag |= GP_STROKE_CYCLIC; } @@ -132,34 +136,11 @@ static void gp_init_colors(tGPDprimitive *p) bGPdata *gpd = p->gpd; Brush *brush = p->brush; - MaterialGPencilStyle *gp_style = NULL; - /* use brush material */ - p->mat = BKE_gpencil_object_material_ensure_from_active_input_brush(p->bmain, p->ob, brush); - - /* assign color information to temp data */ - gp_style = p->mat->gp_style; - if (gp_style) { - - /* set colors */ - if (gp_style->flag & GP_STYLE_STROKE_SHOW) { - copy_v4_v4(gpd->runtime.scolor, gp_style->stroke_rgba); - } - else { - /* if no stroke, use fill */ - copy_v4_v4(gpd->runtime.scolor, gp_style->fill_rgba); - } - - copy_v4_v4(gpd->runtime.sfill, gp_style->fill_rgba); - /* add some alpha to make easy the filling without hide strokes */ - if (gpd->runtime.sfill[3] > 0.8f) { - gpd->runtime.sfill[3] = 0.8f; - } + p->material = BKE_gpencil_object_material_ensure_from_active_input_brush(p->bmain, p->ob, brush); - gpd->runtime.mode = (short)gp_style->mode; - gpd->runtime.bstroke_style = gp_style->stroke_style; - gpd->runtime.bfill_style = gp_style->fill_style; - } + gpd->runtime.matid = BKE_object_material_slot_find_index(p->ob, p->material); + gpd->runtime.sbuffer_brush = brush; } /* Helper to square a primitive */ @@ -254,18 +235,6 @@ static void gp_primitive_update_cps(tGPDprimitive *tgpi) } } -/* Helper to reflect point */ -static void UNUSED_FUNCTION(gp_reflect_point_v2_v2v2v2)(float va[2], - const float p[2], - const float a[2], - const float b[2]) -{ - float point[2]; - closest_to_line_v2(point, p, a, b); - va[0] = point[0] - (p[0] - point[0]); - va[1] = point[1] - (p[1] - point[1]); -} - /* Poll callback for primitive operators */ static bool gpencil_primitive_add_poll(bContext *C) { @@ -294,7 +263,7 @@ static bool gpencil_primitive_add_poll(bContext *C) /* don't allow operator to function if the active layer is locked/hidden * (BUT, if there isn't an active layer, we are free to add new layer when the time comes) */ - bGPDlayer *gpl = BKE_gpencil_layer_getactive(gpd); + bGPDlayer *gpl = BKE_gpencil_layer_active_get(gpd); if ((gpl) && (gpl->flag & (GP_LAYER_LOCKED | GP_LAYER_HIDE))) { CTX_wm_operator_poll_msg_set(C, "Primitives cannot be added as active layer is locked or hidden"); @@ -322,6 +291,8 @@ static void gpencil_primitive_allocate_memory(tGPDprimitive *tgpi) static void gp_primitive_set_initdata(bContext *C, tGPDprimitive *tgpi) { Scene *scene = CTX_data_scene(C); + ToolSettings *ts = scene->toolsettings; + Brush *brush = tgpi->brush; int cfra = CFRA; bGPDlayer *gpl = CTX_data_active_gpencil_layer(C); @@ -339,13 +310,15 @@ static void gp_primitive_set_initdata(bContext *C, tGPDprimitive *tgpi) /* create new temp stroke */ bGPDstroke *gps = MEM_callocN(sizeof(bGPDstroke), "Temp bGPDstroke"); gps->thickness = 2.0f; - gps->gradient_f = 1.0f; - gps->gradient_s[0] = 1.0f; - gps->gradient_s[1] = 1.0f; + gps->fill_opacity_fac = 1.0f; + gps->hardeness = 1.0f; + copy_v2_fl(gps->aspect_ratio, 1.0f); + gps->uv_scale = 1.0f; gps->inittime = 0.0f; - /* enable recalculation flag by default */ - gps->flag |= GP_STROKE_RECALC_GEOMETRY; + /* Apply the vertex color to fill. */ + ED_gpencil_fill_vertex_color_set(ts, brush, gps); + gps->flag &= ~GP_STROKE_SELECT; /* the polygon must be closed, so enabled cyclic */ if (ELEM(tgpi->type, GP_STROKE_BOX, GP_STROKE_CIRCLE)) { @@ -367,10 +340,11 @@ static void gp_primitive_set_initdata(bContext *C, tGPDprimitive *tgpi) /* allocate memory for storage points, but keep empty */ gps->totpoints = 0; gps->points = MEM_callocN(sizeof(bGPDspoint), "gp_stroke_points"); + gps->dvert = NULL; + /* initialize triangle memory to dummy data */ gps->tot_triangles = 0; gps->triangles = NULL; - gps->flag |= GP_STROKE_RECALC_GEOMETRY; /* add to strokes */ BLI_addtail(&tgpi->gpf->strokes, gps); @@ -381,6 +355,8 @@ static void gp_primitive_set_initdata(bContext *C, tGPDprimitive *tgpi) /* Random generator, only init once. */ uint rng_seed = (uint)(PIL_check_seconds_timer_i() & UINT_MAX); tgpi->rng = BLI_rng_new(rng_seed); + + DEG_id_tag_update(&tgpi->gpd->id, ID_RECALC_COPY_ON_WRITE); } /* add new segment to curve */ @@ -898,7 +874,6 @@ static void gp_primitive_update_strokes(bContext *C, tGPDprimitive *tgpi) if (brush->gpencil_settings->flag & GP_BRUSH_USE_JITTER_PRESSURE) { jitter = BKE_curvemapping_evaluateF( brush->gpencil_settings->curve_jitter, 0, curve_pressure); - jitter *= brush->gpencil_settings->draw_sensitivity; } else { jitter = brush->gpencil_settings->draw_jitter; @@ -934,10 +909,10 @@ static void gp_primitive_update_strokes(bContext *C, tGPDprimitive *tgpi) if ((brush->gpencil_settings->flag & GP_BRUSH_GROUP_RANDOM) && (brush->gpencil_settings->draw_random_press > 0.0f)) { if (p2d->rnd[0] > 0.5f) { - pressure -= brush->gpencil_settings->draw_random_press * p2d->rnd[1]; + pressure -= (brush->gpencil_settings->draw_random_press * 2.0f) * p2d->rnd[1]; } else { - pressure += brush->gpencil_settings->draw_random_press * p2d->rnd[2]; + pressure += (brush->gpencil_settings->draw_random_press * 2.0f) * p2d->rnd[2]; } } @@ -945,7 +920,7 @@ static void gp_primitive_update_strokes(bContext *C, tGPDprimitive *tgpi) if (brush->gpencil_settings->flag & GP_BRUSH_USE_STENGTH_PRESSURE) { float curvef = BKE_curvemapping_evaluateF( brush->gpencil_settings->curve_strength, 0, curve_pressure); - strength *= curvef * brush->gpencil_settings->draw_sensitivity; + strength *= curvef; strength *= brush->gpencil_settings->draw_strength; } @@ -973,14 +948,13 @@ static void gp_primitive_update_strokes(bContext *C, tGPDprimitive *tgpi) /* point uv */ if (gpd->runtime.sbuffer_used > 0) { - MaterialGPencilStyle *gp_style = tgpi->mat->gp_style; - const float pixsize = gp_style->texture_pixsize / 1000000.0f; tGPspoint *tptb = (tGPspoint *)gpd->runtime.sbuffer + gpd->runtime.sbuffer_used - 1; bGPDspoint spt, spt2; /* get origin to reproject point */ float origin[3]; - ED_gp_get_drawing_reference(tgpi->scene, tgpi->ob, tgpi->gpl, ts->gpencil_v3d_align, origin); + ED_gpencil_drawing_reference_get( + tgpi->scene, tgpi->ob, tgpi->gpl, ts->gpencil_v3d_align, origin); /* reproject current */ ED_gpencil_tpoint_to_point(tgpi->region, origin, tpt, &spt); ED_gp_project_point_to_plane( @@ -990,11 +964,8 @@ static void gp_primitive_update_strokes(bContext *C, tGPDprimitive *tgpi) ED_gpencil_tpoint_to_point(tgpi->region, origin, tptb, &spt2); ED_gp_project_point_to_plane( tgpi->scene, tgpi->ob, tgpi->rv3d, origin, tgpi->lock_axis - 1, &spt2); - tgpi->totpixlen += len_v3v3(&spt.x, &spt2.x) / pixsize; + tgpi->totpixlen += len_v3v3(&spt.x, &spt2.x); tpt->uv_fac = tgpi->totpixlen; - if ((gp_style) && (gp_style->sima)) { - tpt->uv_fac /= gp_style->sima->gen_x; - } } else { tgpi->totpixlen = 0.0f; @@ -1028,6 +999,8 @@ static void gp_primitive_update_strokes(bContext *C, tGPDprimitive *tgpi) pt->time = 0.0f; pt->flag = 0; pt->uv_fac = tpt->uv_fac; + /* Apply the vertex color to point. */ + ED_gpencil_point_vertex_color_set(ts, brush, pt); if (gps->dvert != NULL) { MDeformVert *dvert = &gps->dvert[i]; @@ -1052,7 +1025,8 @@ static void gp_primitive_update_strokes(bContext *C, tGPDprimitive *tgpi) /* reproject to plane */ if (!is_depth) { float origin[3]; - ED_gp_get_drawing_reference(tgpi->scene, tgpi->ob, tgpi->gpl, ts->gpencil_v3d_align, origin); + ED_gpencil_drawing_reference_get( + tgpi->scene, tgpi->ob, tgpi->gpl, ts->gpencil_v3d_align, origin); ED_gp_project_stroke_to_plane( tgpi->scene, tgpi->ob, tgpi->rv3d, gps, origin, ts->gp_sculpt.lock_axis - 1); } @@ -1060,7 +1034,7 @@ static void gp_primitive_update_strokes(bContext *C, tGPDprimitive *tgpi) /* if parented change position relative to parent object */ for (int i = 0; i < gps->totpoints; i++) { bGPDspoint *pt = &gps->points[i]; - gp_apply_parent_point(tgpi->depsgraph, tgpi->ob, tgpi->gpd, tgpi->gpl, pt); + gp_apply_parent_point(tgpi->depsgraph, tgpi->ob, tgpi->gpl, pt); } /* if camera view, reproject flat to view to avoid perspective effect */ @@ -1068,8 +1042,11 @@ static void gp_primitive_update_strokes(bContext *C, tGPDprimitive *tgpi) ED_gpencil_project_stroke_to_view(C, tgpi->gpl, gps); } - /* force fill recalc */ - gps->flag |= GP_STROKE_RECALC_GEOMETRY; + /* Calc geometry data. */ + BKE_gpencil_stroke_geometry_update(gps); + + /* Update evaluated data. */ + ED_gpencil_sbuffer_update_eval(tgpi->gpd, tgpi->ob_eval); MEM_SAFE_FREE(depth_arr); @@ -1162,13 +1139,14 @@ static void gpencil_primitive_init(bContext *C, wmOperator *op) /* set current scene and window info */ tgpi->bmain = CTX_data_main(C); + tgpi->depsgraph = CTX_data_ensure_evaluated_depsgraph(C); tgpi->scene = scene; tgpi->ob = CTX_data_active_object(C); + tgpi->ob_eval = (Object *)DEG_get_evaluated_object(tgpi->depsgraph, tgpi->ob); tgpi->sa = CTX_wm_area(C); tgpi->region = CTX_wm_region(C); tgpi->rv3d = tgpi->region->regiondata; tgpi->v3d = tgpi->sa->spacedata.first; - tgpi->depsgraph = CTX_data_ensure_evaluated_depsgraph(C); tgpi->win = CTX_wm_window(C); /* save original type */ @@ -1184,7 +1162,7 @@ static void gpencil_primitive_init(bContext *C, wmOperator *op) /* if brush doesn't exist, create a new set (fix damaged files from old versions) */ if ((paint->brush == NULL) || (paint->brush->gpencil_settings == NULL)) { - BKE_brush_gpencil_presets(bmain, ts); + BKE_brush_gpencil_paint_presets(bmain, ts); } /* Set Draw brush. */ @@ -1199,7 +1177,7 @@ static void gpencil_primitive_init(bContext *C, wmOperator *op) tgpi->gpd->runtime.tot_cp_points = 0; /* getcolor info */ - tgpi->mat = BKE_gpencil_object_material_ensure_from_active_input_toolsettings( + tgpi->material = BKE_gpencil_object_material_ensure_from_active_input_toolsettings( bmain, tgpi->ob, ts); /* set parameters */ @@ -1309,20 +1287,17 @@ static void gpencil_primitive_interaction_end(bContext *C, add_frame_mode = GP_GETFRAME_ADD_NEW; } - gpf = BKE_gpencil_layer_getframe(tgpi->gpl, tgpi->cframe, add_frame_mode); + gpf = BKE_gpencil_layer_frame_get(tgpi->gpl, tgpi->cframe, add_frame_mode); /* prepare stroke to get transferred */ gps = tgpi->gpf->strokes.first; if (gps) { gps->thickness = brush->size; - gps->gradient_f = brush->gpencil_settings->gradient_f; - copy_v2_v2(gps->gradient_s, brush->gpencil_settings->gradient_s); - - gps->flag |= GP_STROKE_RECALC_GEOMETRY; - gps->tot_triangles = 0; + gps->hardeness = brush->gpencil_settings->hardeness; + copy_v2_v2(gps->aspect_ratio, brush->gpencil_settings->aspect_ratio); - /* calculate UVs along the stroke */ - ED_gpencil_calc_stroke_uv(tgpi->ob, gps); + /* Calc geometry data. */ + BKE_gpencil_stroke_geometry_update(gps); } /* transfer stroke from temporary buffer to the actual frame */ @@ -1348,7 +1323,7 @@ static void gpencil_primitive_interaction_end(bContext *C, /* Close stroke with geometry */ if ((tgpi->type == GP_STROKE_BOX) || (tgpi->type == GP_STROKE_CIRCLE)) { - BKE_gpencil_close_stroke(gps); + BKE_gpencil_stroke_close(gps); } DEG_id_tag_update(&tgpi->gpd->id, ID_RECALC_COPY_ON_WRITE); diff --git a/source/blender/editors/gpencil/gpencil_sculpt_paint.c b/source/blender/editors/gpencil/gpencil_sculpt_paint.c new file mode 100644 index 00000000000..5cf4681c755 --- /dev/null +++ b/source/blender/editors/gpencil/gpencil_sculpt_paint.c @@ -0,0 +1,2150 @@ +/* + * 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 + * Brush based operators for editing Grease Pencil strokes + */ + +/** \file + * \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 "PIL_time.h" + +#include "BLT_translation.h" + +#include "DNA_meshdata_types.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 "DNA_object_types.h" + +#include "BKE_brush.h" +#include "BKE_colortools.h" +#include "BKE_context.h" +#include "BKE_deform.h" +#include "BKE_gpencil.h" +#include "BKE_gpencil_modifier.h" +#include "BKE_main.h" +#include "BKE_material.h" +#include "BKE_object_deform.h" +#include "BKE_report.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 "DEG_depsgraph.h" +#include "DEG_depsgraph_query.h" + +#include "gpencil_intern.h" + +/* ************************************************ */ +/* General Brush Editing Context */ + +/* Context for brush operators */ +typedef struct tGP_BrushEditData { + /* Current editor/region/etc. */ + Depsgraph *depsgraph; + Main *bmain; + Scene *scene; + Object *object; + + ScrArea *sa; + ARegion *region; + + /* Current GPencil datablock */ + bGPdata *gpd; + + /* Brush Settings */ + GP_Sculpt_Settings *settings; + Brush *brush; + Brush *brush_prev; + + eGP_Sculpt_Flag flag; + eGP_Sculpt_SelectMaskFlag mask; + + /* Space Conversion Data */ + GP_SpaceConversion gsc; + + /* Is the brush currently painting? */ + bool is_painting; + bool is_transformed; + + /* Start of new sculpt stroke */ + bool first; + + /* Is multiframe editing enabled, and are we using falloff for that? */ + bool is_multiframe; + bool use_multiframe_falloff; + + /* Current frame */ + int cfra; + + /* Brush Runtime Data: */ + /* - position and pressure + * - the *_prev variants are the previous values + */ + float mval[2], mval_prev[2]; + float pressure, pressure_prev; + + /* - effect vector (e.g. 2D/3D translation for grab brush) */ + float dvec[3]; + + /* rotation for evaluated data */ + float rot_eval; + + /* - multiframe falloff factor */ + float mf_falloff; + + /* active vertex group */ + int vrgroup; + + /* 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 */ + + /* Object invert matrix */ + float inv_mat[4][4]; + + RNG *rng; +} tGP_BrushEditData; + +/* Callback for performing some brush operation on a single point */ +typedef bool (*GP_BrushApplyCb)(tGP_BrushEditData *gso, + bGPDstroke *gps, + float rotation, + int pt_index, + const int radius, + const int co[2]); + +/* ************************************************ */ +/* Utility Functions */ + +/* apply lock axis reset */ +static void gpsculpt_compute_lock_axis(tGP_BrushEditData *gso, + bGPDspoint *pt, + const float save_pt[3]) +{ + const ToolSettings *ts = gso->scene->toolsettings; + const View3DCursor *cursor = &gso->scene->cursor; + const int axis = ts->gp_sculpt.lock_axis; + + /* lock axis control */ + switch (axis) { + case GP_LOCKAXIS_X: { + pt->x = save_pt[0]; + break; + } + case GP_LOCKAXIS_Y: { + pt->y = save_pt[1]; + break; + } + case GP_LOCKAXIS_Z: { + pt->z = save_pt[2]; + break; + } + case GP_LOCKAXIS_CURSOR: { + /* Compute a plane with cursor normal and position of the point before do the sculpt. */ + const float scale[3] = {1.0f, 1.0f, 1.0f}; + float plane_normal[3] = {0.0f, 0.0f, 1.0f}; + float plane[4]; + float mat[4][4]; + float r_close[3]; + + loc_eul_size_to_mat4(mat, cursor->location, cursor->rotation_euler, scale); + + mul_mat3_m4_v3(mat, plane_normal); + plane_from_point_normal_v3(plane, save_pt, plane_normal); + + /* find closest point to the plane with the new position */ + closest_to_plane_v3(r_close, plane, &pt->x); + copy_v3_v3(&pt->x, r_close); + break; + } + default: { + break; + } + } +} + +/* Context ---------------------------------------- */ + +/* Get the sculpting settings */ +static GP_Sculpt_Settings *gpsculpt_get_settings(Scene *scene) +{ + return &scene->toolsettings->gp_sculpt; +} + +/* Brush Operations ------------------------------- */ + +/* Invert behavior 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->gpencil_settings->sculpt_flag & GP_SCULPT_FLAG_INVERT) != 0); + + /* During runtime, the user can hold down the Ctrl key to invert the basic behavior */ + if (gso->flag & GP_SCULPT_FLAG_INVERT) { + invert ^= true; + } + + /* set temporary status */ + if (invert) { + gso->brush->gpencil_settings->sculpt_flag |= GP_SCULPT_FLAG_TMP_INVERT; + } + else { + gso->brush->gpencil_settings->sculpt_flag &= ~GP_SCULPT_FLAG_TMP_INVERT; + } + + return invert; +} + +/* Compute strength of effect */ +static float gp_brush_influence_calc(tGP_BrushEditData *gso, const int radius, const int co[2]) +{ + Brush *brush = gso->brush; + + /* basic strength factor from brush settings */ + float influence = brush->alpha; + + /* use pressure? */ + if (brush->gpencil_settings->flag & GP_BRUSH_USE_PRESSURE) { + influence *= gso->pressure; + } + + /* distance fading */ + int mval_i[2]; + round_v2i_v2fl(mval_i, gso->mval); + float distance = (float)len_v2v2_int(mval_i, co); + + /* Apply Brush curve. */ + float brush_fallof = BKE_brush_curve_strength(brush, distance, (float)radius); + influence *= brush_fallof; + + /* apply multiframe falloff */ + influence *= gso->mf_falloff; + + /* return influence */ + return influence; +} + +/* Tag stroke to be recalculated. */ +static void gpencil_recalc_geometry_tag(bGPDstroke *gps) +{ + bGPDstroke *gps_active = (gps->runtime.gps_orig) ? gps->runtime.gps_orig : gps; + gps_active->flag |= GP_STROKE_TAG; +} + +/* Recalc any stroke tagged. */ +static void gpencil_update_geometry(bGPdata *gpd) +{ + if (gpd == NULL) { + return; + } + + LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd->layers) { + LISTBASE_FOREACH (bGPDframe *, gpf, &gpl->frames) { + if ((gpl->actframe != gpf) && ((gpf->flag & GP_FRAME_SELECT) == 0)) { + continue; + } + + LISTBASE_FOREACH (bGPDstroke *, gps, &gpf->strokes) { + if (gps->flag & GP_STROKE_TAG) { + BKE_gpencil_stroke_geometry_update(gps); + gps->flag &= ~GP_STROKE_TAG; + } + } + } + } + DEG_id_tag_update(&gpd->id, ID_RECALC_GEOMETRY | ID_RECALC_COPY_ON_WRITE); + WM_main_add_notifier(NC_GPENCIL | ND_DATA | NA_EDITED, NULL); +} + +/* ************************************************ */ +/* 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, + float UNUSED(rot_eval), + int pt_index, + const int radius, + const int co[2]) +{ + float inf = gp_brush_influence_calc(gso, radius, co); + + /* perform smoothing */ + if (gso->brush->gpencil_settings->sculpt_mode_flag & GP_SCULPT_FLAGMODE_APPLY_POSITION) { + BKE_gpencil_stroke_smooth(gps, pt_index, inf); + } + if (gso->brush->gpencil_settings->sculpt_mode_flag & GP_SCULPT_FLAGMODE_APPLY_STRENGTH) { + BKE_gpencil_stroke_smooth_strength(gps, pt_index, inf); + } + if (gso->brush->gpencil_settings->sculpt_mode_flag & GP_SCULPT_FLAGMODE_APPLY_THICKNESS) { + BKE_gpencil_stroke_smooth_thickness(gps, pt_index, inf); + } + if (gso->brush->gpencil_settings->sculpt_mode_flag & GP_SCULPT_FLAGMODE_APPLY_UV) { + BKE_gpencil_stroke_smooth_uv(gps, pt_index, inf); + } + + 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, + float UNUSED(rot_eval), + int pt_index, + const int radius, + const int co[2]) +{ + bGPDspoint *pt = gps->points + pt_index; + 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; +} + +/* ----------------------------------------------- */ +/* Color Strength Brush */ + +/* Make color more or less transparent by the specified amounts */ +static bool gp_brush_strength_apply(tGP_BrushEditData *gso, + bGPDstroke *gps, + float UNUSED(rot_eval), + int pt_index, + const int radius, + const int co[2]) +{ + bGPDspoint *pt = gps->points + pt_index; + float inf; + + /* Compute strength of effect + * - We divide the strength, 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) / 2.0f; + CLAMP_MIN(inf, 0.01f); + + /* apply */ + if (gp_brush_invert_check(gso)) { + /* make line more transparent - reduce alpha factor */ + pt->strength -= inf; + } + else { + /* make line more opaque - increase stroke strength */ + pt->strength += inf; + } + /* Strength should stay within [0.0, 1.0] */ + CLAMP(pt->strength, 0.0f, 1.0f); + + /* smooth the strength */ + BKE_gpencil_stroke_smooth_strength(gps, pt_index, inf); + + 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; + /* angles to calc transformation */ + float *rot_eval; + + /* 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); + memset(data->rot_eval, 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"); + data->rot_eval = MEM_callocN(sizeof(float) * data->capacity, "GP Stroke Grab Rotations"); + + /* 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, + float rot_eval, + int pt_index, + 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] = pt_index; + data->weights[data->size] = inf; + data->rot_eval[data->size] = rot_eval; + 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 */ + RegionView3D *rv3d = gso->region->regiondata; + float *rvec = gso->object->loc; + 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]); + + /* apply evaluated data transformation */ + if (gso->rot_eval != 0.0f) { + const float cval = cos(gso->rot_eval); + const float sval = sin(gso->rot_eval); + float r[2]; + r[0] = (mval_f[0] * cval) - (mval_f[1] * sval); + r[1] = (mval_f[0] * sval) + (mval_f[1] * cval); + copy_v2_v2(mval_f, r); + } + + ED_view3d_win_to_delta(gso->region, mval_f, gso->dvec, zfac); +} + +/* Apply grab transform to all relevant points of the affected strokes */ +static void gp_brush_grab_apply_cached(tGP_BrushEditData *gso, + bGPDstroke *gps, + const float diff_mat[4][4]) +{ + tGPSB_Grab_StrokeData *data = BLI_ghash_lookup(gso->stroke_customdata, gps); + /* If a new frame is created, could be impossible find the stroke. */ + if (data == NULL) { + return; + } + + int i; + float inverse_diff_mat[4][4]; + invert_m4_m4(inverse_diff_mat, diff_mat); + + /* 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}; + + /* get evaluated transformation */ + gso->rot_eval = data->rot_eval[i]; + gp_brush_grab_calc_dvec(gso); + + /* adjust the amount of displacement to apply */ + mul_v3_v3fl(delta, gso->dvec, data->weights[i]); + + float fpt[3]; + float save_pt[3]; + copy_v3_v3(save_pt, &pt->x); + /* apply transformation */ + mul_v3_m4v3(fpt, diff_mat, &pt->x); + /* apply */ + add_v3_v3v3(&pt->x, fpt, delta); + /* undo transformation to the init parent position */ + mul_m4_v3(inverse_diff_mat, &pt->x); + + /* compute lock axis */ + gpsculpt_compute_lock_axis(gso, pt, save_pt); + } +} + +/* 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_SAFE_FREE(data->points); + MEM_SAFE_FREE(data->weights); + MEM_SAFE_FREE(data->rot_eval); + + /* ... 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, + float UNUSED(rot_eval), + int pt_index, + const int radius, + const int co[2]) +{ + bGPDspoint *pt = gps->points + pt_index; + float save_pt[3]; + copy_v3_v3(save_pt, &pt->x); + + 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 */ + mul_mat3_m4_v3(gso->inv_mat, delta); /* only rotation component */ + add_v3_v3(&pt->x, delta); + + /* compute lock axis */ + gpsculpt_compute_lock_axis(gso, pt, save_pt); + + /* 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) +{ + /* Convert mouse position to 3D space + * See: gpencil_paint.c :: gp_stroke_convertcoords() + */ + RegionView3D *rv3d = gso->region->regiondata; + const float *rvec = gso->object->loc; + float zfac = ED_view3d_calc_zfac(rv3d, rvec, NULL); + + float mval_f[2]; + copy_v2_v2(mval_f, gso->mval); + float mval_prj[2]; + float dvec[3]; + + if (ED_view3d_project_float_global(gso->region, 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->region, mval_f, dvec, zfac); + sub_v3_v3v3(gso->dvec, rvec, dvec); + } + else { + zero_v3(gso->dvec); + } +} + +/* Shrink distance between midpoint and this point... */ +static bool gp_brush_pinch_apply(tGP_BrushEditData *gso, + bGPDstroke *gps, + float UNUSED(rot_eval), + int pt_index, + const int radius, + const int co[2]) +{ + bGPDspoint *pt = gps->points + pt_index; + float fac, inf; + float vec[3]; + float save_pt[3]; + copy_v3_v3(save_pt, &pt->x); + + /* 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) */ + float fpt[3]; + mul_v3_m4v3(fpt, gso->object->obmat, &pt->x); + sub_v3_v3v3(vec, fpt, 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(fpt, gso->dvec, vec); + mul_v3_m4v3(&pt->x, gso->object->imat, fpt); + + /* compute lock axis */ + gpsculpt_compute_lock_axis(gso, pt, save_pt); + + /* 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, + float UNUSED(rot_eval), + int pt_index, + const int radius, + const int co[2]) +{ + bGPDspoint *pt = gps->points + pt_index; + float angle, inf; + float save_pt[3]; + copy_v3_v3(save_pt, &pt->x); + + /* 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; + } + + /* Rotate in 2D or 3D space? */ + if (gps->flag & GP_STROKE_3DSPACE) { + /* Perform rotation in 3D space... */ + RegionView3D *rv3d = gso->region->regiondata; + float rmat[3][3]; + float axis[3]; + float vec[3]; + + /* Compute rotation matrix - rotate around view vector by angle */ + negate_v3_v3(axis, rv3d->persinv[2]); + normalize_v3(axis); + + axis_angle_normalized_to_mat3(rmat, axis, angle); + + /* Rotate point */ + float fpt[3]; + mul_v3_m4v3(fpt, gso->object->obmat, &pt->x); + sub_v3_v3v3(vec, fpt, gso->dvec); /* make relative to center + * (center is stored in dvec) */ + mul_m3_v3(rmat, vec); + add_v3_v3v3(fpt, vec, gso->dvec); /* restore */ + mul_v3_m4v3(&pt->x, gso->object->imat, fpt); + + /* compute lock axis */ + gpsculpt_compute_lock_axis(gso, pt, save_pt); + } + else { + const float axis[3] = {0.0f, 0.0f, 1.0f}; + float vec[3] = {0.0f}; + float rmat[3][3]; + + /* Express position of point relative to cursor, ready to rotate */ + // XXX: There is still some offset here, but it's close to working as expected... + vec[0] = (float)(co[0] - gso->mval[0]); + vec[1] = (float)(co[1] - gso->mval[1]); + + /* rotate point */ + axis_angle_normalized_to_mat3(rmat, axis, angle); + mul_m3_v3(rmat, vec); + + /* Convert back to screen-coordinates */ + vec[0] += (float)gso->mval[0]; + vec[1] += (float)gso->mval[1]; + + /* Map from screen-coordinates to final coordinate space */ + if (gps->flag & GP_STROKE_2DSPACE) { + View2D *v2d = gso->gsc.v2d; + UI_view2d_region_to_view(v2d, vec[0], vec[1], &pt->x, &pt->y); + } + else { + // XXX + copy_v2_v2(&pt->x, vec); + } + } + + /* done */ + return true; +} + +/* ----------------------------------------------- */ +/* Randomize Brush */ +/* Apply some random jitter to the point */ +static bool gp_brush_randomize_apply(tGP_BrushEditData *gso, + bGPDstroke *gps, + float UNUSED(rot_eval), + int pt_index, + const int radius, + const int co[2]) +{ + bGPDspoint *pt = gps->points + pt_index; + float save_pt[3]; + copy_v3_v3(save_pt, &pt->x); + + /* 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_rng_get_float(gso->rng) * inf; + + /* apply random to position */ + if (gso->brush->gpencil_settings->sculpt_mode_flag & GP_SCULPT_FLAGMODE_APPLY_POSITION) { + /* 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 + * view-space as needed. + */ + float mvec[2], svec[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]; + + /* scale the displacement by the random displacement, and apply */ + if (BLI_rng_get_float(gso->rng) > 0.5f) { + mul_v2_fl(svec, -fac); + } + else { + mul_v2_fl(svec, fac); + } + + /* convert to dataspace */ + if (gps->flag & GP_STROKE_3DSPACE) { + /* 3D: Project to 3D space */ + bool flip; + RegionView3D *rv3d = gso->region->regiondata; + float zfac = ED_view3d_calc_zfac(rv3d, &pt->x, &flip); + if (flip == false) { + float dvec[3]; + ED_view3d_win_to_delta(gso->gsc.region, svec, dvec, zfac); + add_v3_v3(&pt->x, dvec); + /* compute lock axis */ + gpsculpt_compute_lock_axis(gso, pt, save_pt); + } + } + } + /* apply random to strength */ + if (gso->brush->gpencil_settings->sculpt_mode_flag & GP_SCULPT_FLAGMODE_APPLY_STRENGTH) { + if (BLI_rng_get_float(gso->rng) > 0.5f) { + pt->strength += fac; + } + else { + pt->strength -= fac; + } + CLAMP_MIN(pt->strength, 0.0f); + CLAMP_MAX(pt->strength, 1.0f); + } + /* apply random to thickness (use pressure) */ + if (gso->brush->gpencil_settings->sculpt_mode_flag & GP_SCULPT_FLAGMODE_APPLY_THICKNESS) { + if (BLI_rng_get_float(gso->rng) > 0.5f) { + pt->pressure += fac; + } + else { + pt->pressure -= fac; + } + /* only limit lower value */ + CLAMP_MIN(pt->pressure, 0.0f); + } + /* apply random to UV (use pressure) */ + if (gso->brush->gpencil_settings->sculpt_mode_flag & GP_SCULPT_FLAGMODE_APPLY_UV) { + if (BLI_rng_get_float(gso->rng) > 0.5f) { + pt->uv_rot += fac; + } + else { + pt->uv_rot -= fac; + } + CLAMP(pt->uv_rot, -M_PI_2, M_PI_2); + } + + /* 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 follows the cursor + * "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; + + /** Mapping from colors referenced per stroke, to the new colors in the "pasted" strokes. */ + GHash *new_colors; +} 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"); + } + + /* Init colormap for mapping between the pasted stroke's source color (names) + * and the final colors that will be used here instead. + */ + data->new_colors = gp_copybuf_validate_colormap(C); +} + +/* 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 copybuf colormap */ + if (data->new_colors) { + BLI_ghash_free(data->new_colors, NULL, NULL); + data->new_colors = 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; + + Object *ob = gso->object; + bGPdata *gpd = (bGPdata *)ob->data; + Scene *scene = gso->scene; + 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; + + bGPDlayer *gpl = NULL; + /* Try to use original layer. */ + if (gps->runtime.tmp_layerinfo != NULL) { + gpl = BKE_gpencil_layer_named_get(gpd, gps->runtime.tmp_layerinfo); + } + + /* if not available, use active layer. */ + if (gpl == NULL) { + gpl = CTX_data_active_gpencil_layer(C); + } + bGPDframe *gpf = BKE_gpencil_layer_frame_get(gpl, CFRA, GP_GETFRAME_ADD_NEW); + + /* Make a new stroke */ + new_stroke = BKE_gpencil_stroke_duplicate(gps, true); + + new_stroke->next = new_stroke->prev = NULL; + BLI_addtail(&gpf->strokes, new_stroke); + + /* Fix color references */ + Material *ma = BLI_ghash_lookup(data->new_colors, POINTER_FROM_INT(new_stroke->mat_nr)); + new_stroke->mat_nr = BKE_gpencil_object_material_index_get(ob, ma); + if (!ma || new_stroke->mat_nr < 0) { + new_stroke->mat_nr = 0; + } + /* 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++) { + /* Rotate around center new position */ + mul_mat3_m4_v3(gso->object->obmat, &pt->x); /* only rotation component */ + + /* assume that the delta can just be applied, and then everything works */ + add_v3_v3(&pt->x, delta); + mul_m4_v3(gso->object->imat, &pt->x); + } + + /* 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(tGP_BrushEditData *gso) +{ + tGPSB_CloneBrushData *data = gso->customdata; + size_t snum; + + /* Compute the amount of movement to apply (overwrites dvec) */ + gso->rot_eval = 0.0f; + 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++) { + /* "Smudge" Effect falloff */ + 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); + } + } +} + +/* 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 Continuous Mode */ + if (1 /*gso->brush->mode == GP_EDITBRUSH_CLONE_MODE_STAMP*/) { + /* Stamp - Proceed to translate the newly added strokes */ + gp_brush_clone_adjust(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; +} + +/* ************************************************ */ +/* Header Info for GPencil Sculpt */ + +static void gpsculpt_brush_header_set(bContext *C, tGP_BrushEditData *gso) +{ + Brush *brush = gso->brush; + char str[UI_MAX_DRAW_STR] = ""; + + BLI_snprintf(str, + sizeof(str), + TIP_("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->id.name + 2); + + ED_workspace_status_text(C, str); +} + +/* ************************************************ */ +/* Grease Pencil Sculpting Operator */ + +/* Init/Exit ----------------------------------------------- */ + +static bool gpsculpt_brush_init(bContext *C, wmOperator *op) +{ + Scene *scene = CTX_data_scene(C); + ToolSettings *ts = scene->toolsettings; + Object *ob = CTX_data_active_object(C); + + /* set the brush using the tool */ + tGP_BrushEditData *gso; + + /* setup operator data */ + gso = MEM_callocN(sizeof(tGP_BrushEditData), "tGP_BrushEditData"); + op->customdata = gso; + + gso->depsgraph = CTX_data_ensure_evaluated_depsgraph(C); + gso->bmain = CTX_data_main(C); + /* store state */ + gso->settings = gpsculpt_get_settings(scene); + + /* Random generator, only init once. */ + uint rng_seed = (uint)(PIL_check_seconds_timer_i() & UINT_MAX); + rng_seed ^= POINTER_AS_UINT(gso); + gso->rng = BLI_rng_new(rng_seed); + + 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->object = ob; + if (ob) { + invert_m4_m4(gso->inv_mat, ob->obmat); + gso->vrgroup = ob->actdef - 1; + if (!BLI_findlink(&ob->defbase, gso->vrgroup)) { + gso->vrgroup = -1; + } + /* Check if some modifier can transform the stroke. */ + gso->is_transformed = BKE_gpencil_has_transform_modifiers(ob); + } + else { + unit_m4(gso->inv_mat); + gso->vrgroup = -1; + gso->is_transformed = false; + } + + gso->sa = CTX_wm_area(C); + gso->region = CTX_wm_region(C); + + Paint *paint = &ts->gp_sculptpaint->paint; + gso->brush = paint->brush; + BKE_curvemapping_initialize(gso->brush->curve); + + /* save mask */ + gso->mask = ts->gpencil_selectmode_sculpt; + + /* multiframe settings */ + gso->is_multiframe = (bool)GPENCIL_MULTIEDIT_SESSIONS_ON(gso->gpd); + gso->use_multiframe_falloff = (ts->gp_sculpt.flag & GP_SCULPT_SETT_FLAG_FRAME_FALLOFF) != 0; + + /* Init multi-edit falloff curve data before doing anything, + * so we won't have to do it again later. */ + if (gso->is_multiframe) { + BKE_curvemapping_initialize(ts->gp_sculpt.cur_falloff); + } + + /* initialise custom data for brushes */ + char tool = gso->brush->gpencil_sculpt_tool; + switch (tool) { + case GPSCULPT_TOOL_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 GPSCULPT_TOOL_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); + + return true; +} + +static void gpsculpt_brush_exit(bContext *C, wmOperator *op) +{ + tGP_BrushEditData *gso = op->customdata; + wmWindow *win = CTX_wm_window(C); + char tool = gso->brush->gpencil_sculpt_tool; + + /* free brush-specific data */ + switch (tool) { + case GPSCULPT_TOOL_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 GPSCULPT_TOOL_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); + } + + if (gso->rng != NULL) { + BLI_rng_free(gso->rng); + } + + /* Disable headerprints. */ + ED_workspace_status_text(C, NULL); + + /* disable temp invert flag */ + gso->brush->gpencil_settings->sculpt_flag &= ~GP_SCULPT_FLAG_TMP_INVERT; + + /* Update geometry data for tagged strokes. */ + gpencil_update_geometry(gso->gpd); + + /* free operator data */ + MEM_freeN(gso); + op->customdata = NULL; +} + +/* poll callback for stroke sculpting operator(s) */ +static bool gpsculpt_brush_poll(bContext *C) +{ + ScrArea *sa = CTX_wm_area(C); + if (sa && sa->spacetype != SPACE_VIEW3D) { + return false; + } + + /* 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(bContext *C, tGP_BrushEditData *gso) +{ + bGPdata *gpd = gso->gpd; + + Scene *scene = gso->scene; + 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 */ + LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd->layers) { + /* only editable and visible layers are considered */ + if (BKE_gpencil_layer_is_editable(gpl) && (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. + */ + if (gpf->framenum != cfra) { + BKE_gpencil_frame_addcopy(gpl, cfra); + /* Need tag to recalculate evaluated data to avoid crashes. */ + DEG_id_tag_update(&gso->gpd->id, ID_RECALC_GEOMETRY | ID_RECALC_COPY_ON_WRITE); + WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL); + } + } + } + + /* save off new current frame, so that next update works fine */ + gso->cfra = cfra; +} + +/* Apply ----------------------------------------------- */ + +/* Get angle of the segment relative to the original segment before any transformation + * For strokes with one point only this is impossible to calculate because there isn't a + * valid reference point. + */ +static float gpsculpt_rotation_eval_get(tGP_BrushEditData *gso, + bGPDstroke *gps_eval, + bGPDspoint *pt_eval, + int idx_eval) +{ + /* If multiframe or no modifiers, return 0. */ + if ((GPENCIL_MULTIEDIT_SESSIONS_ON(gso->gpd)) || (!gso->is_transformed)) { + return 0.0f; + } + + GP_SpaceConversion *gsc = &gso->gsc; + bGPDstroke *gps_orig = gps_eval->runtime.gps_orig; + bGPDspoint *pt_orig = &gps_orig->points[pt_eval->runtime.idx_orig]; + bGPDspoint *pt_prev_eval = NULL; + bGPDspoint *pt_orig_prev = NULL; + if (idx_eval != 0) { + pt_prev_eval = &gps_eval->points[idx_eval - 1]; + } + else { + if (gps_eval->totpoints > 1) { + pt_prev_eval = &gps_eval->points[idx_eval + 1]; + } + else { + return 0.0f; + } + } + + if (pt_eval->runtime.idx_orig != 0) { + pt_orig_prev = &gps_orig->points[pt_eval->runtime.idx_orig - 1]; + } + else { + if (gps_orig->totpoints > 1) { + pt_orig_prev = &gps_orig->points[pt_eval->runtime.idx_orig + 1]; + } + else { + return 0.0f; + } + } + + /* create 2D vectors of the stroke segments */ + float v_orig_a[2], v_orig_b[2], v_eval_a[2], v_eval_b[2]; + + gp_point_3d_to_xy(gsc, GP_STROKE_3DSPACE, &pt_orig->x, v_orig_a); + gp_point_3d_to_xy(gsc, GP_STROKE_3DSPACE, &pt_orig_prev->x, v_orig_b); + sub_v2_v2(v_orig_a, v_orig_b); + + gp_point_3d_to_xy(gsc, GP_STROKE_3DSPACE, &pt_eval->x, v_eval_a); + gp_point_3d_to_xy(gsc, GP_STROKE_3DSPACE, &pt_prev_eval->x, v_eval_b); + sub_v2_v2(v_eval_a, v_eval_b); + + return angle_v2v2(v_orig_a, v_eval_a); +} + +/* Apply brush operation to points in this stroke */ +static bool gpsculpt_brush_do_stroke(tGP_BrushEditData *gso, + bGPDstroke *gps, + const float diff_mat[4][4], + GP_BrushApplyCb apply) +{ + GP_SpaceConversion *gsc = &gso->gsc; + rcti *rect = &gso->brush_rect; + Brush *brush = gso->brush; + const int radius = (brush->flag & GP_BRUSH_USE_PRESSURE) ? gso->brush->size * gso->pressure : + gso->brush->size; + + bGPDstroke *gps_active = (gps->runtime.gps_orig) ? gps->runtime.gps_orig : gps; + bGPDspoint *pt_active = NULL; + + bGPDspoint *pt1, *pt2; + bGPDspoint *pt = NULL; + int pc1[2] = {0}; + int pc2[2] = {0}; + int i; + int index; + bool include_last = false; + bool changed = false; + float rot_eval = 0.0f; + + /* Check if the stroke collide with brush. */ + if (!ED_gpencil_stroke_check_collision(gsc, gps, gso->mval, radius, diff_mat)) { + return false; + } + + if (gps->totpoints == 1) { + bGPDspoint pt_temp; + pt = &gps->points[0]; + gp_point_to_parent_space(gps->points, diff_mat, &pt_temp); + gp_point_to_xy(gsc, gps, &pt_temp, &pc1[0], &pc1[1]); + + pt_active = (pt->runtime.pt_orig) ? pt->runtime.pt_orig : pt; + /* 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 */ + int mval_i[2]; + round_v2i_v2fl(mval_i, gso->mval); + if (len_v2v2_int(mval_i, pc1) <= radius) { + /* apply operation to this point */ + if (pt_active != NULL) { + rot_eval = gpsculpt_rotation_eval_get(gso, gps, pt, 0); + changed = apply(gso, gps_active, rot_eval, 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 (GPENCIL_ANY_SCULPT_MASK(gso->mask)) { + if (!(pt1->flag & GP_SPOINT_SELECT) && !(pt2->flag & GP_SPOINT_SELECT)) { + include_last = false; + continue; + } + } + bGPDspoint npt; + gp_point_to_parent_space(pt1, diff_mat, &npt); + gp_point_to_xy(gsc, gps, &npt, &pc1[0], &pc1[1]); + + gp_point_to_parent_space(pt2, diff_mat, &npt); + gp_point_to_xy(gsc, gps, &npt, &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 + * brush 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... */ + pt = &gps->points[i]; + pt_active = (pt->runtime.pt_orig) ? pt->runtime.pt_orig : pt; + index = (pt->runtime.pt_orig) ? pt->runtime.idx_orig : i; + if (pt_active != NULL) { + rot_eval = gpsculpt_rotation_eval_get(gso, gps, pt, i); + ok = apply(gso, gps_active, rot_eval, index, 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) { + pt = &gps->points[i + 1]; + pt_active = (pt->runtime.pt_orig) ? pt->runtime.pt_orig : pt; + index = (pt->runtime.pt_orig) ? pt->runtime.idx_orig : i + 1; + if (pt_active != NULL) { + rot_eval = gpsculpt_rotation_eval_get(gso, gps, pt, i + 1); + ok |= apply(gso, gps_active, rot_eval, index, 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). + */ + pt = &gps->points[i]; + pt_active = (pt->runtime.pt_orig) ? pt->runtime.pt_orig : pt; + index = (pt->runtime.pt_orig) ? pt->runtime.idx_orig : i; + if (pt_active != NULL) { + rot_eval = gpsculpt_rotation_eval_get(gso, gps, pt, i); + changed |= apply(gso, gps_active, rot_eval, index, radius, pc1); + include_last = false; + } + } + } + } + } + + return changed; +} + +/* Apply sculpt brushes to strokes in the given frame */ +static bool gpsculpt_brush_do_frame(bContext *C, + tGP_BrushEditData *gso, + bGPDlayer *gpl, + bGPDframe *gpf, + const float diff_mat[4][4]) +{ + bool changed = false; + bool redo_geom = false; + Object *ob = gso->object; + char tool = gso->brush->gpencil_sculpt_tool; + + LISTBASE_FOREACH (bGPDstroke *, gps, &gpf->strokes) { + /* skip strokes that are invalid for current view */ + if (ED_gpencil_stroke_can_use(C, gps) == false) { + continue; + } + /* check if the color is editable */ + if (ED_gpencil_stroke_color_use(ob, gpl, gps) == false) { + continue; + } + + switch (tool) { + case GPSCULPT_TOOL_SMOOTH: /* Smooth strokes */ + { + changed |= gpsculpt_brush_do_stroke(gso, gps, diff_mat, gp_brush_smooth_apply); + redo_geom |= changed; + break; + } + + case GPSCULPT_TOOL_THICKNESS: /* Adjust stroke thickness */ + { + changed |= gpsculpt_brush_do_stroke(gso, gps, diff_mat, gp_brush_thickness_apply); + break; + } + + case GPSCULPT_TOOL_STRENGTH: /* Adjust stroke color strength */ + { + changed |= gpsculpt_brush_do_stroke(gso, gps, diff_mat, gp_brush_strength_apply); + break; + } + + case GPSCULPT_TOOL_GRAB: /* Grab points */ + { + bGPDstroke *gps_active = (gps->runtime.gps_orig) ? gps->runtime.gps_orig : gps; + if (gps_active != NULL) { + 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_active); + changed |= gpsculpt_brush_do_stroke( + gso, gps_active, diff_mat, gp_brush_grab_store_points); + } + else { + /* Apply effect to the stored points */ + gp_brush_grab_apply_cached(gso, gps_active, diff_mat); + changed |= true; + } + } + redo_geom |= changed; + break; + } + + case GPSCULPT_TOOL_PUSH: /* Push points */ + { + changed |= gpsculpt_brush_do_stroke(gso, gps, diff_mat, gp_brush_push_apply); + redo_geom |= changed; + break; + } + + case GPSCULPT_TOOL_PINCH: /* Pinch points */ + { + changed |= gpsculpt_brush_do_stroke(gso, gps, diff_mat, gp_brush_pinch_apply); + redo_geom |= changed; + break; + } + + case GPSCULPT_TOOL_TWIST: /* Twist points around midpoint */ + { + changed |= gpsculpt_brush_do_stroke(gso, gps, diff_mat, gp_brush_twist_apply); + redo_geom |= changed; + break; + } + + case GPSCULPT_TOOL_RANDOMIZE: /* Apply jitter */ + { + changed |= gpsculpt_brush_do_stroke(gso, gps, diff_mat, gp_brush_randomize_apply); + redo_geom |= changed; + break; + } + + default: + printf("ERROR: Unknown type of GPencil Sculpt brush \n"); + break; + } + + /* Triangulation must be calculated. */ + if (redo_geom) { + bGPDstroke *gps_active = (gps->runtime.gps_orig) ? gps->runtime.gps_orig : gps; + if (gpl->actframe == gpf) { + MaterialGPencilStyle *gp_style = BKE_gpencil_material_settings(ob, gps->mat_nr + 1); + /* Update active frame now, only if material has fill. */ + if (gp_style->flag & GP_MATERIAL_FILL_SHOW) { + BKE_gpencil_stroke_geometry_update(gps_active); + } + else { + gpencil_recalc_geometry_tag(gps_active); + } + } + else { + /* Delay a full recalculation for other frames. */ + gpencil_recalc_geometry_tag(gps_active); + } + } + } + + return changed; +} + +/* Perform two-pass brushes which modify the existing strokes */ +static bool gpsculpt_brush_apply_standard(bContext *C, tGP_BrushEditData *gso) +{ + ToolSettings *ts = gso->scene->toolsettings; + Depsgraph *depsgraph = gso->depsgraph; + Object *obact = gso->object; + bool changed = false; + + Object *ob_eval = (Object *)DEG_get_evaluated_id(depsgraph, &obact->id); + bGPdata *gpd = (bGPdata *)ob_eval->data; + + /* Calculate brush-specific data which applies equally to all points */ + char tool = gso->brush->gpencil_sculpt_tool; + switch (tool) { + case GPSCULPT_TOOL_GRAB: /* Grab points */ + case GPSCULPT_TOOL_PUSH: /* Push points */ + { + /* calculate amount of displacement to apply */ + gso->rot_eval = 0.0f; + gp_brush_grab_calc_dvec(gso); + break; + } + + case GPSCULPT_TOOL_PINCH: /* Pinch points */ + case GPSCULPT_TOOL_TWIST: /* Twist points around midpoint */ + { + /* calculate midpoint of the brush (in data space) */ + gp_brush_calc_midpoint(gso); + break; + } + + case GPSCULPT_TOOL_RANDOMIZE: /* Random jitter */ + { + /* compute the displacement vector for the cursor (in data space) */ + gso->rot_eval = 0.0f; + gp_brush_grab_calc_dvec(gso); + break; + } + + default: + break; + } + + /* Find visible strokes, and perform operations on those if hit */ + LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd->layers) { + /* If no active frame, don't do anything... */ + if ((!BKE_gpencil_layer_is_editable(gpl)) || (gpl->actframe == NULL)) { + continue; + } + + /* calculate difference matrix */ + float diff_mat[4][4]; + BKE_gpencil_parent_matrix_get(depsgraph, obact, gpl, diff_mat); + + /* Active Frame or MultiFrame? */ + if (gso->is_multiframe) { + /* init multiframe falloff options */ + int f_init = 0; + int f_end = 0; + + if (gso->use_multiframe_falloff) { + BKE_gpencil_frame_range_selected(gpl, &f_init, &f_end); + } + + LISTBASE_FOREACH (bGPDframe *, gpf, &gpl->frames) { + /* Always do active frame; Otherwise, only include selected frames */ + if ((gpf == gpl->actframe) || (gpf->flag & GP_FRAME_SELECT)) { + /* compute multiframe falloff factor */ + if (gso->use_multiframe_falloff) { + /* Faloff depends on distance to active frame (relative to the overall frame range) */ + gso->mf_falloff = BKE_gpencil_multiframe_falloff_calc( + gpf, gpl->actframe->framenum, f_init, f_end, ts->gp_sculpt.cur_falloff); + } + else { + /* No falloff */ + gso->mf_falloff = 1.0f; + } + + /* affect strokes in this frame */ + changed |= gpsculpt_brush_do_frame(C, gso, gpl, gpf, diff_mat); + } + } + } + else { + if (gpl->actframe != NULL) { + /* Apply to active frame's strokes */ + gso->mf_falloff = 1.0f; + changed |= gpsculpt_brush_do_frame(C, gso, gpl, gpl->actframe, diff_mat); + } + } + } + + return changed; +} + +/* Calculate settings for applying brush */ +static void gpsculpt_brush_apply(bContext *C, wmOperator *op, PointerRNA *itemptr) +{ + tGP_BrushEditData *gso = op->customdata; + Brush *brush = gso->brush; + const int radius = (brush->flag & GP_BRUSH_USE_PRESSURE) ? gso->brush->size * gso->pressure : + 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_SCULPT_FLAG_INVERT; + } + else { + gso->flag &= ~GP_SCULPT_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 */ + char tool = gso->brush->gpencil_sculpt_tool; + if (tool == GPSCULPT_TOOL_CLONE) { + changed = gpsculpt_brush_apply_clone(C, gso); + } + else { + changed = gpsculpt_brush_apply_standard(C, gso); + } + + /* Updates */ + if (changed) { + DEG_id_tag_update(&gso->gpd->id, ID_RECALC_GEOMETRY); + 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 --------------------------------------------- */ +static Brush *gpsculpt_get_smooth_brush(tGP_BrushEditData *gso) +{ + Main *bmain = gso->bmain; + Brush *brush = BLI_findstring(&bmain->brushes, "Smooth Stroke", offsetof(ID, name) + 2); + + return brush; +} + +/* 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]; + + 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 and otherwise 1.0) */ + float pressure = event->tablet.pressure; + /* special exception here for too high pressure values on first touch in + * windows for some tablets: clamp the values to be sane */ + if (pressure >= 0.99f) { + pressure = 1.0f; + } + RNA_float_set(&itemptr, "pressure", pressure); + + if (event->shift) { + gso->brush_prev = gso->brush; + + gso->brush = gpsculpt_get_smooth_brush(gso); + if (gso->brush == NULL) { + gso->brush = gso->brush_prev; + } + } + else { + if (gso->brush_prev != NULL) { + gso->brush = gso->brush_prev; + } + } + + /* 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"); + const bool is_playing = ED_screen_animation_playing(CTX_wm_manager(C)) != NULL; + bool needs_timer = false; + float brush_rate = 0.0f; + + /* the operator cannot work while play animation */ + if (is_playing) { + BKE_report(op->reports, RPT_ERROR, "Cannot sculpt while play animation"); + + return OPERATOR_CANCELLED; + } + + /* init painting data */ + if (!gpsculpt_brush_init(C, op)) { + return OPERATOR_CANCELLED; + } + + gso = op->customdata; + + /* initialise type-specific data (used for the entire session) */ + char tool = gso->brush->gpencil_sculpt_tool; + switch (tool) { + /* Brushes requiring timer... */ + case GPSCULPT_TOOL_THICKNESS: + brush_rate = 0.01f; + needs_timer = true; + break; + + case GPSCULPT_TOOL_STRENGTH: + brush_rate = 0.01f; + needs_timer = true; + break; + + case GPSCULPT_TOOL_PINCH: + brush_rate = 0.001f; + needs_timer = true; + break; + + case GPSCULPT_TOOL_TWIST: + brush_rate = 0.01f; + 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 *region = CTX_wm_region(C); + + /* ensure that we'll have a new frame to draw on */ + gpsculpt_brush_init_stroke(C, gso); + + /* apply first dab... */ + gso->is_painting = true; + gpsculpt_brush_apply_event(C, op, event); + + /* redraw view with feedback */ + ED_region_tag_redraw(region); + } + + 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; + bool redraw_toolsettings = 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(C, 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; + + /* Change Frame - Allowed */ + case LEFTARROWKEY: + case RIGHTARROWKEY: + case UPARROWKEY: + case DOWNARROWKEY: + return OPERATOR_PASS_THROUGH; + + /* Camera/View Gizmo's - Allowed */ + /* (See rationale in gpencil_paint.c -> gpencil_draw_modal()) */ + case PAD0: + case PAD1: + case PAD2: + case PAD3: + case PAD4: + case PAD5: + case PAD6: + case PAD7: + case PAD8: + case PAD9: + return OPERATOR_PASS_THROUGH; + + /* Unhandled event */ + default: + break; + } + } + + /* Redraw region? */ + if (redraw_region) { + ARegion *region = CTX_wm_region(C); + ED_region_tag_redraw(region); + } + + /* Redraw toolsettings (brush settings)? */ + if (redraw_toolsettings) { + DEG_id_tag_update(&gso->gpd->id, ID_RECALC_GEOMETRY); + WM_event_add_notifier(C, NC_SCENE | ND_TOOLSETTINGS, NULL); + } + + return OPERATOR_RUNNING_MODAL; +} + +/* Also used for weight paint. */ +void GPENCIL_OT_sculpt_paint(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Stroke Sculpt"; + ot->idname = "GPENCIL_OT_sculpt_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 */ + PropertyRNA *prop; + prop = RNA_def_collection_runtime(ot->srna, "stroke", &RNA_OperatorStrokeElement, "Stroke", ""); + RNA_def_property_flag(prop, PROP_HIDDEN | PROP_SKIP_SAVE); + + 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_HIDDEN | PROP_SKIP_SAVE); +} diff --git a/source/blender/editors/gpencil/gpencil_select.c b/source/blender/editors/gpencil/gpencil_select.c index be265ed4bd5..26b68707d55 100644 --- a/source/blender/editors/gpencil/gpencil_select.c +++ b/source/blender/editors/gpencil/gpencil_select.c @@ -84,17 +84,40 @@ static int gpencil_select_mode_from_sculpt(eGP_Sculpt_SelectMaskFlag mode) } } +/* Convert vertex mask mode to Select mode */ +static int gpencil_select_mode_from_vertex(eGP_Sculpt_SelectMaskFlag mode) +{ + if (mode & GP_VERTEX_MASK_SELECTMODE_POINT) { + return GP_SELECTMODE_POINT; + } + else if (mode & GP_VERTEX_MASK_SELECTMODE_STROKE) { + return GP_SELECTMODE_STROKE; + } + else if (mode & GP_VERTEX_MASK_SELECTMODE_SEGMENT) { + return GP_SELECTMODE_SEGMENT; + } + else { + return GP_SELECTMODE_POINT; + } +} + static bool gpencil_select_poll(bContext *C) { bGPdata *gpd = ED_gpencil_data_get_active(C); + ToolSettings *ts = CTX_data_tool_settings(C); if (GPENCIL_SCULPT_MODE(gpd)) { - ToolSettings *ts = CTX_data_tool_settings(C); if (!(GPENCIL_ANY_SCULPT_MASK(ts->gpencil_selectmode_sculpt))) { return false; } } + if (GPENCIL_VERTEX_MODE(gpd)) { + if (!(GPENCIL_ANY_VERTEX_MASK(ts->gpencil_selectmode_vertex))) { + return false; + } + } + /* we just need some visible strokes, and to be in editmode or other modes only to catch event */ if (GPENCIL_ANY_MODE(gpd)) { /* TODO: include a check for visible strokes? */ @@ -350,7 +373,7 @@ static void gp_select_same_layer(bContext *C) Scene *scene = CTX_data_scene(C); CTX_DATA_BEGIN (C, bGPDlayer *, gpl, editable_gpencil_layers) { - bGPDframe *gpf = BKE_gpencil_layer_getframe(gpl, CFRA, GP_GETFRAME_USE_PREV); + bGPDframe *gpf = BKE_gpencil_layer_frame_get(gpl, CFRA, GP_GETFRAME_USE_PREV); bGPDstroke *gps; bool found = false; @@ -832,7 +855,7 @@ void GPENCIL_OT_select_less(wmOperatorType *ot) * from gpencil_paint.c #gp_stroke_eraser_dostroke(). * It would be great to de-duplicate the logic here sometime, but that can wait. */ -static bool gp_stroke_do_circle_sel(bGPdata *gpd, +static bool gp_stroke_do_circle_sel(bGPdata *UNUSED(gpd), bGPDlayer *gpl, bGPDstroke *gps, GP_SpaceConversion *gsc, @@ -845,13 +868,12 @@ static bool gp_stroke_do_circle_sel(bGPdata *gpd, const int selectmode, const float scale) { - const bool is_multiedit = (bool)GPENCIL_MULTIEDIT_SESSIONS_ON(gpd); bGPDspoint *pt1 = NULL; bGPDspoint *pt2 = NULL; int x0 = 0, y0 = 0, x1 = 0, y1 = 0; int i; bool changed = false; - bGPDstroke *gps_active = (!is_multiedit) ? gps->runtime.gps_orig : gps; + bGPDstroke *gps_active = (gps->runtime.gps_orig) ? gps->runtime.gps_orig : gps; bGPDspoint *pt_active = NULL; if (gps->totpoints == 1) { @@ -910,22 +932,22 @@ static bool gp_stroke_do_circle_sel(bGPdata *gpd, */ hit = true; if (select) { - pt_active = (!is_multiedit) ? pt1->runtime.pt_orig : pt1; + pt_active = pt1->runtime.pt_orig; if (pt_active != NULL) { pt_active->flag |= GP_SPOINT_SELECT; } - pt_active = (!is_multiedit) ? pt2->runtime.pt_orig : pt2; + pt_active = pt2->runtime.pt_orig; if (pt_active != NULL) { pt_active->flag |= GP_SPOINT_SELECT; } changed = true; } else { - pt_active = (!is_multiedit) ? pt1->runtime.pt_orig : pt1; + pt_active = pt1->runtime.pt_orig; if (pt_active != NULL) { pt_active->flag &= ~GP_SPOINT_SELECT; } - pt_active = (!is_multiedit) ? pt2->runtime.pt_orig : pt2; + pt_active = pt2->runtime.pt_orig; if (pt_active != NULL) { pt_active->flag &= ~GP_SPOINT_SELECT; } @@ -942,7 +964,7 @@ static bool gp_stroke_do_circle_sel(bGPdata *gpd, /* if stroke mode expand selection */ if ((hit) && (selectmode == GP_SELECTMODE_STROKE)) { for (i = 0, pt1 = gps->points; i < gps->totpoints; i++, pt1++) { - pt_active = (!is_multiedit) ? pt1->runtime.pt_orig : pt1; + pt_active = (pt1->runtime.pt_orig) ? pt1->runtime.pt_orig : pt1; if (pt_active != NULL) { if (select) { pt_active->flag |= GP_SPOINT_SELECT; @@ -955,7 +977,7 @@ static bool gp_stroke_do_circle_sel(bGPdata *gpd, } /* expand selection to segment */ - pt_active = (!is_multiedit) ? pt1->runtime.pt_orig : pt1; + pt_active = (pt1->runtime.pt_orig) ? pt1->runtime.pt_orig : pt1; if ((hit) && (selectmode == GP_SELECTMODE_SEGMENT) && (select) && (pt_active != NULL)) { float r_hita[3], r_hitb[3]; bool hit_select = (bool)(pt1->flag & GP_SPOINT_SELECT); @@ -976,9 +998,17 @@ static int gpencil_circle_select_exec(bContext *C, wmOperator *op) ToolSettings *ts = CTX_data_tool_settings(C); Object *ob = CTX_data_active_object(C); - const int selectmode = (ob && ob->mode == OB_MODE_SCULPT_GPENCIL) ? - gpencil_select_mode_from_sculpt(ts->gpencil_selectmode_sculpt) : - ts->gpencil_selectmode_edit; + int selectmode; + if (ob && ob->mode == OB_MODE_SCULPT_GPENCIL) { + selectmode = gpencil_select_mode_from_sculpt(ts->gpencil_selectmode_sculpt); + } + else if (ob && ob->mode == OB_MODE_VERTEX_GPENCIL) { + selectmode = gpencil_select_mode_from_vertex(ts->gpencil_selectmode_vertex); + } + else { + selectmode = ts->gpencil_selectmode_edit; + } + const float scale = ts->gp_sculpt.isect_threshold; /* if not edit/sculpt mode, the event is catched but not processed */ @@ -1100,9 +1130,16 @@ static int gpencil_generic_select_exec(bContext *C, ToolSettings *ts = CTX_data_tool_settings(C); ScrArea *sa = CTX_wm_area(C); - const short selectmode = (ob && ob->mode == OB_MODE_SCULPT_GPENCIL) ? - gpencil_select_mode_from_sculpt(ts->gpencil_selectmode_sculpt) : - ts->gpencil_selectmode_edit; + int selectmode; + if (ob && ob->mode == OB_MODE_SCULPT_GPENCIL) { + selectmode = gpencil_select_mode_from_sculpt(ts->gpencil_selectmode_sculpt); + } + else if (ob && ob->mode == OB_MODE_VERTEX_GPENCIL) { + selectmode = gpencil_select_mode_from_vertex(ts->gpencil_selectmode_vertex); + } + else { + selectmode = ts->gpencil_selectmode_edit; + } const bool strokemode = ((selectmode == GP_SELECTMODE_STROKE) && ((gpd->flag & GP_DATA_STROKE_PAINTMODE) == 0)); @@ -1145,16 +1182,16 @@ static int gpencil_generic_select_exec(bContext *C, /* select/deselect points */ GP_EVALUATED_STROKES_BEGIN(gpstroke_iter, C, gpl, gps) { - bGPDstroke *gps_active = (!is_multiedit) ? gps->runtime.gps_orig : gps; + bGPDstroke *gps_active = (gps->runtime.gps_orig) ? gps->runtime.gps_orig : gps; bGPDspoint *pt; int i; bool hit = false; for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) { - if ((!is_multiedit) && (pt->runtime.pt_orig == NULL)) { + if (pt->runtime.pt_orig == NULL) { continue; } - bGPDspoint *pt_active = (!is_multiedit) ? pt->runtime.pt_orig : pt; + bGPDspoint *pt_active = pt->runtime.pt_orig; /* convert point coords to screenspace */ const bool is_inside = is_inside_fn(gps, pt, &gsc, gpstroke_iter.diff_mat, user_data); @@ -1192,7 +1229,7 @@ static int gpencil_generic_select_exec(bContext *C, if ((!is_multiedit) && (pt->runtime.pt_orig == NULL)) { continue; } - bGPDspoint *pt_active = (!is_multiedit) ? pt->runtime.pt_orig : pt; + bGPDspoint *pt_active = (pt->runtime.pt_orig) ? pt->runtime.pt_orig : pt; if (sel_op_result) { pt_active->flag |= GP_SPOINT_SELECT; @@ -1419,6 +1456,10 @@ static int gpencil_select_exec(bContext *C, wmOperator *op) whole = (bool)(gpencil_select_mode_from_sculpt(ts->gpencil_selectmode_sculpt) == GP_SELECTMODE_STROKE); } + else if ((ob) && (ob->mode == OB_MODE_VERTEX_GPENCIL)) { + whole = (bool)(gpencil_select_mode_from_vertex(ts->gpencil_selectmode_sculpt) == + GP_SELECTMODE_STROKE); + } else { whole = (bool)(ts->gpencil_selectmode_edit == GP_SELECTMODE_STROKE); } @@ -1433,7 +1474,7 @@ static int gpencil_select_exec(bContext *C, wmOperator *op) /* XXX: maybe we should go from the top of the stack down instead... */ GP_EVALUATED_STROKES_BEGIN(gpstroke_iter, C, gpl, gps) { - bGPDstroke *gps_active = (!is_multiedit) ? gps->runtime.gps_orig : gps; + bGPDstroke *gps_active = (gps->runtime.gps_orig) ? gps->runtime.gps_orig : gps; bGPDspoint *pt; int i; @@ -1526,9 +1567,16 @@ static int gpencil_select_exec(bContext *C, wmOperator *op) hit_stroke->flag |= GP_STROKE_SELECT; /* expand selection to segment */ - const short selectmode = (ob && ob->mode == OB_MODE_SCULPT_GPENCIL) ? - gpencil_select_mode_from_sculpt(ts->gpencil_selectmode_sculpt) : - ts->gpencil_selectmode_edit; + int selectmode; + if (ob && ob->mode == OB_MODE_SCULPT_GPENCIL) { + selectmode = gpencil_select_mode_from_sculpt(ts->gpencil_selectmode_sculpt); + } + else if (ob && ob->mode == OB_MODE_VERTEX_GPENCIL) { + selectmode = gpencil_select_mode_from_vertex(ts->gpencil_selectmode_vertex); + } + else { + selectmode = ts->gpencil_selectmode_edit; + } if (selectmode == GP_SELECTMODE_SEGMENT) { float r_hita[3], r_hitb[3]; @@ -1606,4 +1654,108 @@ void GPENCIL_OT_select(wmOperatorType *ot) RNA_def_property_flag(prop, PROP_HIDDEN); } +/* Select by Vertex Color. */ +static bool gpencil_select_color_poll(bContext *C) +{ + ToolSettings *ts = CTX_data_tool_settings(C); + Object *ob = CTX_data_active_object(C); + if ((ob == NULL) || (ob->type != OB_GPENCIL)) { + return false; + } + bGPdata *gpd = (bGPdata *)ob->data; + + if (GPENCIL_VERTEX_MODE(gpd)) { + if (!(GPENCIL_ANY_VERTEX_MASK(ts->gpencil_selectmode_vertex))) { + return false; + } + + /* Any data to use. */ + if (gpd->layers.first) { + return true; + } + } + + return false; +} + +static int gpencil_select_color_exec(bContext *C, wmOperator *op) +{ + const float threshold = RNA_float_get(op->ptr, "threshold"); + + ToolSettings *ts = CTX_data_tool_settings(C); + Object *ob = CTX_data_active_object(C); + bGPdata *gpd = (bGPdata *)ob->data; + if (!GPENCIL_VERTEX_MODE(gpd)) { + return OPERATOR_CANCELLED; + } + + Paint *paint = &ts->gp_vertexpaint->paint; + Brush *brush = paint->brush; + bool done = false; + + float hsv_brush[3], hsv_stroke[3]; + rgb_to_hsv_compat_v(brush->rgb, hsv_brush); + + /* Select any visible stroke that uses this color */ + CTX_DATA_BEGIN (C, bGPDstroke *, gps, editable_gpencil_strokes) { + bGPDspoint *pt; + int i; + bool gps_selected = false; + /* Check all stroke points. */ + for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) { + if (pt->vert_color[3] < 0.03f) { + continue; + } + + rgb_to_hsv_compat_v(pt->vert_color, hsv_stroke); + /* Only check Hue to get full value and saturation ranges. */ + if (compare_ff(hsv_stroke[0], hsv_brush[0], threshold)) { + pt->flag |= GP_SPOINT_SELECT; + gps_selected = true; + } + } + + if (gps_selected) { + gps->flag |= GP_STROKE_SELECT; + done = true; + } + } + CTX_DATA_END; + + if (done) { + /* updates */ + DEG_id_tag_update(&gpd->id, ID_RECALC_GEOMETRY); + + /* copy on write tag is needed, or else no refresh happens */ + DEG_id_tag_update(&gpd->id, ID_RECALC_COPY_ON_WRITE); + + WM_event_add_notifier(C, NC_GPENCIL | NA_SELECTED, NULL); + WM_event_add_notifier(C, NC_GEOM | ND_SELECT, NULL); + } + + return OPERATOR_FINISHED; +} + +void GPENCIL_OT_select_color(wmOperatorType *ot) +{ + PropertyRNA *prop; + + /* identifiers */ + ot->name = "Select Color"; + ot->idname = "GPENCIL_OT_select_color"; + ot->description = "Select all strokes with same color"; + + /* callbacks */ + ot->exec = gpencil_select_color_exec; + ot->poll = gpencil_select_color_poll; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + /* properties */ + prop = RNA_def_float(ot->srna, "threshold", 0.01f, 0.0f, 1.0f, "Threshold", "", 0.0f, 1.0f); + /* avoid re-using last var */ + RNA_def_property_flag(prop, PROP_SKIP_SAVE); +} + /** \} */ diff --git a/source/blender/editors/gpencil/gpencil_undo.c b/source/blender/editors/gpencil/gpencil_undo.c index 7b57dacd3e4..f9403fd94b0 100644 --- a/source/blender/editors/gpencil/gpencil_undo.c +++ b/source/blender/editors/gpencil/gpencil_undo.c @@ -90,14 +90,14 @@ int ED_undo_gpencil_step(bContext *C, int step, const char *name) if (gpd_ptr) { if (*gpd_ptr) { bGPdata *gpd = *gpd_ptr; - bGPDlayer *gpl, *gpld; + bGPDlayer *gpld; BKE_gpencil_free_layers(&gpd->layers); /* copy layers */ BLI_listbase_clear(&gpd->layers); - for (gpl = new_gpd->layers.first; gpl; gpl = gpl->next) { + LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd->layers) { /* make a copy of source layer and its data */ gpld = BKE_gpencil_layer_duplicate(gpl); BLI_addtail(&gpd->layers, gpld); diff --git a/source/blender/editors/gpencil/gpencil_utils.c b/source/blender/editors/gpencil/gpencil_utils.c index 9c2e0f100cc..a975af1c19a 100644 --- a/source/blender/editors/gpencil/gpencil_utils.c +++ b/source/blender/editors/gpencil/gpencil_utils.c @@ -328,7 +328,7 @@ bool ED_gpencil_data_owner_is_annotation(PointerRNA *owner_ptr) bool ED_gpencil_has_keyframe_v3d(Scene *UNUSED(scene), Object *ob, int cfra) { if (ob && ob->data && (ob->type == OB_GPENCIL)) { - bGPDlayer *gpl = BKE_gpencil_layer_getactive(ob->data); + bGPDlayer *gpl = BKE_gpencil_layer_active_get(ob->data); if (gpl) { if (gpl->actframe) { // XXX: assumes that frame has been fetched already @@ -336,7 +336,7 @@ bool ED_gpencil_has_keyframe_v3d(Scene *UNUSED(scene), Object *ob, int cfra) } else { /* XXX: disabled as could be too much of a penalty */ - /* return BKE_gpencil_layer_find_frame(gpl, cfra); */ + /* return BKE_gpencil_layer_frame_find(gpl, cfra); */ } } } @@ -367,7 +367,7 @@ bool gp_active_layer_poll(bContext *C) return false; } bGPdata *gpd = (bGPdata *)ob->data; - bGPDlayer *gpl = BKE_gpencil_layer_getactive(gpd); + bGPDlayer *gpl = BKE_gpencil_layer_active_get(gpd); return (gpl != NULL); } @@ -553,10 +553,10 @@ bool ED_gpencil_stroke_color_use(Object *ob, const bGPDlayer *gpl, const bGPDstr MaterialGPencilStyle *gp_style = BKE_gpencil_material_settings(ob, gps->mat_nr + 1); if (gp_style != NULL) { - if (gp_style->flag & GP_STYLE_COLOR_HIDE) { + if (gp_style->flag & GP_MATERIAL_HIDE) { return false; } - if (((gpl->flag & GP_LAYER_UNLOCK_COLOR) == 0) && (gp_style->flag & GP_STYLE_COLOR_LOCKED)) { + if (((gpl->flag & GP_LAYER_UNLOCK_COLOR) == 0) && (gp_style->flag & GP_MATERIAL_LOCKED)) { return false; } } @@ -630,8 +630,7 @@ void gp_point_to_parent_space(const bGPDspoint *pt, const float diff_mat[4][4], /** * Change position relative to parent object */ -void gp_apply_parent( - Depsgraph *depsgraph, Object *obact, bGPdata *gpd, bGPDlayer *gpl, bGPDstroke *gps) +void gp_apply_parent(Depsgraph *depsgraph, Object *obact, bGPDlayer *gpl, bGPDstroke *gps) { bGPDspoint *pt; int i; @@ -641,7 +640,7 @@ void gp_apply_parent( float inverse_diff_mat[4][4]; float fpt[3]; - ED_gpencil_parent_location(depsgraph, obact, gpd, gpl, diff_mat); + BKE_gpencil_parent_matrix_get(depsgraph, obact, gpl, diff_mat); invert_m4_m4(inverse_diff_mat, diff_mat); for (i = 0; i < gps->totpoints; i++) { @@ -654,15 +653,14 @@ void gp_apply_parent( /** * Change point position relative to parent object */ -void gp_apply_parent_point( - Depsgraph *depsgraph, Object *obact, bGPdata *gpd, bGPDlayer *gpl, bGPDspoint *pt) +void gp_apply_parent_point(Depsgraph *depsgraph, Object *obact, bGPDlayer *gpl, bGPDspoint *pt) { /* undo matrix */ float diff_mat[4][4]; float inverse_diff_mat[4][4]; float fpt[3]; - ED_gpencil_parent_location(depsgraph, obact, gpd, gpl, diff_mat); + BKE_gpencil_parent_matrix_get(depsgraph, obact, gpl, diff_mat); invert_m4_m4(inverse_diff_mat, diff_mat); mul_v3_m4v3(fpt, inverse_diff_mat, &pt->x); @@ -870,7 +868,7 @@ bool gp_point_xy_to_3d(const GP_SpaceConversion *gsc, const RegionView3D *rv3d = gsc->region->regiondata; float rvec[3]; - ED_gp_get_drawing_reference( + ED_gpencil_drawing_reference_get( scene, gsc->ob, gsc->gpl, scene->toolsettings->gpencil_v3d_align, rvec); float zfac = ED_view3d_calc_zfac(rv3d, rvec, NULL); @@ -930,7 +928,7 @@ void gp_stroke_convertcoords_tpoint(Scene *scene, /* Current method just converts each point in screen-coordinates to * 3D-coordinates using the 3D-cursor as reference. */ - ED_gp_get_drawing_reference(scene, ob, gpl, ts->gpencil_v3d_align, rvec); + ED_gpencil_drawing_reference_get(scene, ob, gpl, ts->gpencil_v3d_align, rvec); zfac = ED_view3d_calc_zfac(region->regiondata, rvec, NULL); if (ED_view3d_project_float_global(region, rvec, mval_prj, V3D_PROJ_TEST_NOP) == @@ -949,7 +947,7 @@ void gp_stroke_convertcoords_tpoint(Scene *scene, * Get drawing reference point for conversion or projection of the stroke * \param[out] r_vec : Reference point found */ -void ED_gp_get_drawing_reference( +void ED_gpencil_drawing_reference_get( const Scene *scene, const Object *ob, bGPDlayer *UNUSED(gpl), char align_flag, float r_vec[3]) { const float *fp = scene->cursor.location; @@ -979,7 +977,6 @@ void ED_gpencil_project_stroke_to_view(bContext *C, bGPDlayer *gpl, bGPDstroke * Scene *scene = CTX_data_scene(C); Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C); Object *ob = CTX_data_active_object(C); - bGPdata *gpd = (bGPdata *)ob->data; GP_SpaceConversion gsc = {NULL}; bGPDspoint *pt; @@ -990,7 +987,7 @@ void ED_gpencil_project_stroke_to_view(bContext *C, bGPDlayer *gpl, bGPDstroke * /* init space conversion stuff */ gp_point_conversion_init(C, &gsc); - ED_gpencil_parent_location(depsgraph, ob, gpd, gpl, diff_mat); + BKE_gpencil_parent_matrix_get(depsgraph, ob, gpl, diff_mat); invert_m4_m4(inverse_diff_mat, diff_mat); /* Adjust each point */ @@ -1182,7 +1179,6 @@ void gp_subdivide_stroke(bGPDstroke *gps, const int subdivide) if (gps->dvert != NULL) { gps->dvert = MEM_recallocN(gps->dvert, sizeof(*gps->dvert) * gps->totpoints); } - gps->flag |= GP_STROKE_RECALC_GEOMETRY; /* move points from last to first to new place */ i2 = gps->totpoints - 1; @@ -1197,6 +1193,7 @@ void gp_subdivide_stroke(bGPDstroke *gps, const int subdivide) pt_final->flag = pt->flag; pt_final->uv_fac = pt->uv_fac; pt_final->uv_rot = pt->uv_rot; + copy_v4_v4(pt_final->vert_color, pt->vert_color); if (gps->dvert != NULL) { MDeformVert *dvert = &gps->dvert[i]; @@ -1223,6 +1220,7 @@ void gp_subdivide_stroke(bGPDstroke *gps, const int subdivide) pt_final->time = interpf(pt->time, next->time, 0.5f); pt_final->uv_fac = interpf(pt->uv_fac, next->uv_fac, 0.5f); pt_final->uv_rot = interpf(pt->uv_rot, next->uv_rot, 0.5f); + interp_v4_v4v4(pt_final->vert_color, pt->vert_color, next->vert_color, 0.5f); if (gps->dvert != NULL) { MDeformVert *dvert_final = &gps->dvert[i2]; @@ -1251,116 +1249,11 @@ void gp_subdivide_stroke(bGPDstroke *gps, const int subdivide) /* free temp memory */ MEM_SAFE_FREE(temp_points); } + /* Calc geometry data. */ + BKE_gpencil_stroke_geometry_update(gps); } -/** - * Add randomness to stroke - * \param gps: Stroke data - * \param brush: Brush data - */ -void gp_randomize_stroke(bGPDstroke *gps, Brush *brush, RNG *rng) -{ - bGPDspoint *pt1, *pt2, *pt3; - float v1[3]; - float v2[3]; - if (gps->totpoints < 3) { - return; - } - - /* get two vectors using 3 points */ - pt1 = &gps->points[0]; - pt2 = &gps->points[1]; - pt3 = &gps->points[(int)(gps->totpoints * 0.75)]; - - sub_v3_v3v3(v1, &pt2->x, &pt1->x); - sub_v3_v3v3(v2, &pt3->x, &pt2->x); - normalize_v3(v1); - normalize_v3(v2); - - /* get normal vector to plane created by two vectors */ - float normal[3]; - cross_v3_v3v3(normal, v1, v2); - normalize_v3(normal); - - /* get orthogonal vector to plane to rotate random effect */ - float ortho[3]; - cross_v3_v3v3(ortho, v1, normal); - normalize_v3(ortho); - - /* Read all points and apply shift vector (first and last point not modified) */ - for (int i = 1; i < gps->totpoints - 1; i++) { - bGPDspoint *pt = &gps->points[i]; - /* get vector with shift (apply a division because random is too sensitive */ - const float fac = BLI_rng_get_float(rng) * (brush->gpencil_settings->draw_random_sub / 10.0f); - float svec[3]; - copy_v3_v3(svec, ortho); - if (BLI_rng_get_float(rng) > 0.5f) { - mul_v3_fl(svec, -fac); - } - else { - mul_v3_fl(svec, fac); - } - - /* apply shift */ - add_v3_v3(&pt->x, svec); - } -} - -/* ******************************************************** */ -/* Layer Parenting - Compute Parent Transforms */ - -/* calculate difference matrix */ -void ED_gpencil_parent_location(const Depsgraph *depsgraph, - Object *obact, - bGPdata *UNUSED(gpd), - bGPDlayer *gpl, - float diff_mat[4][4]) -{ - Object *ob_eval = depsgraph != NULL ? DEG_get_evaluated_object(depsgraph, obact) : obact; - Object *obparent = gpl->parent; - Object *obparent_eval = depsgraph != NULL ? DEG_get_evaluated_object(depsgraph, obparent) : - obparent; - - /* if not layer parented, try with object parented */ - if (obparent_eval == NULL) { - if (ob_eval != NULL) { - if (ob_eval->type == OB_GPENCIL) { - copy_m4_m4(diff_mat, ob_eval->obmat); - return; - } - } - /* not gpencil object */ - unit_m4(diff_mat); - return; - } - else { - if ((gpl->partype == PAROBJECT) || (gpl->partype == PARSKEL)) { - mul_m4_m4m4(diff_mat, obparent_eval->obmat, gpl->inverse); - add_v3_v3(diff_mat[3], ob_eval->obmat[3]); - return; - } - else if (gpl->partype == PARBONE) { - bPoseChannel *pchan = BKE_pose_channel_find_name(obparent_eval->pose, gpl->parsubstr); - if (pchan) { - float tmp_mat[4][4]; - mul_m4_m4m4(tmp_mat, obparent_eval->obmat, pchan->pose_mat); - mul_m4_m4m4(diff_mat, tmp_mat, gpl->inverse); - add_v3_v3(diff_mat[3], ob_eval->obmat[3]); - } - else { - /* if bone not found use object (armature) */ - mul_m4_m4m4(diff_mat, obparent_eval->obmat, gpl->inverse); - add_v3_v3(diff_mat[3], ob_eval->obmat[3]); - } - return; - } - else { - unit_m4(diff_mat); /* not defined type */ - } - } -} - -/* reset parent matrix for all layers */ +/* Reset parent matrix for all layers. */ void ED_gpencil_reset_layers_parent(Depsgraph *depsgraph, Object *obact, bGPdata *gpd) { bGPDspoint *pt; @@ -1370,7 +1263,7 @@ void ED_gpencil_reset_layers_parent(Depsgraph *depsgraph, Object *obact, bGPdata float gpl_loc[3]; zero_v3(gpl_loc); - for (bGPDlayer *gpl = gpd->layers.first; gpl; gpl = gpl->next) { + LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd->layers) { if (gpl->parent != NULL) { /* calculate new matrix */ if ((gpl->partype == PAROBJECT) || (gpl->partype == PARSKEL)) { @@ -1390,12 +1283,12 @@ void ED_gpencil_reset_layers_parent(Depsgraph *depsgraph, Object *obact, bGPdata /* only redo if any change */ if (!equals_m4m4(gpl->inverse, cur_mat)) { /* first apply current transformation to all strokes */ - ED_gpencil_parent_location(depsgraph, obact, gpd, gpl, diff_mat); + BKE_gpencil_parent_matrix_get(depsgraph, obact, gpl, diff_mat); /* undo local object */ sub_v3_v3(diff_mat[3], gpl_loc); - for (bGPDframe *gpf = gpl->frames.first; gpf; gpf = gpf->next) { - for (bGPDstroke *gps = gpf->strokes.first; gps; gps = gps->next) { + LISTBASE_FOREACH (bGPDframe *, gpf, &gpl->frames) { + LISTBASE_FOREACH (bGPDstroke *, gps, &gpf->strokes) { for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) { mul_m4_v3(diff_mat, &pt->x); } @@ -1411,10 +1304,7 @@ void ED_gpencil_reset_layers_parent(Depsgraph *depsgraph, Object *obact, bGPdata /* GP Object Stuff */ /* Helper function to create new OB_GPENCIL Object */ -Object *ED_gpencil_add_object(bContext *C, - Scene *UNUSED(scene), - const float loc[3], - ushort local_view_bits) +Object *ED_gpencil_add_object(bContext *C, const float loc[3], ushort local_view_bits) { float rot[3] = {0.0f}; @@ -1437,7 +1327,7 @@ void ED_gpencil_add_defaults(bContext *C, Object *ob) /* if not exist, create a new one */ if ((paint->brush == NULL) || (paint->brush->gpencil_settings == NULL)) { /* create new brushes */ - BKE_brush_gpencil_presets(bmain, ts); + BKE_brush_gpencil_paint_presets(bmain, ts); } /* ensure a color exists and is assigned to object */ @@ -1676,8 +1566,11 @@ static bool gp_check_cursor_region(bContext *C, int mval_i[2]) ScrArea *sa = CTX_wm_area(C); Object *ob = CTX_data_active_object(C); - if ((ob == NULL) || - (!ELEM(ob->mode, OB_MODE_PAINT_GPENCIL, OB_MODE_SCULPT_GPENCIL, OB_MODE_WEIGHT_GPENCIL))) { + if ((ob == NULL) || (!ELEM(ob->mode, + OB_MODE_PAINT_GPENCIL, + OB_MODE_SCULPT_GPENCIL, + OB_MODE_WEIGHT_GPENCIL, + OB_MODE_VERTEX_GPENCIL))) { return false; } @@ -1753,22 +1646,14 @@ static void gp_brush_cursor_draw(bContext *C, int x, int y, void *customdata) Scene *scene = CTX_data_scene(C); Object *ob = CTX_data_active_object(C); ARegion *region = CTX_wm_region(C); + Paint *paint = BKE_paint_get_active_from_context(C); - GP_Sculpt_Settings *gset = &scene->toolsettings->gp_sculpt; bGPdata *gpd = ED_gpencil_data_get_active(C); - GP_Sculpt_Data *gp_brush = NULL; Brush *brush = NULL; Material *ma = NULL; MaterialGPencilStyle *gp_style = NULL; float *last_mouse_position = customdata; - if ((gpd) && (gpd->flag & GP_DATA_STROKE_WEIGHTMODE)) { - gp_brush = &gset->brush[gset->weighttype]; - } - else { - gp_brush = &gset->brush[gset->brushtype]; - } - /* default radius and color */ float color[3] = {1.0f, 1.0f, 1.0f}; float darkcolor[3]; @@ -1794,7 +1679,7 @@ static void gp_brush_cursor_draw(bContext *C, int x, int y, void *customdata) return; } - if ((brush->gpencil_settings->flag & GP_BRUSH_ENABLE_CURSOR) == 0) { + if ((paint->flags & PAINT_SHOW_BRUSH) == 0) { return; } @@ -1805,7 +1690,7 @@ static void gp_brush_cursor_draw(bContext *C, int x, int y, void *customdata) } /* get current drawing color */ - ma = BKE_gpencil_object_material_get_from_brush(ob, brush); + ma = BKE_gpencil_object_material_from_brush_get(ob, brush); if (ma) { gp_style = ma->gp_style; @@ -1822,29 +1707,73 @@ static void gp_brush_cursor_draw(bContext *C, int x, int y, void *customdata) copy_v3_v3(color, gp_style->stroke_rgba); } else { - radius = 5.0f; - copy_v3_v3(color, brush->add_col); + /* Only Tint tool must show big cursor. */ + if (brush->gpencil_tool == GPAINT_TOOL_TINT) { + radius = brush->size; + copy_v3_v3(color, brush->rgb); + } + else { + radius = 5.0f; + copy_v3_v3(color, brush->add_col); + } } } } - /* for sculpt use sculpt brush size */ - if (GPENCIL_SCULPT_OR_WEIGHT_MODE(gpd)) { - if (gp_brush) { - if ((gp_brush->flag & GP_SCULPT_FLAG_ENABLE_CURSOR) == 0) { - return; - } + /* Sculpt use sculpt brush size */ + if (GPENCIL_SCULPT_MODE(gpd)) { + brush = scene->toolsettings->gp_sculptpaint->paint.brush; + if ((brush == NULL) || (brush->gpencil_settings == NULL)) { + return; + } + if ((paint->flags & PAINT_SHOW_BRUSH) == 0) { + return; + } - radius = gp_brush->size; - if (gp_brush->flag & (GP_SCULPT_FLAG_INVERT | GP_SCULPT_FLAG_TMP_INVERT)) { - copy_v3_v3(color, gp_brush->curcolor_sub); - } - else { - copy_v3_v3(color, gp_brush->curcolor_add); - } + radius = brush->size; + if (brush->gpencil_settings->sculpt_flag & + (GP_SCULPT_FLAG_INVERT | GP_SCULPT_FLAG_TMP_INVERT)) { + copy_v3_v3(color, brush->sub_col); + } + else { + copy_v3_v3(color, brush->add_col); + } + } + + /* Weight Paint */ + if (GPENCIL_WEIGHT_MODE(gpd)) { + brush = scene->toolsettings->gp_weightpaint->paint.brush; + if ((brush == NULL) || (brush->gpencil_settings == NULL)) { + return; + } + if ((paint->flags & PAINT_SHOW_BRUSH) == 0) { + return; + } + + radius = brush->size; + if (brush->gpencil_settings->sculpt_flag & + (GP_SCULPT_FLAG_INVERT | GP_SCULPT_FLAG_TMP_INVERT)) { + copy_v3_v3(color, brush->sub_col); + } + else { + copy_v3_v3(color, brush->add_col); } } + /* For Vertex Paint use brush size. */ + if (GPENCIL_VERTEX_MODE(gpd)) { + brush = scene->toolsettings->gp_vertexpaint->paint.brush; + if ((brush == NULL) || (brush->gpencil_settings == NULL)) { + return; + } + if ((paint->flags & PAINT_SHOW_BRUSH) == 0) { + return; + } + + radius = brush->size; + copy_v3_v3(color, brush->rgb); + } + /* draw icon */ GPUVertFormat *format = immVertexFormat(); uint pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); @@ -1924,30 +1853,6 @@ void ED_gpencil_toggle_brush_cursor(bContext *C, bool enable, void *customdata) } } -/* verify if is using the right brush */ -static void gpencil_verify_brush_type(bContext *C, int newmode) -{ - ToolSettings *ts = CTX_data_tool_settings(C); - GP_Sculpt_Settings *gset = &ts->gp_sculpt; - - switch (newmode) { - case OB_MODE_SCULPT_GPENCIL: - gset->flag &= ~GP_SCULPT_SETT_FLAG_WEIGHT_MODE; - if ((gset->brushtype < 0) || (gset->brushtype >= GP_SCULPT_TYPE_WEIGHT)) { - gset->brushtype = GP_SCULPT_TYPE_PUSH; - } - break; - case OB_MODE_WEIGHT_GPENCIL: - gset->flag |= GP_SCULPT_SETT_FLAG_WEIGHT_MODE; - if ((gset->weighttype < GP_SCULPT_TYPE_WEIGHT) || (gset->weighttype >= GP_SCULPT_TYPE_MAX)) { - gset->weighttype = GP_SCULPT_TYPE_WEIGHT; - } - break; - default: - break; - } -} - /* set object modes */ void ED_gpencil_setup_modes(bContext *C, bGPdata *gpd, int newmode) { @@ -1961,6 +1866,7 @@ void ED_gpencil_setup_modes(bContext *C, bGPdata *gpd, int newmode) gpd->flag &= ~GP_DATA_STROKE_PAINTMODE; gpd->flag &= ~GP_DATA_STROKE_SCULPTMODE; gpd->flag &= ~GP_DATA_STROKE_WEIGHTMODE; + gpd->flag &= ~GP_DATA_STROKE_VERTEXMODE; ED_gpencil_toggle_brush_cursor(C, false, NULL); break; case OB_MODE_PAINT_GPENCIL: @@ -1968,6 +1874,7 @@ void ED_gpencil_setup_modes(bContext *C, bGPdata *gpd, int newmode) gpd->flag |= GP_DATA_STROKE_PAINTMODE; gpd->flag &= ~GP_DATA_STROKE_SCULPTMODE; gpd->flag &= ~GP_DATA_STROKE_WEIGHTMODE; + gpd->flag &= ~GP_DATA_STROKE_VERTEXMODE; ED_gpencil_toggle_brush_cursor(C, true, NULL); break; case OB_MODE_SCULPT_GPENCIL: @@ -1975,7 +1882,7 @@ void ED_gpencil_setup_modes(bContext *C, bGPdata *gpd, int newmode) gpd->flag &= ~GP_DATA_STROKE_PAINTMODE; gpd->flag |= GP_DATA_STROKE_SCULPTMODE; gpd->flag &= ~GP_DATA_STROKE_WEIGHTMODE; - gpencil_verify_brush_type(C, OB_MODE_SCULPT_GPENCIL); + gpd->flag &= ~GP_DATA_STROKE_VERTEXMODE; ED_gpencil_toggle_brush_cursor(C, true, NULL); break; case OB_MODE_WEIGHT_GPENCIL: @@ -1983,7 +1890,15 @@ void ED_gpencil_setup_modes(bContext *C, bGPdata *gpd, int newmode) gpd->flag &= ~GP_DATA_STROKE_PAINTMODE; gpd->flag &= ~GP_DATA_STROKE_SCULPTMODE; gpd->flag |= GP_DATA_STROKE_WEIGHTMODE; - gpencil_verify_brush_type(C, OB_MODE_WEIGHT_GPENCIL); + gpd->flag &= ~GP_DATA_STROKE_VERTEXMODE; + ED_gpencil_toggle_brush_cursor(C, true, NULL); + break; + case OB_MODE_VERTEX_GPENCIL: + gpd->flag &= ~GP_DATA_STROKE_EDITMODE; + gpd->flag &= ~GP_DATA_STROKE_PAINTMODE; + gpd->flag &= ~GP_DATA_STROKE_SCULPTMODE; + gpd->flag &= ~GP_DATA_STROKE_WEIGHTMODE; + gpd->flag |= GP_DATA_STROKE_VERTEXMODE; ED_gpencil_toggle_brush_cursor(C, true, NULL); break; default: @@ -1991,6 +1906,7 @@ void ED_gpencil_setup_modes(bContext *C, bGPdata *gpd, int newmode) gpd->flag &= ~GP_DATA_STROKE_PAINTMODE; gpd->flag &= ~GP_DATA_STROKE_SCULPTMODE; gpd->flag &= ~GP_DATA_STROKE_WEIGHTMODE; + gpd->flag &= ~GP_DATA_STROKE_VERTEXMODE; ED_gpencil_toggle_brush_cursor(C, false, NULL); break; } @@ -2032,6 +1948,7 @@ void ED_gpencil_tpoint_to_point(ARegion *region, /* conversion to 3d format */ gpencil_stroke_convertcoords(region, tpt, origin, p3d); copy_v3_v3(&pt->x, p3d); + zero_v4(pt->vert_color); pt->pressure = tpt->pressure; pt->strength = tpt->strength; @@ -2039,63 +1956,6 @@ void ED_gpencil_tpoint_to_point(ARegion *region, pt->uv_rot = tpt->uv_rot; } -/* texture coordinate utilities */ -void ED_gpencil_calc_stroke_uv(Object *ob, bGPDstroke *gps) -{ - if (gps == NULL) { - return; - } - MaterialGPencilStyle *gp_style = BKE_gpencil_material_settings(ob, gps->mat_nr + 1); - float pixsize; - if (gp_style) { - pixsize = gp_style->texture_pixsize / 1000000.0f; - } - else { - /* use this value by default */ - pixsize = 0.0001f; - } - pixsize = MAX2(pixsize, 0.0000001f); - - bGPDspoint *pt = NULL; - bGPDspoint *ptb = NULL; - int i; - float totlen = 0.0f; - - /* first read all points and calc distance */ - for (i = 0; i < gps->totpoints; i++) { - pt = &gps->points[i]; - /* first point */ - if (i == 0) { - pt->uv_fac = 0.0f; - continue; - } - - ptb = &gps->points[i - 1]; - totlen += len_v3v3(&pt->x, &ptb->x) / pixsize; - pt->uv_fac = totlen; - } - - /* normalize the distance using a factor */ - float factor; - - /* if image, use texture width */ - if ((gp_style) && (gp_style->stroke_style == GP_STYLE_STROKE_STYLE_TEXTURE) && - (gp_style->sima)) { - factor = gp_style->sima->gen_x; - } - else if (totlen == 0) { - return; - } - else { - factor = totlen; - } - - for (i = 0; i < gps->totpoints; i++) { - pt = &gps->points[i]; - pt->uv_fac /= factor; - } -} - /* recalc uv for any stroke using the material */ void ED_gpencil_update_color_uv(Main *bmain, Material *mat) { @@ -2108,11 +1968,11 @@ void ED_gpencil_update_color_uv(Main *bmain, Material *mat) continue; } - for (bGPDlayer *gpl = gpd->layers.first; gpl; gpl = gpl->next) { + LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd->layers) { /* only editable and visible layers are considered */ - if (gpencil_layer_is_editable(gpl)) { - for (bGPDframe *gpf = gpl->frames.first; gpf; gpf = gpf->next) { - for (bGPDstroke *gps = gpf->strokes.first; gps; gps = gps->next) { + if (BKE_gpencil_layer_is_editable(gpl)) { + LISTBASE_FOREACH (bGPDframe *, gpf, &gpl->frames) { + LISTBASE_FOREACH (bGPDstroke *, gps, &gpf->strokes) { /* check if it is editable */ if (ED_gpencil_stroke_color_use(ob, gpl, gps) == false) { continue; @@ -2120,7 +1980,7 @@ void ED_gpencil_update_color_uv(Main *bmain, Material *mat) gps_ma = BKE_gpencil_material(ob, gps->mat_nr + 1); /* update */ if ((gps_ma) && (gps_ma == mat)) { - ED_gpencil_calc_stroke_uv(ob, gps); + BKE_gpencil_stroke_uv_update(gps); } } } @@ -2197,6 +2057,7 @@ static void gp_copy_points(bGPDstroke *gps, bGPDspoint *pt, bGPDspoint *pt_final pt_final->flag = pt->flag; pt_final->uv_fac = pt->uv_fac; pt_final->uv_rot = pt->uv_rot; + copy_v4_v4(pt_final->vert_color, pt->vert_color); if (gps->dvert != NULL) { MDeformVert *dvert = &gps->dvert[i]; @@ -2251,7 +2112,6 @@ static void gp_insert_point( if (gps->dvert != NULL) { gps->dvert = MEM_recallocN(gps->dvert, sizeof(*gps->dvert) * gps->totpoints); } - gps->flag |= GP_STROKE_RECALC_GEOMETRY; /* copy all points */ int i2 = 0; @@ -2275,6 +2135,8 @@ static void gp_insert_point( i2++; } + /* Calc geometry data. */ + BKE_gpencil_stroke_geometry_update(gps); MEM_SAFE_FREE(temp_points); } @@ -2518,10 +2380,9 @@ void ED_gpencil_select_toggle_all(bContext *C, int action) * nothing should be able to touch it */ CTX_DATA_BEGIN (C, bGPDlayer *, gpl, editable_gpencil_layers) { - bGPDframe *gpf; /* deselect all strokes on all frames */ - for (gpf = gpl->frames.first; gpf; gpf = gpf->next) { + LISTBASE_FOREACH (bGPDframe *, gpf, &gpl->frames) { bGPDstroke *gps; for (gps = gpf->strokes.first; gps; gps = gps->next) { @@ -2618,6 +2479,18 @@ tGPspoint *ED_gpencil_sbuffer_ensure(tGPspoint *buffer_array, return buffer_array; } +void ED_gpencil_sbuffer_update_eval(bGPdata *gpd, Object *ob_eval) +{ + bGPdata *gpd_eval = (bGPdata *)ob_eval->data; + + gpd_eval->runtime.sbuffer = gpd->runtime.sbuffer; + gpd_eval->runtime.sbuffer_sflag = gpd->runtime.sbuffer_sflag; + gpd_eval->runtime.sbuffer_used = gpd->runtime.sbuffer_used; + gpd_eval->runtime.sbuffer_size = gpd->runtime.sbuffer_size; + gpd_eval->runtime.tot_cp_points = gpd->runtime.tot_cp_points; + gpd_eval->runtime.cp_points = gpd->runtime.cp_points; +} + /* Tag all scene grease pencil object to update. */ void ED_gpencil_tag_scene_gpencil(Scene *scene) { @@ -2638,3 +2511,107 @@ void ED_gpencil_tag_scene_gpencil(Scene *scene) WM_main_add_notifier(NC_GPENCIL | NA_EDITED, NULL); } + +void ED_gpencil_fill_vertex_color_set(ToolSettings *ts, Brush *brush, bGPDstroke *gps) +{ + if (GPENCIL_USE_VERTEX_COLOR_FILL(ts, brush)) { + copy_v3_v3(gps->vert_color_fill, brush->rgb); + gps->vert_color_fill[3] = brush->gpencil_settings->vertex_factor; + srgb_to_linearrgb_v4(gps->vert_color_fill, gps->vert_color_fill); + } + else { + zero_v4(gps->vert_color_fill); + } +} + +void ED_gpencil_point_vertex_color_set(ToolSettings *ts, Brush *brush, bGPDspoint *pt) +{ + if (GPENCIL_USE_VERTEX_COLOR_STROKE(ts, brush)) { + copy_v3_v3(pt->vert_color, brush->rgb); + pt->vert_color[3] = brush->gpencil_settings->vertex_factor; + srgb_to_linearrgb_v4(pt->vert_color, pt->vert_color); + } + else { + zero_v4(pt->vert_color); + } +} + +void ED_gpencil_sbuffer_vertex_color_set( + Depsgraph *depsgraph, Object *ob, ToolSettings *ts, Brush *brush, Material *material) +{ + bGPdata *gpd = (bGPdata *)ob->data; + Object *ob_eval = (Object *)DEG_get_evaluated_id(depsgraph, &ob->id); + bGPdata *gpd_eval = (bGPdata *)ob_eval->data; + MaterialGPencilStyle *gp_style = material->gp_style; + + float vertex_color[4]; + copy_v3_v3(vertex_color, brush->rgb); + vertex_color[3] = brush->gpencil_settings->vertex_factor; + srgb_to_linearrgb_v4(vertex_color, vertex_color); + + /* Copy fill vertex color. */ + if (GPENCIL_USE_VERTEX_COLOR_FILL(ts, brush)) { + copy_v4_v4(gpd->runtime.vert_color_fill, vertex_color); + } + else { + copy_v4_v4(gpd->runtime.vert_color_fill, gp_style->fill_rgba); + } + /* Copy stroke vertex color. */ + if (GPENCIL_USE_VERTEX_COLOR_STROKE(ts, brush)) { + copy_v4_v4(gpd->runtime.vert_color, vertex_color); + } + else { + copy_v4_v4(gpd->runtime.vert_color, gp_style->stroke_rgba); + } + + /* Copy to eval data because paint operators don't tag refresh until end for speedup painting. */ + if (gpd_eval != NULL) { + copy_v4_v4(gpd_eval->runtime.vert_color, gpd->runtime.vert_color); + copy_v4_v4(gpd_eval->runtime.vert_color_fill, gpd->runtime.vert_color_fill); + gpd_eval->runtime.matid = gpd->runtime.matid; + } +} + +/* Check if the stroke collides with brush. */ +bool ED_gpencil_stroke_check_collision(GP_SpaceConversion *gsc, + bGPDstroke *gps, + float mouse[2], + const int radius, + const float diff_mat[4][4]) +{ + const int offset = (int)ceil(sqrt((radius * radius) * 2)); + bGPDspoint pt_dummy, pt_dummy_ps; + float boundbox_min[2] = {0.0f}; + float boundbox_max[2] = {0.0f}; + float zerov3[3]; + + /* Check we have something to use (only for old files). */ + if (equals_v3v3(zerov3, gps->boundbox_min)) { + BKE_gpencil_stroke_boundingbox_calc(gps); + } + + /* Convert bound box to 2d */ + copy_v3_v3(&pt_dummy.x, gps->boundbox_min); + gp_point_to_parent_space(&pt_dummy, diff_mat, &pt_dummy_ps); + gp_point_to_xy_fl(gsc, gps, &pt_dummy_ps, &boundbox_min[0], &boundbox_min[1]); + + copy_v3_v3(&pt_dummy.x, gps->boundbox_max); + gp_point_to_parent_space(&pt_dummy, diff_mat, &pt_dummy_ps); + gp_point_to_xy_fl(gsc, gps, &pt_dummy_ps, &boundbox_max[0], &boundbox_max[1]); + + /* Ensure the bounding box is oriented to axis. */ + if (boundbox_max[0] < boundbox_min[0]) { + SWAP(float, boundbox_min[0], boundbox_max[0]); + } + if (boundbox_max[1] < boundbox_min[1]) { + SWAP(float, boundbox_min[1], boundbox_max[1]); + } + + rcti rect_stroke = {boundbox_min[0], boundbox_max[0], boundbox_min[1], boundbox_max[1]}; + + /* For mouse, add a small offet to avoid false negative in corners. */ + rcti rect_mouse = {mouse[0] - offset, mouse[0] + offset, mouse[1] - offset, mouse[1] + offset}; + + /* Check collision between both rectangles. */ + return BLI_rcti_isect(&rect_stroke, &rect_mouse, NULL); +} diff --git a/source/blender/editors/gpencil/gpencil_uv.c b/source/blender/editors/gpencil/gpencil_uv.c new file mode 100644 index 00000000000..5e397374437 --- /dev/null +++ b/source/blender/editors/gpencil/gpencil_uv.c @@ -0,0 +1,587 @@ +/* + * 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. + */ + +/** \file + * \ingroup edgpencil + */ + +#include "MEM_guardedalloc.h" + +#include "DNA_gpencil_types.h" + +#include "BLI_blenlib.h" +#include "BLI_string.h" +#include "BLI_math.h" + +#include "BLT_translation.h" + +#include "BKE_context.h" +#include "BKE_gpencil.h" +#include "BKE_unit.h" + +#include "RNA_define.h" +#include "RNA_access.h" + +#include "WM_api.h" +#include "WM_types.h" + +#include "UI_interface.h" + +#include "ED_gpencil.h" +#include "ED_numinput.h" +#include "ED_screen.h" +#include "ED_space_api.h" +#include "ED_view3d.h" + +#include "DEG_depsgraph.h" +#include "DEG_depsgraph_query.h" + +#include "gpencil_intern.h" + +typedef struct GpUvData { + Object *ob; + bGPdata *gpd; + GP_SpaceConversion gsc; + float ob_scale; + + float initial_length; + float pixel_size; /* use when mouse input is interpreted as spatial distance */ + bool is_modal; + + /* Arrays of original loc/rot/scale by stroke. */ + float (*array_loc)[2]; + float *array_rot; + float *array_scale; + + /* modal only */ + float mcenter[2]; + float mouse[2]; + + /** Vector with the original orientation. */ + float vinit_rotation[2]; + + void *draw_handle_pixel; +} GpUvData; + +enum { + GP_UV_ROTATE = 0, + GP_UV_TRANSLATE = 1, + GP_UV_SCALE = 2, + GP_UV_ALL = 3, +}; + +#define SMOOTH_FACTOR 0.3f + +static void gpencil_uv_transform_update_header(wmOperator *op, bContext *C) +{ + const int mode = RNA_enum_get(op->ptr, "mode"); + const char *str = TIP_("Confirm: Enter/LClick, Cancel: (Esc/RClick) %s"); + + char msg[UI_MAX_DRAW_STR]; + ScrArea *sa = CTX_wm_area(C); + + if (sa) { + char flts_str[NUM_STR_REP_LEN * 2]; + switch (mode) { + case GP_UV_TRANSLATE: { + float location[2]; + RNA_float_get_array(op->ptr, "location", location); + BLI_snprintf( + flts_str, NUM_STR_REP_LEN, ", Translation: (%f, %f)", location[0], location[1]); + break; + } + case GP_UV_ROTATE: { + BLI_snprintf(flts_str, + NUM_STR_REP_LEN, + ", Rotation: %f", + RAD2DEG(RNA_float_get(op->ptr, "rotation"))); + break; + } + case GP_UV_SCALE: { + BLI_snprintf( + flts_str, NUM_STR_REP_LEN, ", Scale: %f", RAD2DEG(RNA_float_get(op->ptr, "scale"))); + break; + } + default: + break; + } + BLI_snprintf(msg, sizeof(msg), str, flts_str, flts_str + NUM_STR_REP_LEN); + ED_area_status_text(sa, msg); + } +} + +/* Helper: Get stroke center. */ +static void gpencil_stroke_center(bGPDstroke *gps, float r_center[3]) +{ + bGPDspoint *pt; + int i; + + zero_v3(r_center); + if (gps->totpoints > 0) { + for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) { + add_v3_v3(r_center, &pt->x); + } + + mul_v3_fl(r_center, 1.0f / gps->totpoints); + } +} + +static bool gpencil_uv_transform_init(bContext *C, wmOperator *op, const bool is_modal) +{ + GpUvData *opdata; + if (is_modal) { + float zero[2] = {0.0f}; + RNA_float_set_array(op->ptr, "location", zero); + RNA_float_set(op->ptr, "rotation", 0.0f); + RNA_float_set(op->ptr, "scale", 1.0f); + } + + op->customdata = opdata = MEM_mallocN(sizeof(GpUvData), __func__); + + opdata->is_modal = is_modal; + opdata->ob = CTX_data_active_object(C); + opdata->gpd = (bGPdata *)opdata->ob->data; + gp_point_conversion_init(C, &opdata->gsc); + opdata->array_loc = NULL; + opdata->array_rot = NULL; + opdata->array_scale = NULL; + opdata->ob_scale = mat4_to_scale(opdata->ob->obmat); + + opdata->vinit_rotation[0] = 1.0f; + opdata->vinit_rotation[1] = 0.0f; + + if (is_modal) { + ARegion *region = CTX_wm_region(C); + + opdata->draw_handle_pixel = ED_region_draw_cb_activate( + region->type, ED_region_draw_mouse_line_cb, opdata->mcenter, REGION_DRAW_POST_PIXEL); + } + + /* Calc selected strokes center. */ + zero_v2(opdata->mcenter); + float center[3] = {0.0f}; + int i = 0; + /* Need use evaluated to get the viewport final position. */ + GP_EVALUATED_STROKES_BEGIN(gpstroke_iter, C, gpl, gps) + { + if (gps->flag & GP_STROKE_SELECT) { + float r_center[3]; + gpencil_stroke_center(gps, r_center); + /* Add object location. */ + add_v3_v3(r_center, opdata->ob->obmat[3]); + add_v3_v3(center, r_center); + i++; + } + } + GP_EVALUATED_STROKES_END(gpstroke_iter); + + if (i > 0) { + mul_v3_fl(center, 1.0f / i); + /* Create arrays to save all transformations. */ + opdata->array_loc = MEM_calloc_arrayN(i, 2 * sizeof(float), __func__); + opdata->array_rot = MEM_calloc_arrayN(i, sizeof(float), __func__); + opdata->array_scale = MEM_calloc_arrayN(i, sizeof(float), __func__); + i = 0; + GP_EDITABLE_STROKES_BEGIN (gpstroke_iter, C, gpl, gps) { + if (gps->flag & GP_STROKE_SELECT) { + copy_v2_v2(opdata->array_loc[i], gps->uv_translation); + opdata->array_rot[i] = gps->uv_rotation; + opdata->array_scale[i] = gps->uv_scale; + i++; + } + } + GP_EDITABLE_STROKES_END(gpstroke_iter); + } + /* convert to 2D */ + gp_point_3d_to_xy(&opdata->gsc, GP_STROKE_3DSPACE, center, opdata->mcenter); + + return true; +} + +static void gpencil_uv_transform_exit(bContext *C, wmOperator *op) +{ + GpUvData *opdata; + ScrArea *sa = CTX_wm_area(C); + + opdata = op->customdata; + + if (opdata->is_modal) { + ARegion *region = CTX_wm_region(C); + + ED_region_draw_cb_exit(region->type, opdata->draw_handle_pixel); + } + + WM_cursor_set(CTX_wm_window(C), WM_CURSOR_DEFAULT); + + if (sa) { + ED_area_status_text(sa, NULL); + } + WM_main_add_notifier(NC_GEOM | ND_DATA, NULL); + + MEM_SAFE_FREE(opdata->array_loc); + MEM_SAFE_FREE(opdata->array_rot); + MEM_SAFE_FREE(opdata->array_scale); + MEM_SAFE_FREE(op->customdata); +} + +static void gpencil_transform_fill_cancel(bContext *C, wmOperator *op) +{ + GpUvData *opdata = op->customdata; + UNUSED_VARS(opdata); + + gpencil_uv_transform_exit(C, op); + + /* need to force redisplay or we may still view the modified result */ + ED_region_tag_redraw(CTX_wm_region(C)); +} + +static bool gpencil_uv_transform_calc(bContext *C, wmOperator *op) +{ + const int mode = RNA_enum_get(op->ptr, "mode"); + GpUvData *opdata = op->customdata; + bGPdata *gpd = opdata->gpd; + bool changed = false; + /* Get actual vector. */ + float vr[2]; + sub_v2_v2v2(vr, opdata->mouse, opdata->mcenter); + normalize_v2(vr); + + float location[2]; + RNA_float_get_array(op->ptr, "location", location); + + float uv_rotation = (opdata->is_modal) ? angle_signed_v2v2(opdata->vinit_rotation, vr) : + RNA_float_get(op->ptr, "rotation"); + uv_rotation *= SMOOTH_FACTOR; + + if (opdata->is_modal) { + RNA_float_set(op->ptr, "rotation", uv_rotation); + } + + int i = 0; + + /* Apply transformations to all strokes. */ + if ((mode == GP_UV_TRANSLATE) || (!opdata->is_modal)) { + float mdiff[2]; + mdiff[0] = opdata->mcenter[0] - opdata->mouse[0]; + mdiff[1] = opdata->mcenter[1] - opdata->mouse[1]; + + /* Apply a big amount of smooth always for translate to get smooth result. */ + mul_v2_fl(mdiff, 0.006f); + + /* Apply angle in translation. */ + mdiff[0] *= cos(uv_rotation); + mdiff[1] *= sin(uv_rotation); + if (opdata->is_modal) { + RNA_float_set_array(op->ptr, "location", mdiff); + } + + changed = (bool)((mdiff[0] != 0.0f) || (mdiff[1] != 0.0f)); + if (changed) { + GP_EDITABLE_STROKES_BEGIN (gpstroke_iter, C, gpl, gps) { + if (gps->flag & GP_STROKE_SELECT) { + if (opdata->is_modal) { + add_v2_v2v2(gps->uv_translation, opdata->array_loc[i], mdiff); + } + else { + copy_v2_v2(gps->uv_translation, location); + } + /* Calc geometry data. */ + BKE_gpencil_stroke_geometry_update(gps); + i++; + } + } + GP_EDITABLE_STROKES_END(gpstroke_iter); + } + } + + if ((mode == GP_UV_ROTATE) || (!opdata->is_modal)) { + changed = (bool)(uv_rotation != 0.0f); + if (changed) { + GP_EDITABLE_STROKES_BEGIN (gpstroke_iter, C, gpl, gps) { + if (gps->flag & GP_STROKE_SELECT) { + gps->uv_rotation = (opdata->is_modal) ? opdata->array_rot[i] + uv_rotation : uv_rotation; + /* Calc geometry data. */ + BKE_gpencil_stroke_geometry_update(gps); + i++; + } + } + GP_EDITABLE_STROKES_END(gpstroke_iter); + } + } + + if ((mode == GP_UV_SCALE) || (!opdata->is_modal)) { + float mdiff[2]; + mdiff[0] = opdata->mcenter[0] - opdata->mouse[0]; + mdiff[1] = opdata->mcenter[1] - opdata->mouse[1]; + float scale = (opdata->is_modal) ? + ((len_v2(mdiff) - opdata->initial_length) * opdata->pixel_size) / + opdata->ob_scale : + RNA_float_get(op->ptr, "scale"); + scale *= SMOOTH_FACTOR; + + if (opdata->is_modal) { + RNA_float_set(op->ptr, "scale", scale); + } + + changed = (bool)(scale != 0.0f); + if (changed) { + GP_EDITABLE_STROKES_BEGIN (gpstroke_iter, C, gpl, gps) { + if (gps->flag & GP_STROKE_SELECT) { + gps->uv_scale = (opdata->is_modal) ? opdata->array_scale[i] + scale : scale; + /* Calc geometry data. */ + BKE_gpencil_stroke_geometry_update(gps); + i++; + } + } + GP_EDITABLE_STROKES_END(gpstroke_iter); + } + } + + if ((!opdata->is_modal) || (changed)) { + /* Update cursor line. */ + DEG_id_tag_update(&gpd->id, ID_RECALC_GEOMETRY); + WM_main_add_notifier(NC_GEOM | ND_DATA, NULL); + WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL); + } + + return changed; +} + +static int gpencil_transform_fill_exec(bContext *C, wmOperator *op) +{ + if (!gpencil_uv_transform_init(C, op, false)) { + return OPERATOR_CANCELLED; + } + + if (!gpencil_uv_transform_calc(C, op)) { + gpencil_uv_transform_exit(C, op); + return OPERATOR_CANCELLED; + } + + gpencil_uv_transform_exit(C, op); + return OPERATOR_FINISHED; +} + +static bool gpencil_transform_fill_poll(bContext *C) +{ + Object *ob = CTX_data_active_object(C); + if ((ob == NULL) || (ob->type != OB_GPENCIL)) { + return false; + } + bGPdata *gpd = (bGPdata *)ob->data; + if (gpd == NULL) { + return false; + } + + bGPDlayer *gpl = BKE_gpencil_layer_active_get(gpd); + + if ((gpl == NULL) || (ob->mode != OB_MODE_EDIT_GPENCIL)) { + return false; + } + + return true; +} + +static int gpencil_transform_fill_invoke(bContext *C, wmOperator *op, const wmEvent *event) +{ + RegionView3D *rv3d = CTX_wm_region_view3d(C); + float mlen[2]; + float center_3d[3]; + + if (!gpencil_uv_transform_init(C, op, true)) { + return OPERATOR_CANCELLED; + } + + GpUvData *opdata = op->customdata; + /* initialize mouse values */ + opdata->mouse[0] = event->mval[0]; + opdata->mouse[1] = event->mval[1]; + + copy_v3_v3(center_3d, opdata->ob->loc); + mlen[0] = opdata->mcenter[0] - event->mval[0]; + mlen[1] = opdata->mcenter[1] - event->mval[1]; + opdata->initial_length = len_v2(mlen); + + opdata->pixel_size = rv3d ? ED_view3d_pixel_size(rv3d, center_3d) : 1.0f; + + /* Calc init rotation vector. */ + float mouse[2] = {event->mval[0], event->mval[1]}; + sub_v2_v2v2(opdata->vinit_rotation, mouse, opdata->mcenter); + normalize_v2(opdata->vinit_rotation); + + gpencil_uv_transform_calc(C, op); + + gpencil_uv_transform_update_header(op, C); + WM_cursor_set(CTX_wm_window(C), WM_CURSOR_EW_ARROW); + + WM_event_add_modal_handler(C, op); + return OPERATOR_RUNNING_MODAL; +} + +static int gpencil_transform_fill_modal(bContext *C, wmOperator *op, const wmEvent *event) +{ + GpUvData *opdata = op->customdata; + + switch (event->type) { + case ESCKEY: + case RIGHTMOUSE: { + gpencil_transform_fill_cancel(C, op); + return OPERATOR_CANCELLED; + } + case MOUSEMOVE: { + opdata->mouse[0] = event->mval[0]; + opdata->mouse[1] = event->mval[1]; + + if (gpencil_uv_transform_calc(C, op)) { + gpencil_uv_transform_update_header(op, C); + } + else { + gpencil_transform_fill_cancel(C, op); + return OPERATOR_CANCELLED; + } + break; + } + case LEFTMOUSE: + case PADENTER: + case RETKEY: { + if ((event->val == KM_PRESS) || + ((event->val == KM_RELEASE) && RNA_boolean_get(op->ptr, "release_confirm"))) { + gpencil_uv_transform_calc(C, op); + gpencil_uv_transform_exit(C, op); + return OPERATOR_FINISHED; + } + break; + } + } + + return OPERATOR_RUNNING_MODAL; +} + +void GPENCIL_OT_transform_fill(wmOperatorType *ot) +{ + static const EnumPropertyItem uv_mode[] = { + {GP_UV_TRANSLATE, "TRANSLATE", 0, "Translate", ""}, + {GP_UV_ROTATE, "ROTATE", 0, "Rotate", ""}, + {GP_UV_SCALE, "SCALE", 0, "Scale", ""}, + {0, NULL, 0, NULL, NULL}, + }; + + PropertyRNA *prop; + + /* identifiers */ + ot->name = "Transform Stroke Fill"; + ot->idname = "GPENCIL_OT_transform_fill"; + ot->description = "Transform Grease Pencil Stroke Fill"; + + /* api callbacks */ + ot->invoke = gpencil_transform_fill_invoke; + ot->modal = gpencil_transform_fill_modal; + ot->exec = gpencil_transform_fill_exec; + ot->cancel = gpencil_transform_fill_cancel; + ot->poll = gpencil_transform_fill_poll; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO | OPTYPE_GRAB_CURSOR_XY | OPTYPE_BLOCKING; + + /* properties */ + ot->prop = RNA_def_enum(ot->srna, "mode", uv_mode, GP_UV_ROTATE, "Mode", ""); + + prop = RNA_def_float_vector( + ot->srna, "location", 2, NULL, -FLT_MAX, FLT_MAX, "Location", "", -FLT_MAX, FLT_MAX); + RNA_def_property_flag(prop, PROP_HIDDEN | PROP_SKIP_SAVE); + + prop = RNA_def_float_rotation(ot->srna, + "rotation", + 0, + NULL, + DEG2RADF(-360.0f), + DEG2RADF(360.0f), + "Rotation", + "", + DEG2RADF(-360.0f), + DEG2RADF(360.0f)); + RNA_def_property_float_default(prop, DEG2RADF(0.0f)); + RNA_def_property_flag(prop, PROP_HIDDEN | PROP_SKIP_SAVE); + + prop = RNA_def_float(ot->srna, "scale", 1.0f, 0.001f, 100.0f, "Scale", "", 0.001f, 100.0f); + RNA_def_property_float_default(prop, 0.0f); + RNA_def_property_flag(prop, PROP_HIDDEN | PROP_SKIP_SAVE); + + prop = RNA_def_boolean(ot->srna, "release_confirm", 0, "Confirm on Release", ""); + RNA_def_property_flag(prop, PROP_HIDDEN | PROP_SKIP_SAVE); +} + +/* Clear UV transformations. */ +static int gpencil_reset_transform_fill_exec(bContext *C, wmOperator *op) +{ + const int mode = RNA_enum_get(op->ptr, "mode"); + Object *ob = CTX_data_active_object(C); + bGPdata *gpd = (bGPdata *)ob->data; + bool changed = false; + + /* Loop all selected strokes and reset. */ + GP_EDITABLE_STROKES_BEGIN (gpstroke_iter, C, gpl, gps) { + if (gps->flag & GP_STROKE_SELECT) { + if ((mode == GP_UV_TRANSLATE) || (mode == GP_UV_ALL)) { + zero_v2(gps->uv_translation); + } + if ((mode == GP_UV_ROTATE) || (mode == GP_UV_ALL)) { + gps->uv_rotation = 0.0f; + } + if ((mode == GP_UV_SCALE) || (mode == GP_UV_ALL)) { + gps->uv_scale = 1.0f; + } + /* Calc geometry data. */ + BKE_gpencil_stroke_geometry_update(gps); + changed = true; + } + } + GP_EDITABLE_STROKES_END(gpstroke_iter); + + /* notifiers */ + if (changed) { + DEG_id_tag_update(&gpd->id, ID_RECALC_GEOMETRY); + WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL); + } + + return OPERATOR_FINISHED; +} + +void GPENCIL_OT_reset_transform_fill(wmOperatorType *ot) +{ + static const EnumPropertyItem uv_clear_mode[] = { + {GP_UV_ALL, "ALL", 0, "All", ""}, + {GP_UV_TRANSLATE, "TRANSLATE", 0, "Translate", ""}, + {GP_UV_ROTATE, "ROTATE", 0, "Rotate", ""}, + {GP_UV_SCALE, "SCALE", 0, "Scale", ""}, + {0, NULL, 0, NULL, NULL}, + }; + + /* identifiers */ + ot->name = "Reset Fill Transformations"; + ot->idname = "GPENCIL_OT_reset_transform_fill"; + ot->description = "Reset any UV transformation and back to default values"; + + /* callbacks */ + ot->exec = gpencil_reset_transform_fill_exec; + ot->poll = gpencil_transform_fill_poll; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + /* properties */ + ot->prop = RNA_def_enum(ot->srna, "mode", uv_clear_mode, GP_UV_ALL, "Mode", ""); +} diff --git a/source/blender/editors/gpencil/gpencil_vertex_ops.c b/source/blender/editors/gpencil/gpencil_vertex_ops.c new file mode 100644 index 00000000000..6a3eebf1baf --- /dev/null +++ b/source/blender/editors/gpencil/gpencil_vertex_ops.c @@ -0,0 +1,899 @@ +/* + * 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 + * Brush based operators for editing Grease Pencil strokes + */ + +/** \file + * \ingroup edgpencil + */ + +#include "MEM_guardedalloc.h" + +#include "BLI_blenlib.h" +#include "BLI_ghash.h" +#include "BLI_math.h" + +#include "BLT_translation.h" + +#include "DNA_brush_types.h" +#include "DNA_gpencil_types.h" + +#include "BKE_colortools.h" +#include "BKE_context.h" +#include "BKE_gpencil.h" +#include "BKE_gpencil_modifier.h" +#include "BKE_main.h" +#include "BKE_material.h" +#include "BKE_paint.h" +#include "BKE_report.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 "DEG_depsgraph.h" +#include "DEG_depsgraph_query.h" + +#include "gpencil_intern.h" + +enum { + GP_PAINT_VERTEX_STROKE = 0, + GP_PAINT_VERTEX_FILL = 1, + GP_PAINT_VERTEX_BOTH = 2, +}; + +static const EnumPropertyItem gpencil_modesEnumPropertyItem_mode[] = { + {GP_PAINT_VERTEX_STROKE, "STROKE", 0, "Stroke", ""}, + {GP_PAINT_VERTEX_FILL, "FILL", 0, "Fill", ""}, + {GP_PAINT_VERTEX_BOTH, "BOTH", 0, "Both", ""}, + {0, NULL, 0, NULL, NULL}, +}; + +/* Poll callback for stroke vertex paint operator. */ +static bool gp_vertexpaint_mode_poll(bContext *C) +{ + ToolSettings *ts = CTX_data_tool_settings(C); + Object *ob = CTX_data_active_object(C); + if ((ob == NULL) || (ob->type != OB_GPENCIL)) { + return false; + } + + bGPdata *gpd = (bGPdata *)ob->data; + if (GPENCIL_VERTEX_MODE(gpd)) { + if (!(GPENCIL_ANY_VERTEX_MASK(ts->gpencil_selectmode_vertex))) { + return false; + } + + /* Any data to use. */ + if (gpd->layers.first) { + return true; + } + } + + return false; +} + +static int gp_vertexpaint_brightness_contrast_exec(bContext *C, wmOperator *op) +{ + Object *ob = CTX_data_active_object(C); + bGPdata *gpd = (bGPdata *)ob->data; + bool changed = false; + int i; + bGPDspoint *pt; + const int mode = RNA_enum_get(op->ptr, "mode"); + + float gain, offset; + { + float brightness = RNA_float_get(op->ptr, "brightness"); + float contrast = RNA_float_get(op->ptr, "contrast"); + brightness /= 100.0f; + float delta = contrast / 200.0f; + /* + * The algorithm is by Werner D. Streidt + * (http://visca.com/ffactory/archives/5-99/msg00021.html) + * Extracted of OpenCV demhist.c + */ + if (contrast > 0) { + gain = 1.0f - delta * 2.0f; + gain = 1.0f / max_ff(gain, FLT_EPSILON); + offset = gain * (brightness - delta); + } + else { + delta *= -1; + gain = max_ff(1.0f - delta * 2.0f, 0.0f); + offset = gain * brightness + delta; + } + } + + /* Loop all selected strokes. */ + GP_EDITABLE_STROKES_BEGIN (gpstroke_iter, C, gpl, gps) { + if (gps->flag & GP_STROKE_SELECT) { + changed = true; + /* Fill color. */ + if (gps->flag & GP_STROKE_SELECT) { + changed = true; + if (mode != GP_PAINT_VERTEX_STROKE) { + if (gps->vert_color_fill[3] > 0.0f) { + for (int i2 = 0; i2 < 3; i2++) { + gps->vert_color_fill[i2] = gain * gps->vert_color_fill[i2] + offset; + } + } + } + } + + /* Stroke points. */ + if (mode != GP_PAINT_VERTEX_FILL) { + for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) { + if ((pt->flag & GP_SPOINT_SELECT) && (pt->vert_color[3] > 0.0f)) { + for (int i2 = 0; i2 < 3; i2++) { + pt->vert_color[i2] = gain * pt->vert_color[i2] + offset; + } + } + } + } + } + } + GP_EDITABLE_STROKES_END(gpstroke_iter); + + /* notifiers */ + if (changed) { + DEG_id_tag_update(&gpd->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY); + WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL); + } + + return OPERATOR_FINISHED; +} + +void GPENCIL_OT_vertex_color_brightness_contrast(wmOperatorType *ot) +{ + PropertyRNA *prop; + + /* identifiers */ + ot->name = "Vertex Paint Bright/Contrast"; + ot->idname = "GPENCIL_OT_vertex_color_brightness_contrast"; + ot->description = "Adjust vertex color brightness/contrast"; + + /* api callbacks */ + ot->exec = gp_vertexpaint_brightness_contrast_exec; + ot->poll = gp_vertexpaint_mode_poll; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + /* params */ + ot->prop = RNA_def_enum(ot->srna, "mode", gpencil_modesEnumPropertyItem_mode, 0, "Mode", ""); + const float min = -100, max = +100; + prop = RNA_def_float(ot->srna, "brightness", 0.0f, min, max, "Brightness", "", min, max); + prop = RNA_def_float(ot->srna, "contrast", 0.0f, min, max, "Contrast", "", min, max); + RNA_def_property_ui_range(prop, min, max, 1, 1); +} + +static int gp_vertexpaint_hsv_exec(bContext *C, wmOperator *op) +{ + Object *ob = CTX_data_active_object(C); + bGPdata *gpd = (bGPdata *)ob->data; + + bool changed = false; + int i; + bGPDspoint *pt; + float hsv[3]; + + const int mode = RNA_enum_get(op->ptr, "mode"); + float hue = RNA_float_get(op->ptr, "h"); + float sat = RNA_float_get(op->ptr, "s"); + float val = RNA_float_get(op->ptr, "v"); + + /* Loop all selected strokes. */ + GP_EDITABLE_STROKES_BEGIN (gpstroke_iter, C, gpl, gps) { + if (gps->flag & GP_STROKE_SELECT) { + changed = true; + + /* Fill color. */ + if (mode != GP_PAINT_VERTEX_STROKE) { + if (gps->vert_color_fill[3] > 0.0f) { + + rgb_to_hsv_v(gps->vert_color_fill, hsv); + + hsv[0] += (hue - 0.5f); + if (hsv[0] > 1.0f) { + hsv[0] -= 1.0f; + } + else if (hsv[0] < 0.0f) { + hsv[0] += 1.0f; + } + hsv[1] *= sat; + hsv[2] *= val; + + hsv_to_rgb_v(hsv, gps->vert_color_fill); + } + } + + /* Stroke points. */ + if (mode != GP_PAINT_VERTEX_FILL) { + for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) { + if ((pt->flag & GP_SPOINT_SELECT) && (pt->vert_color[3] > 0.0f)) { + rgb_to_hsv_v(pt->vert_color, hsv); + + hsv[0] += (hue - 0.5f); + if (hsv[0] > 1.0f) { + hsv[0] -= 1.0f; + } + else if (hsv[0] < 0.0f) { + hsv[0] += 1.0f; + } + hsv[1] *= sat; + hsv[2] *= val; + + hsv_to_rgb_v(hsv, pt->vert_color); + } + } + } + } + } + GP_EDITABLE_STROKES_END(gpstroke_iter); + + /* notifiers */ + if (changed) { + DEG_id_tag_update(&gpd->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY); + WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL); + } + + return OPERATOR_FINISHED; +} + +void GPENCIL_OT_vertex_color_hsv(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Vertex Paint Hue Saturation Value"; + ot->idname = "GPENCIL_OT_vertex_color_hsv"; + ot->description = "Adjust vertex color HSV values"; + + /* api callbacks */ + ot->exec = gp_vertexpaint_hsv_exec; + ot->poll = gp_vertexpaint_mode_poll; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + /* params */ + ot->prop = RNA_def_enum(ot->srna, "mode", gpencil_modesEnumPropertyItem_mode, 0, "Mode", ""); + RNA_def_float(ot->srna, "h", 0.5f, 0.0f, 1.0f, "Hue", "", 0.0f, 1.0f); + RNA_def_float(ot->srna, "s", 1.0f, 0.0f, 2.0f, "Saturation", "", 0.0f, 2.0f); + RNA_def_float(ot->srna, "v", 1.0f, 0.0f, 2.0f, "Value", "", 0.0f, 2.0f); +} + +static int gp_vertexpaint_invert_exec(bContext *C, wmOperator *op) +{ + Object *ob = CTX_data_active_object(C); + bGPdata *gpd = (bGPdata *)ob->data; + + bool changed = false; + int i; + bGPDspoint *pt; + + const int mode = RNA_enum_get(op->ptr, "mode"); + + /* Loop all selected strokes. */ + GP_EDITABLE_STROKES_BEGIN (gpstroke_iter, C, gpl, gps) { + if (gps->flag & GP_STROKE_SELECT) { + changed = true; + /* Fill color. */ + if (gps->flag & GP_STROKE_SELECT) { + changed = true; + if (mode != GP_PAINT_VERTEX_STROKE) { + if (gps->vert_color_fill[3] > 0.0f) { + for (int i2 = 0; i2 < 3; i2++) { + gps->vert_color_fill[i2] = 1.0f - gps->vert_color_fill[i2]; + } + } + } + } + + /* Stroke points. */ + if (mode != GP_PAINT_VERTEX_FILL) { + for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) { + if ((pt->flag & GP_SPOINT_SELECT) && (pt->vert_color[3] > 0.0f)) { + for (int i2 = 0; i2 < 3; i2++) { + pt->vert_color[i2] = 1.0f - pt->vert_color[i2]; + } + } + } + } + } + } + GP_EDITABLE_STROKES_END(gpstroke_iter); + + /* notifiers */ + if (changed) { + DEG_id_tag_update(&gpd->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY); + WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL); + } + + return OPERATOR_FINISHED; +} + +void GPENCIL_OT_vertex_color_invert(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Vertex Paint Invert"; + ot->idname = "GPENCIL_OT_vertex_color_invert"; + ot->description = "Invert RGB values"; + + /* api callbacks */ + ot->exec = gp_vertexpaint_invert_exec; + ot->poll = gp_vertexpaint_mode_poll; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + /* params */ + ot->prop = RNA_def_enum(ot->srna, "mode", gpencil_modesEnumPropertyItem_mode, 0, "Mode", ""); +} + +static int gp_vertexpaint_levels_exec(bContext *C, wmOperator *op) +{ + Object *ob = CTX_data_active_object(C); + bGPdata *gpd = (bGPdata *)ob->data; + + bool changed = false; + int i; + bGPDspoint *pt; + + const int mode = RNA_enum_get(op->ptr, "mode"); + float gain = RNA_float_get(op->ptr, "gain"); + float offset = RNA_float_get(op->ptr, "offset"); + + /* Loop all selected strokes. */ + GP_EDITABLE_STROKES_BEGIN (gpstroke_iter, C, gpl, gps) { + + /* Fill color. */ + if (gps->flag & GP_STROKE_SELECT) { + changed = true; + if (mode != GP_PAINT_VERTEX_STROKE) { + if (gps->vert_color_fill[3] > 0.0f) { + for (int i2 = 0; i2 < 3; i2++) { + gps->vert_color_fill[i2] = gain * (gps->vert_color_fill[i2] + offset); + } + } + } + } + + /* Stroke points. */ + if (mode != GP_PAINT_VERTEX_FILL) { + for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) { + if ((pt->flag & GP_SPOINT_SELECT) && (pt->vert_color[3] > 0.0f)) { + for (int i2 = 0; i < 3; i2++) { + pt->vert_color[i2] = gain * (pt->vert_color[i2] + offset); + } + } + } + } + } + GP_EDITABLE_STROKES_END(gpstroke_iter); + + /* notifiers */ + if (changed) { + DEG_id_tag_update(&gpd->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY); + WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL); + } + + return OPERATOR_FINISHED; +} + +void GPENCIL_OT_vertex_color_levels(wmOperatorType *ot) +{ + + /* identifiers */ + ot->name = "Vertex Paint Levels"; + ot->idname = "GPENCIL_OT_vertex_color_levels"; + ot->description = "Adjust levels of vertex colors"; + + /* api callbacks */ + ot->exec = gp_vertexpaint_levels_exec; + ot->poll = gp_vertexpaint_mode_poll; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + /* params */ + ot->prop = RNA_def_enum(ot->srna, "mode", gpencil_modesEnumPropertyItem_mode, 0, "Mode", ""); + + RNA_def_float( + ot->srna, "offset", 0.0f, -1.0f, 1.0f, "Offset", "Value to add to colors", -1.0f, 1.0f); + RNA_def_float( + ot->srna, "gain", 1.0f, 0.0f, FLT_MAX, "Gain", "Value to multiply colors by", 0.0f, 10.0f); +} + +static int gp_vertexpaint_set_exec(bContext *C, wmOperator *op) +{ + ToolSettings *ts = CTX_data_tool_settings(C); + Object *ob = CTX_data_active_object(C); + bGPdata *gpd = (bGPdata *)ob->data; + Paint *paint = &ts->gp_vertexpaint->paint; + Brush *brush = brush = paint->brush; + + bool changed = false; + int i; + bGPDspoint *pt; + + const int mode = RNA_enum_get(op->ptr, "mode"); + float factor = RNA_float_get(op->ptr, "factor"); + + /* Loop all selected strokes. */ + GP_EDITABLE_STROKES_BEGIN (gpstroke_iter, C, gpl, gps) { + + /* Fill color. */ + if (gps->flag & GP_STROKE_SELECT) { + changed = true; + if (mode != GP_PAINT_VERTEX_STROKE) { + copy_v3_v3(gps->vert_color_fill, brush->rgb); + gps->vert_color_fill[3] = factor; + } + } + + /* Stroke points. */ + if (mode != GP_PAINT_VERTEX_FILL) { + for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) { + if (pt->flag & GP_SPOINT_SELECT) { + copy_v3_v3(pt->vert_color, brush->rgb); + pt->vert_color[3] = factor; + } + } + } + } + GP_EDITABLE_STROKES_END(gpstroke_iter); + + /* notifiers */ + if (changed) { + DEG_id_tag_update(&gpd->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY); + WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL); + } + + return OPERATOR_FINISHED; +} + +void GPENCIL_OT_vertex_color_set(wmOperatorType *ot) +{ + + /* identifiers */ + ot->name = "Vertex Paint Set Color"; + ot->idname = "GPENCIL_OT_vertex_color_set"; + ot->description = "Set active color to all selected vertex"; + + /* api callbacks */ + ot->exec = gp_vertexpaint_set_exec; + ot->poll = gp_vertexpaint_mode_poll; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + /* params */ + ot->prop = RNA_def_enum(ot->srna, "mode", gpencil_modesEnumPropertyItem_mode, 0, "Mode", ""); + RNA_def_float(ot->srna, "factor", 1.0f, 0.001f, 1.0f, "Factor", "Mix Factor", 0.001f, 1.0f); +} + +/* Helper to extract color from vertex color to create a palette. */ +static bool gp_extract_palette_from_vertex(bContext *C, const bool selected, const int threshold) +{ + Main *bmain = CTX_data_main(C); + Object *ob = CTX_data_active_object(C); + bool done = false; + const float range = pow(10.0f, threshold); + float col[3]; + + GHash *color_table = BLI_ghash_int_new(__func__); + + /* Extract all colors. */ + CTX_DATA_BEGIN (C, bGPDlayer *, gpl, editable_gpencil_layers) { + LISTBASE_FOREACH (bGPDframe *, gpf, &gpl->frames) { + LISTBASE_FOREACH (bGPDstroke *, gps, &gpf->strokes) { + if (ED_gpencil_stroke_can_use(C, gps) == false) { + continue; + } + if (ED_gpencil_stroke_color_use(ob, gpl, gps) == false) { + continue; + } + MaterialGPencilStyle *gp_style = BKE_gpencil_material_settings(ob, gps->mat_nr + 1); + if (gp_style == NULL) { + continue; + } + + if ((selected) && ((gps->flag & GP_STROKE_SELECT) == 0)) { + continue; + } + + bool use_stroke = (gp_style->flag & GP_MATERIAL_STROKE_SHOW); + bool use_fill = (gp_style->flag & GP_MATERIAL_FILL_SHOW); + + /* Material is disabled. */ + if ((!use_fill) && (!use_stroke)) { + continue; + } + + /* Only solid strokes or stencil. */ + if ((use_stroke) && ((gp_style->stroke_style == GP_MATERIAL_STROKE_STYLE_TEXTURE) && + ((gp_style->flag & GP_MATERIAL_STROKE_PATTERN) == 0))) { + continue; + } + + /* Only solid fill. */ + if ((use_fill) && (gp_style->fill_style != GP_MATERIAL_FILL_STYLE_SOLID)) { + continue; + } + + /* Fill color. */ + if (gps->vert_color_fill[3] > 0.0f) { + col[0] = truncf(gps->vert_color_fill[0] * range) / range; + col[1] = truncf(gps->vert_color_fill[1] * range) / range; + col[2] = truncf(gps->vert_color_fill[2] * range) / range; + + uint key = rgb_to_cpack(col[0], col[1], col[2]); + + if (!BLI_ghash_haskey(color_table, POINTER_FROM_INT(key))) { + BLI_ghash_insert(color_table, POINTER_FROM_INT(key), POINTER_FROM_INT(key)); + } + } + + /* Read all points to get all colors. */ + bGPDspoint *pt; + int i; + for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) { + col[0] = truncf(pt->vert_color[0] * range) / range; + col[1] = truncf(pt->vert_color[1] * range) / range; + col[2] = truncf(pt->vert_color[2] * range) / range; + + uint key = rgb_to_cpack(col[0], col[1], col[2]); + if (!BLI_ghash_haskey(color_table, POINTER_FROM_INT(key))) { + BLI_ghash_insert(color_table, POINTER_FROM_INT(key), POINTER_FROM_INT(key)); + } + } + } + } + } + CTX_DATA_END; + + /* Create the Palette. */ + done = BKE_palette_from_hash(bmain, color_table, ob->id.name + 2, true); + + /* Free memory. */ + BLI_ghash_free(color_table, NULL, NULL); + + return done; +} + +/* Convert Materials to Vertex Color. */ +typedef struct GPMatArray { + uint key; + Material *ma; + int index; +} GPMatArray; + +static uint get_material_type(MaterialGPencilStyle *gp_style, + bool use_stroke, + bool use_fill, + char *name) +{ + uint r_i = 0; + if ((use_stroke) && (use_fill)) { + switch (gp_style->mode) { + case GP_MATERIAL_MODE_LINE: { + r_i = 1; + strcpy(name, "Line Stroke-Fill"); + break; + } + case GP_MATERIAL_MODE_DOT: { + r_i = 2; + strcpy(name, "Dots Stroke-Fill"); + break; + } + case GP_MATERIAL_MODE_SQUARE: { + r_i = 3; + strcpy(name, "Squares Stroke-Fill"); + break; + } + default: + break; + } + } + else if (use_stroke) { + switch (gp_style->mode) { + case GP_MATERIAL_MODE_LINE: { + r_i = 4; + strcpy(name, "Line Stroke"); + break; + } + case GP_MATERIAL_MODE_DOT: { + r_i = 5; + strcpy(name, "Dots Stroke"); + break; + } + case GP_MATERIAL_MODE_SQUARE: { + r_i = 6; + strcpy(name, "Squares Stroke"); + break; + } + default: + break; + } + } + else { + r_i = 7; + strcpy(name, "Solid Fill"); + } + + /* Create key TSSSSFFFF (T: Type S: Stroke Alpha F: Fill Alpha) */ + r_i *= 1e8; + if (use_stroke) { + r_i += gp_style->stroke_rgba[3] * 1e7; + } + if (use_fill) { + r_i += gp_style->fill_rgba[3] * 1e3; + } + + return r_i; +} + +static bool gp_material_to_vertex_poll(bContext *C) +{ + /* only supported with grease pencil objects */ + Object *ob = CTX_data_active_object(C); + if ((ob == NULL) || (ob->type != OB_GPENCIL)) { + return false; + } + + return true; +} + +static int gp_material_to_vertex_exec(bContext *C, wmOperator *op) +{ + Main *bmain = CTX_data_main(C); + Object *ob = CTX_data_active_object(C); + bGPdata *gpd = (bGPdata *)ob->data; + const bool remove = RNA_boolean_get(op->ptr, "remove"); + const bool palette = RNA_boolean_get(op->ptr, "palette"); + const bool selected = RNA_boolean_get(op->ptr, "selected"); + + char name[32] = ""; + Material *ma = NULL; + GPMatArray *mat_elm = NULL; + int i; + + bool changed = false; + + short *totcol = BKE_object_material_len_p(ob); + if (totcol == 0) { + return OPERATOR_CANCELLED; + } + + /* These arrays hold all materials and index in the material slots for all combinations. */ + int totmat = *totcol; + GPMatArray *mat_table = MEM_calloc_arrayN(totmat, sizeof(GPMatArray), __func__); + + /* Update stroke material index. */ + CTX_DATA_BEGIN (C, bGPDlayer *, gpl, editable_gpencil_layers) { + LISTBASE_FOREACH (bGPDframe *, gpf, &gpl->frames) { + LISTBASE_FOREACH (bGPDstroke *, gps, &gpf->strokes) { + if (ED_gpencil_stroke_can_use(C, gps) == false) { + continue; + } + if (ED_gpencil_stroke_color_use(ob, gpl, gps) == false) { + continue; + } + + if ((selected) && ((gps->flag & GP_STROKE_SELECT) == 0)) { + continue; + } + + MaterialGPencilStyle *gp_style = BKE_gpencil_material_settings(ob, gps->mat_nr + 1); + if (gp_style == NULL) { + continue; + } + + bool use_stroke = ((gp_style->flag & GP_MATERIAL_STROKE_SHOW) && + (gp_style->stroke_rgba[3] > 0.0f)); + bool use_fill = ((gp_style->flag & GP_MATERIAL_FILL_SHOW) && + (gp_style->fill_rgba[3] > 0.0f)); + bool is_stencil = ((gp_style->stroke_style == GP_MATERIAL_STROKE_STYLE_TEXTURE) && + (gp_style->flag & GP_MATERIAL_STROKE_PATTERN)); + /* Material is disabled. */ + if ((!use_fill) && (!use_stroke)) { + continue; + } + + /* Only solid strokes or stencil. */ + if ((use_stroke) && ((gp_style->stroke_style == GP_MATERIAL_STROKE_STYLE_TEXTURE) && + ((gp_style->flag & GP_MATERIAL_STROKE_PATTERN) == 0))) { + continue; + } + + /* Only solid fill. */ + if ((use_fill) && (gp_style->fill_style != GP_MATERIAL_FILL_STYLE_SOLID)) { + continue; + } + + /* Only for no Stencil materials. */ + if (!is_stencil) { + /* Create material type unique key by type and alpha. */ + uint key = get_material_type(gp_style, use_stroke, use_fill, name); + + /* Check if material exist. */ + bool found = false; + for (i = 0; i < totmat; i++) { + mat_elm = &mat_table[i]; + if (mat_elm->ma == NULL) { + break; + } + if (key == mat_elm->key) { + found = true; + break; + } + } + + /* If not found create a new material. */ + if (!found) { + ma = BKE_gpencil_material_add(bmain, name); + if (use_stroke) { + ma->gp_style->flag |= GP_MATERIAL_STROKE_SHOW; + } + else { + ma->gp_style->flag &= ~GP_MATERIAL_STROKE_SHOW; + } + + if (use_fill) { + ma->gp_style->flag |= GP_MATERIAL_FILL_SHOW; + } + else { + ma->gp_style->flag &= ~GP_MATERIAL_FILL_SHOW; + } + + ma->gp_style->stroke_rgba[3] = gp_style->stroke_rgba[3]; + ma->gp_style->fill_rgba[3] = gp_style->fill_rgba[3]; + + BKE_object_material_slot_add(bmain, ob); + BKE_object_material_assign(bmain, ob, ma, ob->totcol, BKE_MAT_ASSIGN_USERPREF); + + mat_elm->key = key; + mat_elm->ma = ma; + mat_elm->index = ob->totcol - 1; + } + else { + mat_elm = &mat_table[i]; + } + + /* Update stroke */ + gps->mat_nr = mat_elm->index; + } + + changed = true; + copy_v3_v3(gps->vert_color_fill, gp_style->fill_rgba); + gps->vert_color_fill[3] = 1.0f; + + /* Update all points. */ + bGPDspoint *pt; + for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) { + copy_v3_v3(pt->vert_color, gp_style->stroke_rgba); + pt->vert_color[3] = 1.0f; + } + } + } + } + CTX_DATA_END; + + /* notifiers */ + if (changed) { + DEG_id_tag_update(&gpd->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY); + WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL); + } + + /* Free memory. */ + MEM_SAFE_FREE(mat_table); + + /* Generate a Palette. */ + if (palette) { + gp_extract_palette_from_vertex(C, selected, 1); + } + + /* Clean unused materials. */ + if (remove) { + WM_operator_name_call( + C, "OBJECT_OT_material_slot_remove_unused", WM_OP_INVOKE_REGION_WIN, NULL); + } + + return OPERATOR_FINISHED; +} + +void GPENCIL_OT_material_to_vertex_color(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Convert Stroke Materials to Vertex Color"; + ot->idname = "GPENCIL_OT_material_to_vertex_color"; + ot->description = "Replace materials in strokes with Vertex Color"; + + /* api callbacks */ + ot->exec = gp_material_to_vertex_exec; + ot->poll = gp_material_to_vertex_poll; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + /* properties */ + ot->prop = RNA_def_boolean(ot->srna, + "remove", + true, + "Remove Unused Materiales", + "Remove any unused material after the conversion"); + RNA_def_boolean(ot->srna, "palette", true, "Create Palette", "Create a new palette with colors"); + RNA_def_boolean(ot->srna, "selected", false, "Only Selected", "Convert only selected strokes"); + RNA_def_int(ot->srna, "threshold", 3, 1, 4, "Threshold", "", 1, 4); +} + +/* Extract Palette from Vertex Color. */ +static bool gp_extract_palette_vertex_poll(bContext *C) +{ + /* only supported with grease pencil objects */ + Object *ob = CTX_data_active_object(C); + if ((ob == NULL) || (ob->type != OB_GPENCIL)) { + return false; + } + + return true; +} + +static int gp_extract_palette_vertex_exec(bContext *C, wmOperator *op) +{ + const bool selected = RNA_boolean_get(op->ptr, "selected"); + const int threshold = RNA_int_get(op->ptr, "threshold"); + + if (gp_extract_palette_from_vertex(C, selected, threshold)) { + BKE_reportf(op->reports, RPT_INFO, "Palette created"); + } + else { + BKE_reportf(op->reports, RPT_ERROR, "Unable to find Vertex Information to create palette"); + } + + return OPERATOR_FINISHED; +} + +void GPENCIL_OT_extract_palette_vertex(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Extract Palette from Vertex Color"; + ot->idname = "GPENCIL_OT_extract_palette_vertex"; + ot->description = "Extract all colors used in Grease Pencil Vertex and create a Palette"; + + /* api callbacks */ + ot->exec = gp_extract_palette_vertex_exec; + ot->poll = gp_extract_palette_vertex_poll; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + /* properties */ + ot->prop = RNA_def_boolean( + ot->srna, "selected", false, "Only Selected", "Convert only selected strokes"); + RNA_def_int(ot->srna, "threshold", 1, 1, 4, "Threshold", "", 1, 4); +} diff --git a/source/blender/editors/gpencil/gpencil_vertex_paint.c b/source/blender/editors/gpencil/gpencil_vertex_paint.c new file mode 100644 index 00000000000..5b5a306aa25 --- /dev/null +++ b/source/blender/editors/gpencil/gpencil_vertex_paint.c @@ -0,0 +1,1414 @@ +/* + * 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 + * Brush based operators for editing Grease Pencil strokes + */ + +/** \file + * \ingroup edgpencil + */ + +#include "MEM_guardedalloc.h" + +#include "BLI_blenlib.h" +#include "BLI_math.h" + +#include "BLT_translation.h" + +#include "DNA_brush_types.h" +#include "DNA_gpencil_types.h" + +#include "BKE_brush.h" +#include "BKE_colortools.h" +#include "BKE_context.h" +#include "BKE_gpencil.h" +#include "BKE_report.h" + +#include "WM_api.h" +#include "WM_types.h" + +#include "RNA_access.h" +#include "RNA_define.h" + +#include "UI_view2d.h" + +#include "ED_gpencil.h" +#include "ED_screen.h" +#include "ED_view3d.h" + +#include "DEG_depsgraph.h" +#include "DEG_depsgraph_query.h" + +#include "gpencil_intern.h" + +/* ************************************************ */ +/* General Brush Editing Context */ +#define GP_SELECT_BUFFER_CHUNK 256 +#define GP_GRID_PIXEL_SIZE 10.0f + +/* Temp Flags while Painting. */ +typedef enum eGPDvertex_brush_Flag { + /* invert the effect of the brush */ + GP_VERTEX_FLAG_INVERT = (1 << 0), + /* temporary invert action */ + GP_VERTEX_FLAG_TMP_INVERT = (1 << 1), +} eGPDvertex_brush_Flag; + +/* Grid of Colors for Smear. */ +typedef struct tGP_Grid { + /** Lower right corner of rectangle of grid cell. */ + float bottom[2]; + /** Upper left corner of rectangle of grid cell. */ + float top[2]; + /** Average Color */ + float color[4]; + /** Total points included. */ + int totcol; + +} tGP_Grid; + +/* List of points affected by brush. */ +typedef struct tGP_Selected { + /** Referenced stroke. */ + bGPDstroke *gps; + /** Point index in points array. */ + int pt_index; + /** Position */ + int pc[2]; + /** Color */ + float color[4]; +} tGP_Selected; + +/* Context for brush operators */ +typedef struct tGP_BrushVertexpaintData { + Scene *scene; + Object *object; + + ARegion *region; + + /* Current GPencil datablock */ + bGPdata *gpd; + + Brush *brush; + eGPDvertex_brush_Flag flag; + eGP_Vertex_SelectMaskFlag mask; + + /* Space Conversion Data */ + GP_SpaceConversion gsc; + + /* Is the brush currently painting? */ + bool is_painting; + + /* Start of new paint */ + bool first; + + /* Is multiframe editing enabled, and are we using falloff for that? */ + bool is_multiframe; + bool use_multiframe_falloff; + + /* Brush Runtime Data: */ + /* - position and pressure + * - the *_prev variants are the previous values + */ + float mval[2], mval_prev[2]; + float pressure, pressure_prev; + + /* - Effect 2D vector */ + float dvec[2]; + + /* - multiframe falloff factor */ + float mf_falloff; + + /* brush geometry (bounding box) */ + rcti brush_rect; + + /* Temp data to save selected points */ + /** Stroke buffer. */ + tGP_Selected *pbuffer; + /** Number of elements currently used in cache. */ + int pbuffer_used; + /** Number of total elements available in cache. */ + int pbuffer_size; + + /** Grid of average colors */ + tGP_Grid *grid; + /** Total number of rows/cols. */ + int grid_size; + /** Total number of cells elments in the grid array. */ + int grid_len; + /** Grid sample position (used to determine distance of falloff) */ + int grid_sample[2]; + /** Grid is ready to use */ + bool grid_ready; + +} tGP_BrushVertexpaintData; + +/* Ensure the buffer to hold temp selected point size is enough to save all points selected. */ +static tGP_Selected *gpencil_select_buffer_ensure(tGP_Selected *buffer_array, + int *buffer_size, + int *buffer_used, + const bool clear) +{ + tGP_Selected *p = NULL; + + /* By default a buffer is created with one block with a predefined number of free slots, + * if the size is not enough, the cache is reallocated adding a new block of free slots. + * This is done in order to keep cache small and improve speed. */ + if (*buffer_used + 1 > *buffer_size) { + if ((*buffer_size == 0) || (buffer_array == NULL)) { + p = MEM_callocN(sizeof(struct tGP_Selected) * GP_SELECT_BUFFER_CHUNK, __func__); + *buffer_size = GP_SELECT_BUFFER_CHUNK; + } + else { + *buffer_size += GP_SELECT_BUFFER_CHUNK; + p = MEM_recallocN(buffer_array, sizeof(struct tGP_Selected) * *buffer_size); + } + + if (p == NULL) { + *buffer_size = *buffer_used = 0; + } + + buffer_array = p; + } + + /* clear old data */ + if (clear) { + *buffer_used = 0; + if (buffer_array != NULL) { + memset(buffer_array, 0, sizeof(tGP_Selected) * *buffer_size); + } + } + + return buffer_array; +} + +/* Brush Operations ------------------------------- */ + +/* Invert behavior of brush? */ +static bool brush_invert_check(tGP_BrushVertexpaintData *gso) +{ + /* The basic setting is no inverted */ + bool invert = false; + + /* During runtime, the user can hold down the Ctrl key to invert the basic behavior */ + if (gso->flag & GP_VERTEX_FLAG_INVERT) { + invert ^= true; + } + + return invert; +} + +/* Compute strength of effect. */ +static float brush_influence_calc(tGP_BrushVertexpaintData *gso, const int radius, const int co[2]) +{ + Brush *brush = gso->brush; + float influence = brush->size; + + /* use pressure? */ + if (brush->gpencil_settings->flag & GP_BRUSH_USE_PRESSURE) { + influence *= gso->pressure; + } + + /* distance fading */ + int mval_i[2]; + round_v2i_v2fl(mval_i, gso->mval); + float distance = (float)len_v2v2_int(mval_i, co); + + /* Apply Brush curve. */ + float brush_fallof = BKE_brush_curve_strength(brush, distance, (float)radius); + influence *= brush_fallof; + + /* apply multiframe falloff */ + influence *= gso->mf_falloff; + + /* return influence */ + return influence; +} + +/* Compute effect vector for directional brushes. */ +static void brush_calc_dvec_2d(tGP_BrushVertexpaintData *gso) +{ + gso->dvec[0] = (float)(gso->mval[0] - gso->mval_prev[0]); + gso->dvec[1] = (float)(gso->mval[1] - gso->mval_prev[1]); + + normalize_v2(gso->dvec); +} + +/* Init a grid of cells around mouse position. + * + * For each Cell. + * + * *--------* Top + * | | + * | | + * Bottom *--------* + * + * The number of cells is calculated using the brush size and a predefined + * number of pixels (see: GP_GRID_PIXEL_SIZE) + */ + +static void gp_grid_cells_init(tGP_BrushVertexpaintData *gso) +{ + tGP_Grid *grid; + float bottom[2]; + float top[2]; + int grid_index = 0; + + /* The grid center is (0,0). */ + bottom[0] = gso->brush_rect.xmin - gso->mval[0]; + bottom[1] = gso->brush_rect.ymax - GP_GRID_PIXEL_SIZE - gso->mval[1]; + + /* Calc all cell of the grid from top/left. */ + for (int y = gso->grid_size - 1; y >= 0; y--) { + top[1] = bottom[1] + GP_GRID_PIXEL_SIZE; + + for (int x = 0; x < gso->grid_size; x++) { + top[0] = bottom[0] + GP_GRID_PIXEL_SIZE; + + grid = &gso->grid[grid_index]; + + copy_v2_v2(grid->bottom, bottom); + copy_v2_v2(grid->top, top); + + bottom[0] += GP_GRID_PIXEL_SIZE; + + grid_index++; + } + + /* Reset for new row. */ + bottom[0] = gso->brush_rect.xmin - gso->mval[0]; + bottom[1] -= GP_GRID_PIXEL_SIZE; + } +} + +/* Get the index used in the grid base on dvec. */ +static void gp_grid_cell_average_color_idx_get(tGP_BrushVertexpaintData *gso, int r_idx[2]) +{ + /* Lower direction. */ + if (gso->dvec[1] < 0.0f) { + if ((gso->dvec[0] >= -1.0f) && (gso->dvec[0] < -0.8f)) { + r_idx[0] = 0; + r_idx[1] = -1; + } + else if ((gso->dvec[0] >= -0.8f) && (gso->dvec[0] < -0.6f)) { + r_idx[0] = -1; + r_idx[1] = -1; + } + else if ((gso->dvec[0] >= -0.6f) && (gso->dvec[0] < 0.6f)) { + r_idx[0] = -1; + r_idx[1] = 0; + } + else if ((gso->dvec[0] >= 0.6f) && (gso->dvec[0] < 0.8f)) { + r_idx[0] = -1; + r_idx[1] = 1; + } + else if (gso->dvec[0] >= 0.8f) { + r_idx[0] = 0; + r_idx[1] = 1; + } + } + /* Upper direction. */ + else { + if ((gso->dvec[0] >= -1.0f) && (gso->dvec[0] < -0.8f)) { + r_idx[0] = 0; + r_idx[1] = -1; + } + else if ((gso->dvec[0] >= -0.8f) && (gso->dvec[0] < -0.6f)) { + r_idx[0] = 1; + r_idx[1] = -1; + } + else if ((gso->dvec[0] >= -0.6f) && (gso->dvec[0] < 0.6f)) { + r_idx[0] = 1; + r_idx[1] = 0; + } + else if ((gso->dvec[0] >= 0.6f) && (gso->dvec[0] < 0.8f)) { + r_idx[0] = 1; + r_idx[1] = 1; + } + else if (gso->dvec[0] >= 0.8f) { + r_idx[0] = 0; + r_idx[1] = 1; + } + } +} + +static int gp_grid_cell_index_get(tGP_BrushVertexpaintData *gso, int pc[2]) +{ + float bottom[2], top[2]; + + for (int i = 0; i < gso->grid_len; i++) { + tGP_Grid *grid = &gso->grid[i]; + add_v2_v2v2(bottom, grid->bottom, gso->mval); + add_v2_v2v2(top, grid->top, gso->mval); + + if (pc[0] >= bottom[0] && pc[0] <= top[0] && pc[1] >= bottom[1] && pc[1] <= top[1]) { + return i; + } + } + + return -1; +} + +/* Fill the grid with the color in each cell and assign point cell index. */ +static void gp_grid_colors_calc(tGP_BrushVertexpaintData *gso) +{ + tGP_Selected *selected = NULL; + bGPDstroke *gps_selected = NULL; + bGPDspoint *pt = NULL; + tGP_Grid *grid = NULL; + + /* Don't calculate again. */ + if (gso->grid_ready) { + return; + } + + /* Extract colors by cell. */ + for (int i = 0; i < gso->pbuffer_used; i++) { + selected = &gso->pbuffer[i]; + gps_selected = selected->gps; + pt = &gps_selected->points[selected->pt_index]; + int grid_index = gp_grid_cell_index_get(gso, selected->pc); + + if (grid_index > -1) { + grid = &gso->grid[grid_index]; + /* Add stroke mix color (only if used). */ + if (pt->vert_color[3] > 0.0f) { + add_v3_v3(grid->color, selected->color); + grid->color[3] = 1.0f; + grid->totcol++; + } + } + } + + /* Average colors. */ + for (int i = 0; i < gso->grid_len; i++) { + grid = &gso->grid[i]; + if (grid->totcol > 0) { + mul_v3_fl(grid->color, (1.0f / (float)grid->totcol)); + } + } + + /* Save sample position. */ + round_v2i_v2fl(gso->grid_sample, gso->mval); + + gso->grid_ready = true; + + return; +} + +/* ************************************************ */ +/* 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. */ + +/* Tint Brush */ +static bool brush_tint_apply(tGP_BrushVertexpaintData *gso, + bGPDstroke *gps, + int pt_index, + const int radius, + const int co[2]) +{ + Brush *brush = gso->brush; + + /* Attenuate factor to get a smoother tinting. */ + float inf = (brush_influence_calc(gso, radius, co) * brush->gpencil_settings->draw_strength) / + 100.0f; + float inf_fill = (gso->pressure * brush->gpencil_settings->draw_strength) / 1000.0f; + + CLAMP(inf, 0.0f, 1.0f); + CLAMP(inf_fill, 0.0f, 1.0f); + + bGPDspoint *pt = &gps->points[pt_index]; + + /* Apply color to Stroke point. */ + if (GPENCIL_TINT_VERTEX_COLOR_STROKE(brush)) { + if (brush_invert_check(gso)) { + pt->vert_color[3] -= inf; + CLAMP_MIN(pt->vert_color[3], 0.0f); + } + else { + /* Premult. */ + mul_v3_fl(pt->vert_color, pt->vert_color[3]); + /* "Alpha over" blending. */ + interp_v3_v3v3(pt->vert_color, pt->vert_color, brush->rgb, inf); + pt->vert_color[3] = pt->vert_color[3] * (1.0 - inf) + inf; + /* Un-premult. */ + if (pt->vert_color[3] > 0.0f) { + mul_v3_fl(pt->vert_color, 1.0f / pt->vert_color[3]); + } + } + } + + /* Apply color to Fill area (all with same color and factor). */ + if (GPENCIL_TINT_VERTEX_COLOR_FILL(brush)) { + if (brush_invert_check(gso)) { + gps->vert_color_fill[3] -= inf_fill; + CLAMP_MIN(gps->vert_color_fill[3], 0.0f); + } + else { + /* Premult. */ + mul_v3_fl(gps->vert_color_fill, gps->vert_color_fill[3]); + /* "Alpha over" blending. */ + interp_v3_v3v3(gps->vert_color_fill, gps->vert_color_fill, brush->rgb, inf_fill); + gps->vert_color_fill[3] = gps->vert_color_fill[3] * (1.0 - inf_fill) + inf_fill; + /* Un-premult. */ + if (gps->vert_color_fill[3] > 0.0f) { + mul_v3_fl(gps->vert_color_fill, 1.0f / gps->vert_color_fill[3]); + } + } + } + + return true; +} + +/* Replace Brush (Don't use pressure or invert). */ +static bool brush_replace_apply(tGP_BrushVertexpaintData *gso, bGPDstroke *gps, int pt_index) +{ + Brush *brush = gso->brush; + bGPDspoint *pt = &gps->points[pt_index]; + + /* Apply color to Stroke point. */ + if (GPENCIL_TINT_VERTEX_COLOR_STROKE(brush)) { + copy_v3_v3(pt->vert_color, brush->rgb); + /* If not mix color, full replace. */ + if (pt->vert_color[3] == 0.0f) { + pt->vert_color[3] = 1.0f; + } + } + + /* Apply color to Fill area (all with same color and factor). */ + if (GPENCIL_TINT_VERTEX_COLOR_FILL(brush)) { + copy_v3_v3(gps->vert_color_fill, brush->rgb); + /* If not mix color, full replace. */ + if (gps->vert_color_fill[3] == 0.0f) { + gps->vert_color_fill[3] = 1.0f; + } + } + + return true; +} + +/* Get surrounding color. */ +static bool get_surrounding_color(tGP_BrushVertexpaintData *gso, + bGPDstroke *gps, + int pt_index, + float r_color[3]) +{ + tGP_Selected *selected = NULL; + bGPDstroke *gps_selected = NULL; + bGPDspoint *pt = NULL; + + int totcol = 0; + zero_v3(r_color); + + /* Average the surrounding points except current one. */ + for (int i = 0; i < gso->pbuffer_used; i++) { + selected = &gso->pbuffer[i]; + gps_selected = selected->gps; + /* current point is not evaluated. */ + if ((gps_selected == gps) && (selected->pt_index == pt_index)) { + continue; + } + + pt = &gps_selected->points[selected->pt_index]; + + /* Add stroke mix color (only if used). */ + if (pt->vert_color[3] > 0.0f) { + add_v3_v3(r_color, selected->color); + totcol++; + } + } + if (totcol > 0) { + mul_v3_fl(r_color, (1.0f / (float)totcol)); + return true; + } + + return false; +} + +/* Blur Brush */ +static bool brush_blur_apply(tGP_BrushVertexpaintData *gso, + bGPDstroke *gps, + int pt_index, + const int radius, + const int co[2]) +{ + Brush *brush = gso->brush; + + /* Attenuate factor to get a smoother tinting. */ + float inf = (brush_influence_calc(gso, radius, co) * brush->gpencil_settings->draw_strength) / + 100.0f; + float inf_fill = (gso->pressure * brush->gpencil_settings->draw_strength) / 1000.0f; + + bGPDspoint *pt = &gps->points[pt_index]; + + /* Get surrounding color. */ + float blur_color[3]; + if (get_surrounding_color(gso, gps, pt_index, blur_color)) { + /* Apply color to Stroke point. */ + if (GPENCIL_TINT_VERTEX_COLOR_STROKE(brush)) { + interp_v3_v3v3(pt->vert_color, pt->vert_color, blur_color, inf); + } + + /* Apply color to Fill area (all with same color and factor). */ + if (GPENCIL_TINT_VERTEX_COLOR_FILL(brush)) { + interp_v3_v3v3(gps->vert_color_fill, gps->vert_color_fill, blur_color, inf_fill); + } + return true; + } + + return false; +} + +/* Average Brush */ +static bool brush_average_apply(tGP_BrushVertexpaintData *gso, + bGPDstroke *gps, + int pt_index, + const int radius, + const int co[2], + float average_color[3]) +{ + Brush *brush = gso->brush; + + /* Attenuate factor to get a smoother tinting. */ + float inf = (brush_influence_calc(gso, radius, co) * brush->gpencil_settings->draw_strength) / + 100.0f; + float inf_fill = (gso->pressure * brush->gpencil_settings->draw_strength) / 1000.0f; + + bGPDspoint *pt = &gps->points[pt_index]; + + float alpha = pt->vert_color[3]; + float alpha_fill = gps->vert_color_fill[3]; + + if (brush_invert_check(gso)) { + alpha -= inf; + alpha_fill -= inf_fill; + } + else { + alpha += inf; + alpha_fill += inf_fill; + } + + /* Apply color to Stroke point. */ + if (GPENCIL_TINT_VERTEX_COLOR_STROKE(brush)) { + CLAMP(alpha, 0.0f, 1.0f); + interp_v3_v3v3(pt->vert_color, pt->vert_color, average_color, inf); + pt->vert_color[3] = alpha; + } + + /* Apply color to Fill area (all with same color and factor). */ + if (GPENCIL_TINT_VERTEX_COLOR_FILL(brush)) { + CLAMP(alpha_fill, 0.0f, 1.0f); + copy_v3_v3(gps->vert_color_fill, average_color); + gps->vert_color_fill[3] = alpha_fill; + } + + return true; +} + +/* Smear Brush */ +static bool brush_smear_apply(tGP_BrushVertexpaintData *gso, + bGPDstroke *gps, + int pt_index, + tGP_Selected *selected) +{ + Brush *brush = gso->brush; + tGP_Grid *grid = NULL; + int average_idx[2]; + ARRAY_SET_ITEMS(average_idx, 0, 0); + + bool changed = false; + + /* Need some movement, so first input is not done. */ + if (gso->first) { + return false; + } + + bGPDspoint *pt = &gps->points[pt_index]; + + /* Need get average colors in the grid. */ + if ((!gso->grid_ready) && (gso->pbuffer_used > 0)) { + gp_grid_colors_calc(gso); + } + + /* The influence is equal to strength and no decay around brush radius. */ + float inf = brush->gpencil_settings->draw_strength; + if (brush->flag & GP_BRUSH_USE_PRESSURE) { + inf *= gso->pressure; + } + + /* Calc distance from initial sample location and add a fallof effect. */ + int mval_i[2]; + round_v2i_v2fl(mval_i, gso->mval); + float distance = (float)len_v2v2_int(mval_i, gso->grid_sample); + float fac = 1.0f - (distance / (float)(brush->size * 2)); + CLAMP(fac, 0.0f, 1.0f); + inf *= fac; + + /* Retry row and col for average color. */ + gp_grid_cell_average_color_idx_get(gso, average_idx); + + /* Retry average color cell. */ + int grid_index = gp_grid_cell_index_get(gso, selected->pc); + if (grid_index > -1) { + int row = grid_index / gso->grid_size; + int col = grid_index - (gso->grid_size * row); + row += average_idx[0]; + col += average_idx[1]; + CLAMP(row, 0, gso->grid_size); + CLAMP(col, 0, gso->grid_size); + + int new_index = (row * gso->grid_size) + col; + CLAMP(new_index, 0, gso->grid_len - 1); + grid = &gso->grid[new_index]; + } + + /* Apply color to Stroke point. */ + if (GPENCIL_TINT_VERTEX_COLOR_STROKE(brush)) { + if (grid_index > -1) { + if (grid->color[3] > 0.0f) { + // copy_v3_v3(pt->vert_color, grid->color); + interp_v3_v3v3(pt->vert_color, pt->vert_color, grid->color, inf); + changed = true; + } + } + } + + /* Apply color to Fill area (all with same color and factor). */ + if (GPENCIL_TINT_VERTEX_COLOR_FILL(brush)) { + if (grid_index > -1) { + if (grid->color[3] > 0.0f) { + interp_v3_v3v3(gps->vert_color_fill, gps->vert_color_fill, grid->color, inf); + changed = true; + } + } + } + + return changed; +} + +/* ************************************************ */ +/* Header Info */ +static void gp_vertexpaint_brush_header_set(bContext *C) +{ + ED_workspace_status_text(C, + TIP_("GPencil Vertex Paint: LMB to paint | RMB/Escape to Exit" + " | Ctrl to Invert Action")); +} + +/* ************************************************ */ +/* Grease Pencil Vertex Paint Operator */ + +/* Init/Exit ----------------------------------------------- */ + +static bool gp_vertexpaint_brush_init(bContext *C, wmOperator *op) +{ + Scene *scene = CTX_data_scene(C); + ToolSettings *ts = CTX_data_tool_settings(C); + Object *ob = CTX_data_active_object(C); + Paint *paint = ob->mode == OB_MODE_VERTEX_GPENCIL ? &ts->gp_vertexpaint->paint : + &ts->gp_paint->paint; + + /* set the brush using the tool */ + tGP_BrushVertexpaintData *gso; + + /* setup operator data */ + gso = MEM_callocN(sizeof(tGP_BrushVertexpaintData), "tGP_BrushVertexpaintData"); + op->customdata = gso; + + gso->brush = paint->brush; + BKE_curvemapping_initialize(gso->brush->curve); + + gso->is_painting = false; + gso->first = true; + + gso->pbuffer = NULL; + gso->pbuffer_size = 0; + gso->pbuffer_used = 0; + + /* Alloc grid array */ + gso->grid_size = (int)(((gso->brush->size * 2.0f) / GP_GRID_PIXEL_SIZE) + 1.0); + /* Square value. */ + gso->grid_len = gso->grid_size * gso->grid_size; + gso->grid = MEM_callocN(sizeof(tGP_Grid) * gso->grid_len, "tGP_Grid"); + gso->grid_ready = false; + + gso->gpd = ED_gpencil_data_get_active(C); + gso->scene = scene; + gso->object = ob; + + gso->region = CTX_wm_region(C); + + /* Save mask. */ + gso->mask = ts->gpencil_selectmode_vertex; + + /* Multiframe settings. */ + gso->is_multiframe = (bool)GPENCIL_MULTIEDIT_SESSIONS_ON(gso->gpd); + gso->use_multiframe_falloff = (ts->gp_sculpt.flag & GP_SCULPT_SETT_FLAG_FRAME_FALLOFF) != 0; + + /* Init multi-edit falloff curve data before doing anything, + * so we won't have to do it again later. */ + if (gso->is_multiframe) { + BKE_curvemapping_initialize(ts->gp_sculpt.cur_falloff); + } + + /* Setup space conversions. */ + gp_point_conversion_init(C, &gso->gsc); + + /* Update header. */ + gp_vertexpaint_brush_header_set(C); + + return true; +} + +static void gp_vertexpaint_brush_exit(bContext *C, wmOperator *op) +{ + tGP_BrushVertexpaintData *gso = op->customdata; + + /* Disable headerprints. */ + ED_workspace_status_text(C, NULL); + + /* Disable temp invert flag. */ + gso->brush->flag &= ~GP_VERTEX_FLAG_TMP_INVERT; + + /* Free operator data */ + MEM_SAFE_FREE(gso->pbuffer); + MEM_SAFE_FREE(gso->grid); + MEM_SAFE_FREE(gso); + op->customdata = NULL; +} + +/* Poll callback for stroke vertex paint operator. */ +static bool gp_vertexpaint_brush_poll(bContext *C) +{ + /* NOTE: this is a bit slower, but is the most accurate... */ + return CTX_DATA_COUNT(C, editable_gpencil_strokes) != 0; +} + +/* Helper to save the points selected by the brush. */ +static void gp_save_selected_point(tGP_BrushVertexpaintData *gso, + bGPDstroke *gps, + int index, + int pc[2]) +{ + tGP_Selected *selected; + bGPDspoint *pt = &gps->points[index]; + + /* Ensure the array to save the list of selected points is big enough. */ + gso->pbuffer = gpencil_select_buffer_ensure( + gso->pbuffer, &gso->pbuffer_size, &gso->pbuffer_used, false); + + selected = &gso->pbuffer[gso->pbuffer_used]; + selected->gps = gps; + selected->pt_index = index; + copy_v2_v2_int(selected->pc, pc); + copy_v4_v4(selected->color, pt->vert_color); + + gso->pbuffer_used++; +} + +/* Select points in this stroke and add to an array to be used later. */ +static void gp_vertexpaint_select_stroke(tGP_BrushVertexpaintData *gso, + bGPDstroke *gps, + const float diff_mat[4][4]) +{ + GP_SpaceConversion *gsc = &gso->gsc; + rcti *rect = &gso->brush_rect; + Brush *brush = gso->brush; + const int radius = (brush->flag & GP_BRUSH_USE_PRESSURE) ? gso->brush->size * gso->pressure : + gso->brush->size; + bGPDstroke *gps_active = (gps->runtime.gps_orig) ? gps->runtime.gps_orig : gps; + bGPDspoint *pt_active = NULL; + + bGPDspoint *pt1, *pt2; + bGPDspoint *pt = NULL; + int pc1[2] = {0}; + int pc2[2] = {0}; + int i; + int index; + bool include_last = false; + + /* Check if the stroke collide with brush. */ + if (!ED_gpencil_stroke_check_collision(gsc, gps, gso->mval, radius, diff_mat)) { + return; + } + + if (gps->totpoints == 1) { + bGPDspoint pt_temp; + pt = &gps->points[0]; + gp_point_to_parent_space(gps->points, diff_mat, &pt_temp); + gp_point_to_xy(gsc, gps, &pt_temp, &pc1[0], &pc1[1]); + + pt_active = (pt->runtime.pt_orig) ? pt->runtime.pt_orig : pt; + /* 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 */ + int mval_i[2]; + round_v2i_v2fl(mval_i, gso->mval); + if (len_v2v2_int(mval_i, pc1) <= radius) { + /* apply operation to this point */ + if (pt_active != NULL) { + gp_save_selected_point(gso, gps_active, 0, 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 ((GPENCIL_ANY_VERTEX_MASK(gso->mask)) && (GPENCIL_VERTEX_MODE(gso->gpd))) { + if (!(pt1->flag & GP_SPOINT_SELECT) && !(pt2->flag & GP_SPOINT_SELECT)) { + include_last = false; + continue; + } + } + + bGPDspoint npt; + gp_point_to_parent_space(pt1, diff_mat, &npt); + gp_point_to_xy(gsc, gps, &npt, &pc1[0], &pc1[1]); + + gp_point_to_parent_space(pt2, diff_mat, &npt); + gp_point_to_xy(gsc, gps, &npt, &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 + * brush 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])) { + + /* To each point individually... */ + pt = &gps->points[i]; + pt_active = (pt->runtime.pt_orig) ? pt->runtime.pt_orig : pt; + index = (pt->runtime.pt_orig) ? pt->runtime.idx_orig : i; + if (pt_active != NULL) { + gp_save_selected_point(gso, gps_active, index, 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) { + pt = &gps->points[i + 1]; + pt_active = (pt->runtime.pt_orig) ? pt->runtime.pt_orig : pt; + index = (pt->runtime.pt_orig) ? pt->runtime.idx_orig : i + 1; + if (pt_active != NULL) { + gp_save_selected_point(gso, gps_active, index, pc2); + include_last = false; + } + } + else { + include_last = true; + } + } + 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). + */ + pt = &gps->points[i]; + pt_active = (pt->runtime.pt_orig) ? pt->runtime.pt_orig : pt; + index = (pt->runtime.pt_orig) ? pt->runtime.idx_orig : i; + if (pt_active != NULL) { + gp_save_selected_point(gso, gps_active, index, pc1); + + include_last = false; + } + } + } + } + } +} + +/* Apply vertex paint brushes to strokes in the given frame. */ +static bool gp_vertexpaint_brush_do_frame(bContext *C, + tGP_BrushVertexpaintData *gso, + bGPDlayer *gpl, + bGPDframe *gpf, + const float diff_mat[4][4]) +{ + Object *ob = CTX_data_active_object(C); + char tool = ob->mode == OB_MODE_VERTEX_GPENCIL ? gso->brush->gpencil_vertex_tool : + gso->brush->gpencil_tool; + const int radius = (gso->brush->flag & GP_BRUSH_USE_PRESSURE) ? + gso->brush->size * gso->pressure : + gso->brush->size; + tGP_Selected *selected = NULL; + int i; + + /*--------------------------------------------------------------------- + * First step: select the points affected. This step is required to have + * all selected points before apply the effect, because it could be + * required to average data. + *--------------------------------------------------------------------- */ + LISTBASE_FOREACH (bGPDstroke *, gps, &gpf->strokes) { + /* Skip strokes that are invalid for current view. */ + if (ED_gpencil_stroke_can_use(C, gps) == false) { + continue; + } + /* Check if the color is editable. */ + if (ED_gpencil_stroke_color_use(ob, gpl, gps) == false) { + continue; + } + + /* Check points below the brush. */ + gp_vertexpaint_select_stroke(gso, gps, diff_mat); + } + + /* For Average tool, need calculate the average resulting color from all colors + * under the brush. */ + float average_color[3] = {0}; + int totcol = 0; + if ((tool == GPVERTEX_TOOL_AVERAGE) && (gso->pbuffer_used > 0)) { + for (i = 0; i < gso->pbuffer_used; i++) { + selected = &gso->pbuffer[i]; + bGPDstroke *gps = selected->gps; + bGPDspoint *pt = &gps->points[selected->pt_index]; + + /* Add stroke mix color (only if used). */ + if (pt->vert_color[3] > 0.0f) { + add_v3_v3(average_color, pt->vert_color); + totcol++; + } + + /* If Fill color mix, add to average. */ + if (gps->vert_color_fill[3] > 0.0f) { + add_v3_v3(average_color, gps->vert_color_fill); + totcol++; + } + } + + /* Get average. */ + if (totcol > 0) { + mul_v3_fl(average_color, (1.0f / (float)totcol)); + } + } + + /*--------------------------------------------------------------------- + * Second step: Apply effect. + *--------------------------------------------------------------------- */ + bool changed = false; + for (i = 0; i < gso->pbuffer_used; i++) { + changed = true; + selected = &gso->pbuffer[i]; + + switch (tool) { + case GPAINT_TOOL_TINT: + case GPVERTEX_TOOL_DRAW: { + brush_tint_apply(gso, selected->gps, selected->pt_index, radius, selected->pc); + changed |= true; + break; + } + case GPVERTEX_TOOL_BLUR: { + brush_blur_apply(gso, selected->gps, selected->pt_index, radius, selected->pc); + changed |= true; + break; + } + case GPVERTEX_TOOL_AVERAGE: { + brush_average_apply( + gso, selected->gps, selected->pt_index, radius, selected->pc, average_color); + changed |= true; + break; + } + case GPVERTEX_TOOL_SMEAR: { + brush_smear_apply(gso, selected->gps, selected->pt_index, selected); + changed |= true; + break; + } + case GPVERTEX_TOOL_REPLACE: { + brush_replace_apply(gso, selected->gps, selected->pt_index); + changed |= true; + break; + } + + default: + printf("ERROR: Unknown type of GPencil Vertex Paint brush\n"); + break; + } + } + /* Clear the selected array, but keep the memory allocation.*/ + gso->pbuffer = gpencil_select_buffer_ensure( + gso->pbuffer, &gso->pbuffer_size, &gso->pbuffer_used, true); + + return changed; +} + +/* Apply brush effect to all layers. */ +static bool gp_vertexpaint_brush_apply_to_layers(bContext *C, tGP_BrushVertexpaintData *gso) +{ + ToolSettings *ts = CTX_data_tool_settings(C); + Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C); + Object *obact = gso->object; + bool changed = false; + + Object *ob_eval = (Object *)DEG_get_evaluated_id(depsgraph, &obact->id); + bGPdata *gpd = (bGPdata *)ob_eval->data; + + /* Find visible strokes, and perform operations on those if hit */ + LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd->layers) { + /* If locked or no active frame, don't do anything. */ + if ((!BKE_gpencil_layer_is_editable(gpl)) || (gpl->actframe == NULL)) { + continue; + } + + /* calculate difference matrix */ + float diff_mat[4][4]; + BKE_gpencil_parent_matrix_get(depsgraph, obact, gpl, diff_mat); + + /* Active Frame or MultiFrame? */ + if (gso->is_multiframe) { + /* init multiframe falloff options */ + int f_init = 0; + int f_end = 0; + + if (gso->use_multiframe_falloff) { + BKE_gpencil_frame_range_selected(gpl, &f_init, &f_end); + } + + LISTBASE_FOREACH (bGPDframe *, gpf, &gpl->frames) { + /* Always do active frame; Otherwise, only include selected frames */ + if ((gpf == gpl->actframe) || (gpf->flag & GP_FRAME_SELECT)) { + /* compute multiframe falloff factor */ + if (gso->use_multiframe_falloff) { + /* Faloff depends on distance to active frame (relative to the overall frame range) + */ + gso->mf_falloff = BKE_gpencil_multiframe_falloff_calc( + gpf, gpl->actframe->framenum, f_init, f_end, ts->gp_sculpt.cur_falloff); + } + else { + /* No falloff */ + gso->mf_falloff = 1.0f; + } + + /* affect strokes in this frame */ + changed |= gp_vertexpaint_brush_do_frame(C, gso, gpl, gpf, diff_mat); + } + } + } + else { + /* Apply to active frame's strokes */ + if (gpl->actframe != NULL) { + gso->mf_falloff = 1.0f; + changed |= gp_vertexpaint_brush_do_frame(C, gso, gpl, gpl->actframe, diff_mat); + } + } + } + + return changed; +} + +/* Calculate settings for applying brush */ +static void gp_vertexpaint_brush_apply(bContext *C, wmOperator *op, PointerRNA *itemptr) +{ + tGP_BrushVertexpaintData *gso = op->customdata; + Brush *brush = gso->brush; + const int radius = ((brush->flag & GP_BRUSH_USE_PRESSURE) ? gso->brush->size * gso->pressure : + 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_VERTEX_FLAG_INVERT; + } + else { + gso->flag &= ~GP_VERTEX_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; + + /* Calc 2D direction vector and relative angle. */ + brush_calc_dvec_2d(gso); + + /* Calc grid for smear tool. */ + gp_grid_cells_init(gso); + + changed = gp_vertexpaint_brush_apply_to_layers(C, gso); + + /* Updates */ + if (changed) { + DEG_id_tag_update(&gso->gpd->id, ID_RECALC_GEOMETRY); + 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 gp_vertexpaint_brush_apply_event(bContext *C, wmOperator *op, const wmEvent *event) +{ + tGP_BrushVertexpaintData *gso = op->customdata; + PointerRNA itemptr; + float mouse[2]; + + 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). */ + float pressure = event->tablet.pressure; + CLAMP(pressure, 0.0f, 1.0f); + RNA_float_set(&itemptr, "pressure", pressure); + + /* apply */ + gp_vertexpaint_brush_apply(C, op, &itemptr); +} + +/* reapply */ +static int gp_vertexpaint_brush_exec(bContext *C, wmOperator *op) +{ + if (!gp_vertexpaint_brush_init(C, op)) { + return OPERATOR_CANCELLED; + } + + RNA_BEGIN (op->ptr, itemptr, "stroke") { + gp_vertexpaint_brush_apply(C, op, &itemptr); + } + RNA_END; + + gp_vertexpaint_brush_exit(C, op); + + return OPERATOR_FINISHED; +} + +/* start modal painting */ +static int gp_vertexpaint_brush_invoke(bContext *C, wmOperator *op, const wmEvent *event) +{ + tGP_BrushVertexpaintData *gso = NULL; + const bool is_modal = RNA_boolean_get(op->ptr, "wait_for_input"); + const bool is_playing = ED_screen_animation_playing(CTX_wm_manager(C)) != NULL; + + /* the operator cannot work while play animation */ + if (is_playing) { + BKE_report(op->reports, RPT_ERROR, "Cannot Paint while play animation"); + + return OPERATOR_CANCELLED; + } + + /* init painting data */ + if (!gp_vertexpaint_brush_init(C, op)) { + return OPERATOR_CANCELLED; + } + + gso = op->customdata; + + /* register modal handler */ + WM_event_add_modal_handler(C, op); + + /* start drawing immediately? */ + if (is_modal == false) { + ARegion *region = CTX_wm_region(C); + + /* apply first dab... */ + gso->is_painting = true; + gp_vertexpaint_brush_apply_event(C, op, event); + + /* redraw view with feedback */ + ED_region_tag_redraw(region); + } + + return OPERATOR_RUNNING_MODAL; +} + +/* painting - handle events */ +static int gp_vertexpaint_brush_modal(bContext *C, wmOperator *op, const wmEvent *event) +{ + tGP_BrushVertexpaintData *gso = op->customdata; + const bool is_modal = RNA_boolean_get(op->ptr, "wait_for_input"); + bool redraw_region = false; + bool redraw_toolsettings = 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 */ + gp_vertexpaint_brush_apply_event(C, op, event); + + /* force redraw, so that the cursor will at least be valid */ + redraw_region = true; + break; + + /* Painting mbut release = Stop painting (back to idle) */ + case LEFTMOUSE: + if (is_modal) { + /* go back to idling... */ + gso->is_painting = false; + } + else { + /* end painting, since we're not modal */ + gso->is_painting = false; + + gp_vertexpaint_brush_exit(C, op); + return OPERATOR_FINISHED; + } + break; + + /* Abort painting if any of the usual things are tried */ + case MIDDLEMOUSE: + case RIGHTMOUSE: + case ESCKEY: + gp_vertexpaint_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; + + gp_vertexpaint_brush_apply_event(C, op, event); + break; + + /* Exit modal operator, based on the "standard" ops */ + case RIGHTMOUSE: + case ESCKEY: + gp_vertexpaint_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; + + /* Change Frame - Allowed */ + case LEFTARROWKEY: + case RIGHTARROWKEY: + case UPARROWKEY: + case DOWNARROWKEY: + return OPERATOR_PASS_THROUGH; + + /* Camera/View Gizmo's - Allowed */ + /* (See rationale in gpencil_paint.c -> gpencil_draw_modal()) */ + case PAD0: + case PAD1: + case PAD2: + case PAD3: + case PAD4: + case PAD5: + case PAD6: + case PAD7: + case PAD8: + case PAD9: + return OPERATOR_PASS_THROUGH; + + /* Unhandled event */ + default: + break; + } + } + + /* Redraw region? */ + if (redraw_region) { + ED_region_tag_redraw(CTX_wm_region(C)); + } + + /* Redraw toolsettings (brush settings)? */ + if (redraw_toolsettings) { + DEG_id_tag_update(&gso->gpd->id, ID_RECALC_GEOMETRY); + WM_event_add_notifier(C, NC_SCENE | ND_TOOLSETTINGS, NULL); + } + + return OPERATOR_RUNNING_MODAL; +} + +void GPENCIL_OT_vertex_paint(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Stroke Vertex Paint"; + ot->idname = "GPENCIL_OT_vertex_paint"; + ot->description = "Paint stroke points with a color"; + + /* api callbacks */ + ot->exec = gp_vertexpaint_brush_exec; + ot->invoke = gp_vertexpaint_brush_invoke; + ot->modal = gp_vertexpaint_brush_modal; + ot->cancel = gp_vertexpaint_brush_exit; + ot->poll = gp_vertexpaint_brush_poll; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO | OPTYPE_BLOCKING; + + /* properties */ + PropertyRNA *prop; + prop = RNA_def_collection_runtime(ot->srna, "stroke", &RNA_OperatorStrokeElement, "Stroke", ""); + RNA_def_property_flag(prop, PROP_HIDDEN | PROP_SKIP_SAVE); + + prop = RNA_def_boolean(ot->srna, "wait_for_input", true, "Wait for Input", ""); + RNA_def_property_flag(prop, PROP_HIDDEN | PROP_SKIP_SAVE); +} diff --git a/source/blender/editors/gpencil/gpencil_weight_paint.c b/source/blender/editors/gpencil/gpencil_weight_paint.c new file mode 100644 index 00000000000..6b337afa559 --- /dev/null +++ b/source/blender/editors/gpencil/gpencil_weight_paint.c @@ -0,0 +1,901 @@ +/* + * 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 + * Brush based operators for editing Grease Pencil strokes + */ + +/** \file + * \ingroup edgpencil + */ + +#include "MEM_guardedalloc.h" + +#include "BLI_blenlib.h" +#include "BLI_math.h" + +#include "BLT_translation.h" + +#include "DNA_brush_types.h" +#include "DNA_gpencil_types.h" + +#include "BKE_brush.h" +#include "BKE_colortools.h" +#include "BKE_context.h" +#include "BKE_deform.h" +#include "BKE_gpencil.h" +#include "BKE_main.h" +#include "DNA_meshdata_types.h" +#include "BKE_object_deform.h" +#include "BKE_report.h" + +#include "WM_api.h" +#include "WM_types.h" + +#include "RNA_access.h" +#include "RNA_define.h" + +#include "UI_view2d.h" + +#include "ED_gpencil.h" +#include "ED_screen.h" +#include "ED_view3d.h" + +#include "DEG_depsgraph.h" +#include "DEG_depsgraph_query.h" + +#include "gpencil_intern.h" + +/* ************************************************ */ +/* General Brush Editing Context */ +#define GP_SELECT_BUFFER_CHUNK 256 + +/* Grid of Colors for Smear. */ +typedef struct tGP_Grid { + /** Lower right corner of rectangle of grid cell. */ + float bottom[2]; + /** Upper left corner of rectangle of grid cell. */ + float top[2]; + /** Average Color */ + float color[4]; + /** Total points included. */ + int totcol; + +} tGP_Grid; + +/* List of points affected by brush. */ +typedef struct tGP_Selected { + /** Referenced stroke. */ + bGPDstroke *gps; + /** Point index in points array. */ + int pt_index; + /** Position */ + int pc[2]; + /** Color */ + float color[4]; +} tGP_Selected; + +/* Context for brush operators */ +typedef struct tGP_BrushWeightpaintData { + struct Main *bmain; + Scene *scene; + Object *object; + + ARegion *region; + + /* Current GPencil datablock */ + bGPdata *gpd; + + Brush *brush; + + /* Space Conversion Data */ + GP_SpaceConversion gsc; + + /* Is the brush currently painting? */ + bool is_painting; + + /* Start of new paint */ + bool first; + + /* Is multiframe editing enabled, and are we using falloff for that? */ + bool is_multiframe; + bool use_multiframe_falloff; + + /* active vertex group */ + int vrgroup; + + /* Brush Runtime Data: */ + /* - position and pressure + * - the *_prev variants are the previous values + */ + float mval[2], mval_prev[2]; + float pressure, pressure_prev; + + /* - Effect 2D vector */ + float dvec[2]; + + /* - multiframe falloff factor */ + float mf_falloff; + + /* brush geometry (bounding box) */ + rcti brush_rect; + + /* Temp data to save selected points */ + /** Stroke buffer. */ + tGP_Selected *pbuffer; + /** Number of elements currently used in cache. */ + int pbuffer_used; + /** Number of total elements available in cache. */ + int pbuffer_size; +} tGP_BrushWeightpaintData; + +/* Ensure the buffer to hold temp selected point size is enough to save all points selected. */ +static tGP_Selected *gpencil_select_buffer_ensure(tGP_Selected *buffer_array, + int *buffer_size, + int *buffer_used, + const bool clear) +{ + tGP_Selected *p = NULL; + + /* By default a buffer is created with one block with a predefined number of free slots, + * if the size is not enough, the cache is reallocated adding a new block of free slots. + * This is done in order to keep cache small and improve speed. */ + if (*buffer_used + 1 > *buffer_size) { + if ((*buffer_size == 0) || (buffer_array == NULL)) { + p = MEM_callocN(sizeof(struct tGP_Selected) * GP_SELECT_BUFFER_CHUNK, __func__); + *buffer_size = GP_SELECT_BUFFER_CHUNK; + } + else { + *buffer_size += GP_SELECT_BUFFER_CHUNK; + p = MEM_recallocN(buffer_array, sizeof(struct tGP_Selected) * *buffer_size); + } + + if (p == NULL) { + *buffer_size = *buffer_used = 0; + } + + buffer_array = p; + } + + /* clear old data */ + if (clear) { + *buffer_used = 0; + if (buffer_array != NULL) { + memset(buffer_array, 0, sizeof(tGP_Selected) * *buffer_size); + } + } + + return buffer_array; +} + +/* Brush Operations ------------------------------- */ + +/* Compute strength of effect. */ +static float brush_influence_calc(tGP_BrushWeightpaintData *gso, const int radius, const int co[2]) +{ + Brush *brush = gso->brush; + + /* basic strength factor from brush settings */ + float influence = brush->alpha; + + /* use pressure? */ + if (brush->gpencil_settings->flag & GP_BRUSH_USE_PRESSURE) { + influence *= gso->pressure; + } + + /* distance fading */ + int mval_i[2]; + round_v2i_v2fl(mval_i, gso->mval); + float distance = (float)len_v2v2_int(mval_i, co); + influence *= 1.0f - (distance / max_ff(radius, 1e-8)); + + /* Apply Brush curve. */ + float brush_fallof = BKE_brush_curve_strength(brush, distance, (float)radius); + influence *= brush_fallof; + + /* apply multiframe falloff */ + influence *= gso->mf_falloff; + + /* return influence */ + return influence; +} + +/* Compute effect vector for directional brushes. */ +static void brush_calc_dvec_2d(tGP_BrushWeightpaintData *gso) +{ + gso->dvec[0] = (float)(gso->mval[0] - gso->mval_prev[0]); + gso->dvec[1] = (float)(gso->mval[1] - gso->mval_prev[1]); + + normalize_v2(gso->dvec); +} + +/* ************************************************ */ +/* 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. */ + +/* Draw Brush */ +static bool brush_draw_apply(tGP_BrushWeightpaintData *gso, + bGPDstroke *gps, + int pt_index, + const int radius, + const int co[2]) +{ + /* create dvert */ + BKE_gpencil_dvert_ensure(gps); + + MDeformVert *dvert = gps->dvert + pt_index; + float inf; + + /* Compute strength of effect */ + inf = brush_influence_calc(gso, radius, co); + + /* need a vertex group */ + if (gso->vrgroup == -1) { + if (gso->object) { + BKE_object_defgroup_add(gso->object); + DEG_relations_tag_update(gso->bmain); + gso->vrgroup = 0; + } + } + else { + bDeformGroup *defgroup = BLI_findlink(&gso->object->defbase, gso->vrgroup); + if (defgroup->flag & DG_LOCK_WEIGHT) { + return false; + } + } + /* Get current weight and blend. */ + MDeformWeight *dw = BKE_defvert_ensure_index(dvert, gso->vrgroup); + if (dw) { + dw->weight = interpf(gso->brush->weight, dw->weight, inf); + CLAMP(dw->weight, 0.0f, 1.0f); + } + return true; +} + +/* ************************************************ */ +/* Header Info */ +static void gp_weightpaint_brush_header_set(bContext *C) +{ + ED_workspace_status_text(C, TIP_("GPencil Weight Paint: LMB to paint | RMB/Escape to Exit")); +} + +/* ************************************************ */ +/* Grease Pencil Weight Paint Operator */ + +/* Init/Exit ----------------------------------------------- */ + +static bool gp_weightpaint_brush_init(bContext *C, wmOperator *op) +{ + Scene *scene = CTX_data_scene(C); + ToolSettings *ts = CTX_data_tool_settings(C); + Object *ob = CTX_data_active_object(C); + Paint *paint = &ts->gp_weightpaint->paint; + + /* set the brush using the tool */ + tGP_BrushWeightpaintData *gso; + + /* setup operator data */ + gso = MEM_callocN(sizeof(tGP_BrushWeightpaintData), "tGP_BrushWeightpaintData"); + op->customdata = gso; + + gso->bmain = CTX_data_main(C); + + gso->brush = paint->brush; + BKE_curvemapping_initialize(gso->brush->curve); + + gso->is_painting = false; + gso->first = true; + + gso->pbuffer = NULL; + gso->pbuffer_size = 0; + gso->pbuffer_used = 0; + + gso->gpd = ED_gpencil_data_get_active(C); + gso->scene = scene; + gso->object = ob; + if (ob) { + gso->vrgroup = ob->actdef - 1; + if (!BLI_findlink(&ob->defbase, gso->vrgroup)) { + gso->vrgroup = -1; + } + } + else { + gso->vrgroup = -1; + } + + gso->region = CTX_wm_region(C); + + /* Multiframe settings. */ + gso->is_multiframe = (bool)GPENCIL_MULTIEDIT_SESSIONS_ON(gso->gpd); + gso->use_multiframe_falloff = (ts->gp_sculpt.flag & GP_SCULPT_SETT_FLAG_FRAME_FALLOFF) != 0; + + /* Init multi-edit falloff curve data before doing anything, + * so we won't have to do it again later. */ + if (gso->is_multiframe) { + BKE_curvemapping_initialize(ts->gp_sculpt.cur_falloff); + } + + /* Setup space conversions. */ + gp_point_conversion_init(C, &gso->gsc); + + /* Update header. */ + gp_weightpaint_brush_header_set(C); + + return true; +} + +static void gp_weightpaint_brush_exit(bContext *C, wmOperator *op) +{ + tGP_BrushWeightpaintData *gso = op->customdata; + + /* Disable headerprints. */ + ED_workspace_status_text(C, NULL); + + /* Free operator data */ + MEM_SAFE_FREE(gso->pbuffer); + MEM_SAFE_FREE(gso); + op->customdata = NULL; +} + +/* Poll callback for stroke weight paint operator. */ +static bool gp_weightpaint_brush_poll(bContext *C) +{ + /* NOTE: this is a bit slower, but is the most accurate... */ + return CTX_DATA_COUNT(C, editable_gpencil_strokes) != 0; +} + +/* Helper to save the points selected by the brush. */ +static void gp_save_selected_point(tGP_BrushWeightpaintData *gso, + bGPDstroke *gps, + int index, + int pc[2]) +{ + tGP_Selected *selected; + bGPDspoint *pt = &gps->points[index]; + + /* Ensure the array to save the list of selected points is big enough. */ + gso->pbuffer = gpencil_select_buffer_ensure( + gso->pbuffer, &gso->pbuffer_size, &gso->pbuffer_used, false); + + selected = &gso->pbuffer[gso->pbuffer_used]; + selected->gps = gps; + selected->pt_index = index; + copy_v2_v2_int(selected->pc, pc); + copy_v4_v4(selected->color, pt->vert_color); + + gso->pbuffer_used++; +} + +/* Select points in this stroke and add to an array to be used later. */ +static void gp_weightpaint_select_stroke(tGP_BrushWeightpaintData *gso, + bGPDstroke *gps, + const float diff_mat[4][4]) +{ + GP_SpaceConversion *gsc = &gso->gsc; + rcti *rect = &gso->brush_rect; + Brush *brush = gso->brush; + const int radius = (brush->flag & GP_BRUSH_USE_PRESSURE) ? gso->brush->size * gso->pressure : + gso->brush->size; + bGPDstroke *gps_active = (gps->runtime.gps_orig) ? gps->runtime.gps_orig : gps; + bGPDspoint *pt_active = NULL; + + bGPDspoint *pt1, *pt2; + bGPDspoint *pt = NULL; + int pc1[2] = {0}; + int pc2[2] = {0}; + int i; + int index; + bool include_last = false; + + /* Check if the stroke collide with brush. */ + if (!ED_gpencil_stroke_check_collision(gsc, gps, gso->mval, radius, diff_mat)) { + return; + } + + if (gps->totpoints == 1) { + bGPDspoint pt_temp; + pt = &gps->points[0]; + gp_point_to_parent_space(gps->points, diff_mat, &pt_temp); + gp_point_to_xy(gsc, gps, &pt_temp, &pc1[0], &pc1[1]); + + pt_active = (pt->runtime.pt_orig) ? pt->runtime.pt_orig : pt; + /* 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 */ + int mval_i[2]; + round_v2i_v2fl(mval_i, gso->mval); + if (len_v2v2_int(mval_i, pc1) <= radius) { + /* apply operation to this point */ + if (pt_active != NULL) { + gp_save_selected_point(gso, gps_active, 0, 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; + + bGPDspoint npt; + gp_point_to_parent_space(pt1, diff_mat, &npt); + gp_point_to_xy(gsc, gps, &npt, &pc1[0], &pc1[1]); + + gp_point_to_parent_space(pt2, diff_mat, &npt); + gp_point_to_xy(gsc, gps, &npt, &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 + * brush 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])) { + + /* To each point individually... */ + pt = &gps->points[i]; + pt_active = (pt->runtime.pt_orig) ? pt->runtime.pt_orig : pt; + index = (pt->runtime.pt_orig) ? pt->runtime.idx_orig : i; + if (pt_active != NULL) { + gp_save_selected_point(gso, gps_active, index, 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) { + pt = &gps->points[i + 1]; + pt_active = (pt->runtime.pt_orig) ? pt->runtime.pt_orig : pt; + index = (pt->runtime.pt_orig) ? pt->runtime.idx_orig : i + 1; + if (pt_active != NULL) { + gp_save_selected_point(gso, gps_active, index, pc2); + include_last = false; + } + } + else { + include_last = true; + } + } + 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). + */ + pt = &gps->points[i]; + pt_active = (pt->runtime.pt_orig) ? pt->runtime.pt_orig : pt; + index = (pt->runtime.pt_orig) ? pt->runtime.idx_orig : i; + if (pt_active != NULL) { + gp_save_selected_point(gso, gps_active, index, pc1); + + include_last = false; + } + } + } + } + } +} + +/* Apply weight paint brushes to strokes in the given frame. */ +static bool gp_weightpaint_brush_do_frame(bContext *C, + tGP_BrushWeightpaintData *gso, + bGPDlayer *gpl, + bGPDframe *gpf, + const float diff_mat[4][4]) +{ + Object *ob = CTX_data_active_object(C); + char tool = gso->brush->gpencil_weight_tool; + const int radius = (gso->brush->flag & GP_BRUSH_USE_PRESSURE) ? + gso->brush->size * gso->pressure : + gso->brush->size; + tGP_Selected *selected = NULL; + int i; + + /*--------------------------------------------------------------------- + * First step: select the points affected. This step is required to have + * all selected points before apply the effect, because it could be + * required to do some step. Now is not used, but the operator is ready. + *--------------------------------------------------------------------- */ + LISTBASE_FOREACH (bGPDstroke *, gps, &gpf->strokes) { + /* Skip strokes that are invalid for current view. */ + if (ED_gpencil_stroke_can_use(C, gps) == false) { + continue; + } + /* Check if the color is editable. */ + if (ED_gpencil_stroke_color_use(ob, gpl, gps) == false) { + continue; + } + + /* Check points below the brush. */ + gp_weightpaint_select_stroke(gso, gps, diff_mat); + } + + /*--------------------------------------------------------------------- + * Second step: Apply effect. + *--------------------------------------------------------------------- */ + bool changed = false; + for (i = 0; i < gso->pbuffer_used; i++) { + changed = true; + selected = &gso->pbuffer[i]; + + switch (tool) { + case GPWEIGHT_TOOL_DRAW: { + brush_draw_apply(gso, selected->gps, selected->pt_index, radius, selected->pc); + changed |= true; + break; + } + default: + printf("ERROR: Unknown type of GPencil Weight Paint brush\n"); + break; + } + } + /* Clear the selected array, but keep the memory allocation.*/ + gso->pbuffer = gpencil_select_buffer_ensure( + gso->pbuffer, &gso->pbuffer_size, &gso->pbuffer_used, true); + + return changed; +} + +/* Apply brush effect to all layers. */ +static bool gp_weightpaint_brush_apply_to_layers(bContext *C, tGP_BrushWeightpaintData *gso) +{ + ToolSettings *ts = CTX_data_tool_settings(C); + Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C); + Object *obact = gso->object; + bool changed = false; + + Object *ob_eval = (Object *)DEG_get_evaluated_id(depsgraph, &obact->id); + bGPdata *gpd = (bGPdata *)ob_eval->data; + + /* Find visible strokes, and perform operations on those if hit */ + LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd->layers) { + /* If locked or no active frame, don't do anything. */ + if ((!BKE_gpencil_layer_is_editable(gpl)) || (gpl->actframe == NULL)) { + continue; + } + + /* calculate difference matrix */ + float diff_mat[4][4]; + BKE_gpencil_parent_matrix_get(depsgraph, obact, gpl, diff_mat); + + /* Active Frame or MultiFrame? */ + if (gso->is_multiframe) { + /* init multiframe falloff options */ + int f_init = 0; + int f_end = 0; + + if (gso->use_multiframe_falloff) { + BKE_gpencil_frame_range_selected(gpl, &f_init, &f_end); + } + + LISTBASE_FOREACH (bGPDframe *, gpf, &gpl->frames) { + /* Always do active frame; Otherwise, only include selected frames */ + if ((gpf == gpl->actframe) || (gpf->flag & GP_FRAME_SELECT)) { + /* compute multiframe falloff factor */ + if (gso->use_multiframe_falloff) { + /* Faloff depends on distance to active frame (relative to the overall frame range) + */ + gso->mf_falloff = BKE_gpencil_multiframe_falloff_calc( + gpf, gpl->actframe->framenum, f_init, f_end, ts->gp_sculpt.cur_falloff); + } + else { + /* No falloff */ + gso->mf_falloff = 1.0f; + } + + /* affect strokes in this frame */ + changed |= gp_weightpaint_brush_do_frame(C, gso, gpl, gpf, diff_mat); + } + } + } + else { + if (gpl->actframe != NULL) { + /* Apply to active frame's strokes */ + gso->mf_falloff = 1.0f; + changed |= gp_weightpaint_brush_do_frame(C, gso, gpl, gpl->actframe, diff_mat); + } + } + } + + return changed; +} + +/* Calculate settings for applying brush */ +static void gp_weightpaint_brush_apply(bContext *C, wmOperator *op, PointerRNA *itemptr) +{ + tGP_BrushWeightpaintData *gso = op->customdata; + Brush *brush = gso->brush; + const int radius = ((brush->flag & GP_BRUSH_USE_PRESSURE) ? gso->brush->size * gso->pressure : + 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"); + + /* 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; + + /* Calc 2D direction vector and relative angle. */ + brush_calc_dvec_2d(gso); + + changed = gp_weightpaint_brush_apply_to_layers(C, gso); + + /* Updates */ + if (changed) { + DEG_id_tag_update(&gso->gpd->id, ID_RECALC_GEOMETRY); + 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 gp_weightpaint_brush_apply_event(bContext *C, wmOperator *op, const wmEvent *event) +{ + tGP_BrushWeightpaintData *gso = op->customdata; + PointerRNA itemptr; + float mouse[2]; + + 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). */ + float pressure = event->tablet.pressure; + CLAMP(pressure, 0.0f, 1.0f); + RNA_float_set(&itemptr, "pressure", pressure); + + /* apply */ + gp_weightpaint_brush_apply(C, op, &itemptr); +} + +/* reapply */ +static int gp_weightpaint_brush_exec(bContext *C, wmOperator *op) +{ + if (!gp_weightpaint_brush_init(C, op)) { + return OPERATOR_CANCELLED; + } + + RNA_BEGIN (op->ptr, itemptr, "stroke") { + gp_weightpaint_brush_apply(C, op, &itemptr); + } + RNA_END; + + gp_weightpaint_brush_exit(C, op); + + return OPERATOR_FINISHED; +} + +/* start modal painting */ +static int gp_weightpaint_brush_invoke(bContext *C, wmOperator *op, const wmEvent *event) +{ + tGP_BrushWeightpaintData *gso = NULL; + const bool is_modal = RNA_boolean_get(op->ptr, "wait_for_input"); + const bool is_playing = ED_screen_animation_playing(CTX_wm_manager(C)) != NULL; + + /* the operator cannot work while play animation */ + if (is_playing) { + BKE_report(op->reports, RPT_ERROR, "Cannot Paint while play animation"); + + return OPERATOR_CANCELLED; + } + + /* init painting data */ + if (!gp_weightpaint_brush_init(C, op)) { + return OPERATOR_CANCELLED; + } + + gso = op->customdata; + + /* register modal handler */ + WM_event_add_modal_handler(C, op); + + /* start drawing immediately? */ + if (is_modal == false) { + ARegion *region = CTX_wm_region(C); + + /* apply first dab... */ + gso->is_painting = true; + gp_weightpaint_brush_apply_event(C, op, event); + + /* redraw view with feedback */ + ED_region_tag_redraw(region); + } + + return OPERATOR_RUNNING_MODAL; +} + +/* painting - handle events */ +static int gp_weightpaint_brush_modal(bContext *C, wmOperator *op, const wmEvent *event) +{ + tGP_BrushWeightpaintData *gso = op->customdata; + const bool is_modal = RNA_boolean_get(op->ptr, "wait_for_input"); + bool redraw_region = false; + bool redraw_toolsettings = 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 */ + gp_weightpaint_brush_apply_event(C, op, event); + + /* force redraw, so that the cursor will at least be valid */ + redraw_region = true; + break; + + /* Painting mbut release = Stop painting (back to idle) */ + case LEFTMOUSE: + if (is_modal) { + /* go back to idling... */ + gso->is_painting = false; + } + else { + /* end painting, since we're not modal */ + gso->is_painting = false; + + gp_weightpaint_brush_exit(C, op); + return OPERATOR_FINISHED; + } + break; + + /* Abort painting if any of the usual things are tried */ + case MIDDLEMOUSE: + case RIGHTMOUSE: + case ESCKEY: + gp_weightpaint_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; + + gp_weightpaint_brush_apply_event(C, op, event); + break; + + /* Exit modal operator, based on the "standard" ops */ + case RIGHTMOUSE: + case ESCKEY: + gp_weightpaint_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; + + /* Change Frame - Allowed */ + case LEFTARROWKEY: + case RIGHTARROWKEY: + case UPARROWKEY: + case DOWNARROWKEY: + return OPERATOR_PASS_THROUGH; + + /* Camera/View Gizmo's - Allowed */ + /* (See rationale in gpencil_paint.c -> gpencil_draw_modal()) */ + case PAD0: + case PAD1: + case PAD2: + case PAD3: + case PAD4: + case PAD5: + case PAD6: + case PAD7: + case PAD8: + case PAD9: + return OPERATOR_PASS_THROUGH; + + /* Unhandled event */ + default: + break; + } + } + + /* Redraw region? */ + if (redraw_region) { + ED_region_tag_redraw(CTX_wm_region(C)); + } + + /* Redraw toolsettings (brush settings)? */ + if (redraw_toolsettings) { + DEG_id_tag_update(&gso->gpd->id, ID_RECALC_GEOMETRY); + WM_event_add_notifier(C, NC_SCENE | ND_TOOLSETTINGS, NULL); + } + + return OPERATOR_RUNNING_MODAL; +} + +void GPENCIL_OT_weight_paint(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Stroke Weight Paint"; + ot->idname = "GPENCIL_OT_weight_paint"; + ot->description = "Paint stroke points with a color"; + + /* api callbacks */ + ot->exec = gp_weightpaint_brush_exec; + ot->invoke = gp_weightpaint_brush_invoke; + ot->modal = gp_weightpaint_brush_modal; + ot->cancel = gp_weightpaint_brush_exit; + ot->poll = gp_weightpaint_brush_poll; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO | OPTYPE_BLOCKING; + + /* properties */ + PropertyRNA *prop; + prop = RNA_def_collection_runtime(ot->srna, "stroke", &RNA_OperatorStrokeElement, "Stroke", ""); + RNA_def_property_flag(prop, PROP_HIDDEN | PROP_SKIP_SAVE); + + prop = RNA_def_boolean(ot->srna, "wait_for_input", true, "Wait for Input", ""); + RNA_def_property_flag(prop, PROP_HIDDEN | PROP_SKIP_SAVE); +} diff --git a/source/blender/editors/include/ED_gpencil.h b/source/blender/editors/include/ED_gpencil.h index 8d2d9b934d4..8dd162ea538 100644 --- a/source/blender/editors/include/ED_gpencil.h +++ b/source/blender/editors/include/ED_gpencil.h @@ -39,6 +39,7 @@ struct bGPDspoint; struct bGPDstroke; struct bGPdata; struct tGPspoint; +struct GP_SpaceConversion; struct ARegion; struct Depsgraph; @@ -46,6 +47,7 @@ struct Main; struct RegionView3D; struct ReportList; struct Scene; +struct ToolSettings; struct ScrArea; struct View3D; struct ViewLayer; @@ -75,23 +77,8 @@ typedef struct tGPspoint { float uv_rot; /* uv rotation for dor mode */ float rnd[3]; /* rnd value */ bool rnd_dirty; /* rnd flag */ - short tflag; /* Internal flag */ } tGPspoint; -/* tGPspoint->flag */ -typedef enum etGPspoint_tFlag { - /* Created by Fake event (used when mouse/pen move very fast while drawing). */ - GP_TPOINT_FAKE = (1 << 0), -} etGPspoint_tFlag; - -/* used to sort by zdepth gpencil objects in viewport */ -/* TODO: this could be a system parameter in userprefs screen */ -#define GP_CACHE_BLOCK_SIZE 16 -typedef struct tGPencilSort { - struct Base *base; - float zdepth; -} tGPencilSort; - /* ----------- Grease Pencil Tools/Context ------------- */ /* Context-dependent */ @@ -210,12 +197,6 @@ bool ED_gpencil_add_lattice_modifier(const struct bContext *C, /* ------------ Transformation Utilities ------------ */ -/* get difference matrix */ -void ED_gpencil_parent_location(const struct Depsgraph *depsgraph, - struct Object *obact, - struct bGPdata *gpd, - struct bGPDlayer *gpl, - float diff_mat[4][4]); /* reset parent matrix for all layers */ void ED_gpencil_reset_layers_parent(struct Depsgraph *depsgraph, struct Object *obact, @@ -231,7 +212,6 @@ void ED_gpencil_create_stroke(struct bContext *C, struct Object *ob, float mat[4 /* ------------ Object Utilities ------------ */ struct Object *ED_gpencil_add_object(struct bContext *C, - struct Scene *scene, const float loc[3], unsigned short local_view_bits); void ED_gpencil_add_defaults(struct bContext *C, struct Object *ob); @@ -251,11 +231,11 @@ void ED_gp_project_point_to_plane(const struct Scene *scene, const float origin[3], const int axis, struct bGPDspoint *pt); -void ED_gp_get_drawing_reference(const struct Scene *scene, - const struct Object *ob, - struct bGPDlayer *gpl, - char align_flag, - float vec[3]); +void ED_gpencil_drawing_reference_get(const struct Scene *scene, + const struct Object *ob, + struct bGPDlayer *gpl, + char align_flag, + float vec[3]); void ED_gpencil_project_stroke_to_view(struct bContext *C, struct bGPDlayer *gpl, struct bGPDstroke *gps); @@ -277,7 +257,6 @@ void ED_gpencil_tpoint_to_point(struct ARegion *region, float origin[3], const struct tGPspoint *tpt, struct bGPDspoint *pt); -void ED_gpencil_calc_stroke_uv(struct Object *ob, struct bGPDstroke *gps); void ED_gpencil_update_color_uv(struct Main *bmain, struct Material *mat); /* extend selection to stroke intersections @@ -303,9 +282,30 @@ struct tGPspoint *ED_gpencil_sbuffer_ensure(struct tGPspoint *buffer_array, int *buffer_size, int *buffer_used, const bool clear); +void ED_gpencil_sbuffer_update_eval(struct bGPdata *gpd, struct Object *ob_eval); + /* Tag all scene grease pencil object to update. */ void ED_gpencil_tag_scene_gpencil(struct Scene *scene); +/* Vertex color set. */ +void ED_gpencil_fill_vertex_color_set(struct ToolSettings *ts, + struct Brush *brush, + struct bGPDstroke *gps); +void ED_gpencil_point_vertex_color_set(struct ToolSettings *ts, + struct Brush *brush, + struct bGPDspoint *pt); +void ED_gpencil_sbuffer_vertex_color_set(struct Depsgraph *depsgraph, + struct Object *ob, + struct ToolSettings *ts, + struct Brush *brush, + struct Material *material); + +bool ED_gpencil_stroke_check_collision(struct GP_SpaceConversion *gsc, + struct bGPDstroke *gps, + float mouse[2], + const int radius, + const float diff_mat[4][4]); + #ifdef __cplusplus } #endif diff --git a/source/blender/editors/interface/interface.c b/source/blender/editors/interface/interface.c index 165a53203f3..a0a3d0a3b85 100644 --- a/source/blender/editors/interface/interface.c +++ b/source/blender/editors/interface/interface.c @@ -4115,9 +4115,9 @@ static void ui_def_but_rna__panel_type(bContext *C, uiLayout *layout, void *but_ void ui_but_rna_menu_convert_to_panel_type(uiBut *but, const char *panel_type) { - BLI_assert(but->type == UI_BTYPE_MENU); - BLI_assert(but->menu_create_func == ui_def_but_rna__menu); - BLI_assert((void *)but->poin == but); + BLI_assert(ELEM(but->type, UI_BTYPE_MENU, UI_BTYPE_COLOR)); + // BLI_assert(but->menu_create_func == ui_def_but_rna__menu); + // BLI_assert((void *)but->poin == but); but->menu_create_func = ui_def_but_rna__panel_type; but->func_argN = BLI_strdup(panel_type); } diff --git a/source/blender/editors/interface/interface_eyedropper_gpencil_color.c b/source/blender/editors/interface/interface_eyedropper_gpencil_color.c index 790609a17d8..f9f5c745e94 100644 --- a/source/blender/editors/interface/interface_eyedropper_gpencil_color.c +++ b/source/blender/editors/interface/interface_eyedropper_gpencil_color.c @@ -39,6 +39,7 @@ #include "BKE_gpencil.h" #include "BKE_main.h" #include "BKE_material.h" +#include "BKE_paint.h" #include "BKE_report.h" #include "UI_interface.h" @@ -65,6 +66,8 @@ typedef struct EyedropperGPencil { struct ColorManagedDisplay *display; /** color under cursor RGB */ float color[3]; + /** Mode */ + int mode; } EyedropperGPencil; /* Helper: Draw status message while the user is running the operator */ @@ -89,6 +92,7 @@ static bool eyedropper_gpencil_init(bContext *C, wmOperator *op) display_device = scene->display_settings.display_device; eye->display = IMB_colormanagement_display_get_named(display_device); + eye->mode = RNA_enum_get(op->ptr, "mode"); return true; } @@ -101,31 +105,15 @@ static void eyedropper_gpencil_exit(bContext *C, wmOperator *op) MEM_SAFE_FREE(op->customdata); } -/* Set the material. */ -static void eyedropper_gpencil_color_set(bContext *C, const wmEvent *event, EyedropperGPencil *eye) +static void eyedropper_add_material( + bContext *C, float col_conv[4], const bool only_stroke, const bool only_fill, const bool both) { Main *bmain = CTX_data_main(C); Object *ob = CTX_data_active_object(C); Material *ma = NULL; - const bool only_stroke = ((!event->ctrl) && (!event->shift)); - const bool only_fill = ((!event->ctrl) && (event->shift)); - const bool both = ((event->ctrl) && (event->shift)); - - float col_conv[4]; bool found = false; - /* Convert from linear rgb space to display space because grease pencil colors are in display - * space, and this conversion is needed to undo the conversion to linear performed by - * eyedropper_color_sample_fl. */ - if (eye->display) { - copy_v3_v3(col_conv, eye->color); - IMB_colormanagement_scene_linear_to_display_v3(col_conv, eye->display); - } - else { - copy_v3_v3(col_conv, eye->color); - } - /* Look for a similar material in grease pencil slots. */ short *totcol = BKE_object_material_len_p(ob); for (short i = 0; i < *totcol; i++) { @@ -138,15 +126,15 @@ static void eyedropper_gpencil_color_set(bContext *C, const wmEvent *event, Eyed if (gp_style != NULL) { /* Check stroke color. */ bool found_stroke = compare_v3v3(gp_style->stroke_rgba, col_conv, 0.01f) && - (gp_style->flag & GP_STYLE_STROKE_SHOW); + (gp_style->flag & GP_MATERIAL_STROKE_SHOW); /* Check fill color. */ bool found_fill = compare_v3v3(gp_style->fill_rgba, col_conv, 0.01f) && - (gp_style->flag & GP_STYLE_FILL_SHOW); + (gp_style->flag & GP_MATERIAL_FILL_SHOW); - if ((only_stroke) && (found_stroke) && ((gp_style->flag & GP_STYLE_FILL_SHOW) == 0)) { + if ((only_stroke) && (found_stroke) && ((gp_style->flag & GP_MATERIAL_FILL_SHOW) == 0)) { found = true; } - else if ((only_fill) && (found_fill) && ((gp_style->flag & GP_STYLE_STROKE_SHOW) == 0)) { + else if ((only_fill) && (found_fill) && ((gp_style->flag & GP_MATERIAL_STROKE_SHOW) == 0)) { found = true; } else if ((both) && (found_stroke) && (found_fill)) { @@ -180,22 +168,22 @@ static void eyedropper_gpencil_color_set(bContext *C, const wmEvent *event, Eyed /* Only create Stroke (default option). */ if (only_stroke) { /* Stroke color. */ - gp_style_new->flag |= GP_STYLE_STROKE_SHOW; - gp_style_new->flag &= ~GP_STYLE_FILL_SHOW; + gp_style_new->flag |= GP_MATERIAL_STROKE_SHOW; + gp_style_new->flag &= ~GP_MATERIAL_FILL_SHOW; copy_v3_v3(gp_style_new->stroke_rgba, col_conv); zero_v4(gp_style_new->fill_rgba); } /* Fill Only. */ else if (only_fill) { /* Fill color. */ - gp_style_new->flag &= ~GP_STYLE_STROKE_SHOW; - gp_style_new->flag |= GP_STYLE_FILL_SHOW; + gp_style_new->flag &= ~GP_MATERIAL_STROKE_SHOW; + gp_style_new->flag |= GP_MATERIAL_FILL_SHOW; zero_v4(gp_style_new->stroke_rgba); copy_v3_v3(gp_style_new->fill_rgba, col_conv); } /* Stroke and Fill. */ else if (both) { - gp_style_new->flag |= GP_STYLE_STROKE_SHOW | GP_STYLE_FILL_SHOW; + gp_style_new->flag |= GP_MATERIAL_STROKE_SHOW | GP_MATERIAL_FILL_SHOW; copy_v3_v3(gp_style_new->stroke_rgba, col_conv); copy_v3_v3(gp_style_new->fill_rgba, col_conv); } @@ -203,6 +191,69 @@ static void eyedropper_gpencil_color_set(bContext *C, const wmEvent *event, Eyed ED_undo_push(C, "Add Grease Pencil Material"); } +/* Create a new palette color and palette if needed. */ +static void eyedropper_add_palette_color(bContext *C, float col_conv[4]) +{ + Main *bmain = CTX_data_main(C); + Scene *scene = CTX_data_scene(C); + ToolSettings *ts = scene->toolsettings; + GpPaint *gp_paint = ts->gp_paint; + GpVertexPaint *gp_vertexpaint = ts->gp_vertexpaint; + Paint *paint = &gp_paint->paint; + Paint *vertexpaint = &gp_vertexpaint->paint; + + /* Check for Palette in Draw and Vertex Paint Mode. */ + if (paint->palette == NULL) { + paint->palette = BKE_palette_add(bmain, "Grease Pencil"); + if (vertexpaint->palette == NULL) { + vertexpaint->palette = paint->palette; + } + } + /* Check if the color exist already. */ + Palette *palette = paint->palette; + for (PaletteColor *palcolor = palette->colors.first; palcolor; palcolor = palcolor->next) { + if (compare_v3v3(palcolor->rgb, col_conv, 0.01f)) { + return; + } + } + + /* Create Colors. */ + PaletteColor *palcol = BKE_palette_color_add(palette); + if (palcol) { + copy_v3_v3(palcol->rgb, col_conv); + } +} + +/* Set the material or the palette color. */ +static void eyedropper_gpencil_color_set(bContext *C, const wmEvent *event, EyedropperGPencil *eye) +{ + + const bool only_stroke = ((!event->ctrl) && (!event->shift)); + const bool only_fill = ((!event->ctrl) && (event->shift)); + const bool both = ((event->ctrl) && (event->shift)); + + float col_conv[4]; + + /* Convert from linear rgb space to display space because grease pencil colors are in display + * space, and this conversion is needed to undo the conversion to linear performed by + * eyedropper_color_sample_fl. */ + if (eye->display) { + copy_v3_v3(col_conv, eye->color); + IMB_colormanagement_scene_linear_to_display_v3(col_conv, eye->display); + } + else { + copy_v3_v3(col_conv, eye->color); + } + + /* Add material or Palette color*/ + if (eye->mode == 0) { + eyedropper_add_material(C, col_conv, only_stroke, only_fill, both); + } + else { + eyedropper_add_palette_color(C, col_conv); + } +} + /* Sample the color below cursor. */ static void eyedropper_gpencil_color_sample(bContext *C, EyedropperGPencil *eye, int mx, int my) { @@ -307,6 +358,12 @@ static bool eyedropper_gpencil_poll(bContext *C) void UI_OT_eyedropper_gpencil_color(wmOperatorType *ot) { + static const EnumPropertyItem items_mode[] = { + {0, "MATERIAL", 0, "Material", ""}, + {1, "PALETTE", 0, "Palette", ""}, + {0, NULL, 0, NULL, NULL}, + }; + /* identifiers */ ot->name = "Grease Pencil Eyedropper"; ot->idname = "UI_OT_eyedropper_gpencil_color"; @@ -321,4 +378,7 @@ void UI_OT_eyedropper_gpencil_color(wmOperatorType *ot) /* flags */ ot->flag = OPTYPE_UNDO | OPTYPE_BLOCKING; + + /* properties */ + ot->prop = RNA_def_enum(ot->srna, "mode", items_mode, 0, "Mode", ""); } diff --git a/source/blender/editors/interface/interface_handlers.c b/source/blender/editors/interface/interface_handlers.c index 6a9632f54bb..088a904ec78 100644 --- a/source/blender/editors/interface/interface_handlers.c +++ b/source/blender/editors/interface/interface_handlers.c @@ -3994,7 +3994,12 @@ static void ui_block_open_begin(bContext *C, uiBut *but, uiHandleButtonData *dat copy_v3_v3(data->vec, data->origvec); but->editvec = data->vec; - handlefunc = ui_block_func_COLOR; + if (ui_but_menu_draw_as_popover(but)) { + popoverfunc = but->menu_create_func; + } + else { + handlefunc = ui_block_func_COLOR; + } arg = but; break; @@ -4063,8 +4068,8 @@ int ui_but_menu_direction(uiBut *but) } /** - * Hack for #uiList #UI_BTYPE_LISTROW buttons to "give" events to overlaying #UI_BTYPE_TEXT buttons - * (Ctrl-Click rename feature & co). + * Hack for #uiList #UI_BTYPE_LISTROW buttons to "give" events to overlaying #UI_BTYPE_TEXT + * buttons (Ctrl-Click rename feature & co). */ static uiBut *ui_but_list_row_text_activate(bContext *C, uiBut *but, @@ -5553,14 +5558,13 @@ static int ui_do_but_BLOCK(bContext *C, uiBut *but, uiHandleButtonData *data, co button_activate_state(C, but, BUTTON_STATE_EXIT); ui_apply_but(C, but->block, but, data, true); - /* Button's state need to be changed to EXIT so moving mouse away from this mouse wouldn't - * lead to cancel changes made to this button, but changing state to EXIT also makes no - * button active for a while which leads to triggering operator - * when doing fast scrolling mouse wheel. - * using post activate stuff from button allows to make button be active again after - * checking for all all that mouse leave and cancel stuff, - * so quick scroll wouldn't be an issue anymore. - * Same goes for scrolling wheel in another direction below (sergey). + /* Button's state need to be changed to EXIT so moving mouse away from this mouse + * wouldn't lead to cancel changes made to this button, but changing state to EXIT also + * makes no button active for a while which leads to triggering operator when doing fast + * scrolling mouse wheel. using post activate stuff from button allows to make button be + * active again after checking for all all that mouse leave and cancel stuff, so quick + * scroll wouldn't be an issue anymore. Same goes for scrolling wheel in another + * direction below (sergey). */ data->postbut = but; data->posttype = BUTTON_ACTIVATE_OVER; @@ -5797,15 +5801,27 @@ static int ui_do_but_COLOR(bContext *C, uiBut *but, uiHandleButtonData *data, co } else { Scene *scene = CTX_data_scene(C); + bool updated = false; if (but->rnaprop && RNA_property_subtype(but->rnaprop) == PROP_COLOR_GAMMA) { RNA_property_float_get_array(&but->rnapoin, but->rnaprop, color); BKE_brush_color_set(scene, brush, color); + updated = true; } else if (but->rnaprop && RNA_property_subtype(but->rnaprop) == PROP_COLOR) { RNA_property_float_get_array(&but->rnapoin, but->rnaprop, color); IMB_colormanagement_scene_linear_to_srgb_v3(color); BKE_brush_color_set(scene, brush, color); + updated = true; + } + + if (updated) { + PointerRNA brush_ptr; + PropertyRNA *brush_color_prop; + + RNA_id_pointer_create(&brush->id, &brush_ptr); + brush_color_prop = RNA_struct_find_property(&brush_ptr, "color"); + RNA_property_update(C, &brush_ptr, brush_color_prop); } } @@ -9519,7 +9535,8 @@ static int ui_handle_menu_event(bContext *C, /* Closing sub-levels of pull-downs. * * The actual event is handled by the button under the cursor. - * This is done so we can right click on menu items even when they have sub-menus open. */ + * This is done so we can right click on menu items even when they have sub-menus open. + */ case RIGHTMOUSE: if (inside == false) { if (event->val == KM_PRESS && (block->flag & UI_BLOCK_LOOP)) { diff --git a/source/blender/editors/interface/interface_icons.c b/source/blender/editors/interface/interface_icons.c index c1018a67fb3..3e07023e52d 100644 --- a/source/blender/editors/interface/interface_icons.c +++ b/source/blender/editors/interface/interface_icons.c @@ -2050,7 +2050,10 @@ static int ui_id_brush_get_icon(const bContext *C, ID *id) } /* reset the icon */ - if ((ob != NULL) && (ob->mode & OB_MODE_PAINT_GPENCIL) && (br->gpencil_settings != NULL)) { + if ((ob != NULL) && + (ob->mode & (OB_MODE_PAINT_GPENCIL | OB_MODE_VERTEX_GPENCIL | OB_MODE_SCULPT_GPENCIL | + OB_MODE_WEIGHT_GPENCIL)) && + (br->gpencil_settings != NULL)) { switch (br->gpencil_settings->icon_id) { case GP_BRUSH_ICON_PENCIL: br->id.icon_id = ICON_GPBRUSH_PENCIL; @@ -2088,6 +2091,54 @@ static int ui_id_brush_get_icon(const bContext *C, ID *id) case GP_BRUSH_ICON_ERASE_STROKE: br->id.icon_id = ICON_GPBRUSH_ERASE_STROKE; break; + case GP_BRUSH_ICON_TINT: + br->id.icon_id = ICON_BRUSH_TEXDRAW; + break; + case GP_BRUSH_ICON_VERTEX_DRAW: + br->id.icon_id = ICON_BRUSH_MIX; + break; + case GP_BRUSH_ICON_VERTEX_BLUR: + br->id.icon_id = ICON_BRUSH_BLUR; + break; + case GP_BRUSH_ICON_VERTEX_AVERAGE: + br->id.icon_id = ICON_BRUSH_BLUR; + break; + case GP_BRUSH_ICON_VERTEX_SMEAR: + br->id.icon_id = ICON_BRUSH_BLUR; + break; + case GP_BRUSH_ICON_VERTEX_REPLACE: + br->id.icon_id = ICON_BRUSH_MIX; + break; + case GP_BRUSH_ICON_GPBRUSH_SMOOTH: + br->id.icon_id = ICON_GPBRUSH_SMOOTH; + break; + case GP_BRUSH_ICON_GPBRUSH_THICKNESS: + br->id.icon_id = ICON_GPBRUSH_THICKNESS; + break; + case GP_BRUSH_ICON_GPBRUSH_STRENGTH: + br->id.icon_id = ICON_GPBRUSH_STRENGTH; + break; + case GP_BRUSH_ICON_GPBRUSH_RANDOMIZE: + br->id.icon_id = ICON_GPBRUSH_RANDOMIZE; + break; + case GP_BRUSH_ICON_GPBRUSH_GRAB: + br->id.icon_id = ICON_GPBRUSH_GRAB; + break; + case GP_BRUSH_ICON_GPBRUSH_PUSH: + br->id.icon_id = ICON_GPBRUSH_PUSH; + break; + case GP_BRUSH_ICON_GPBRUSH_TWIST: + br->id.icon_id = ICON_GPBRUSH_TWIST; + break; + case GP_BRUSH_ICON_GPBRUSH_PINCH: + br->id.icon_id = ICON_GPBRUSH_PINCH; + break; + case GP_BRUSH_ICON_GPBRUSH_CLONE: + br->id.icon_id = ICON_GPBRUSH_CLONE; + break; + case GP_BRUSH_ICON_GPBRUSH_WEIGHT: + br->id.icon_id = ICON_GPBRUSH_WEIGHT; + break; default: br->id.icon_id = ICON_GPBRUSH_PEN; break; diff --git a/source/blender/editors/interface/interface_layout.c b/source/blender/editors/interface/interface_layout.c index 8e65b818314..dd002f4e77f 100644 --- a/source/blender/editors/interface/interface_layout.c +++ b/source/blender/editors/interface/interface_layout.c @@ -2325,7 +2325,7 @@ void uiItemFullR_with_popover(uiLayout *layout, uiItemFullR(layout, ptr, prop, index, value, flag, name, icon); but = but->next; while (but) { - if (but->rnaprop == prop && but->type == UI_BTYPE_MENU) { + if (but->rnaprop == prop && ELEM(but->type, UI_BTYPE_MENU, UI_BTYPE_COLOR)) { ui_but_rna_menu_convert_to_panel_type(but, panel_type); break; } diff --git a/source/blender/editors/interface/interface_templates.c b/source/blender/editors/interface/interface_templates.c index c80c5317735..b752a1a1429 100644 --- a/source/blender/editors/interface/interface_templates.c +++ b/source/blender/editors/interface/interface_templates.c @@ -5385,6 +5385,21 @@ void uiTemplateColorPicker(uiLayout *layout, } } +static void ui_template_palette_menu(bContext *UNUSED(C), uiLayout *layout, void *UNUSED(but_p)) +{ + uiLayout *row; + + uiItemL(layout, IFACE_("Sort by:"), ICON_NONE); + row = uiLayoutRow(layout, false); + uiItemEnumO_value(row, IFACE_("Hue"), ICON_NONE, "PALETTE_OT_sort", "type", 1); + row = uiLayoutRow(layout, false); + uiItemEnumO_value(row, IFACE_("Saturation"), ICON_NONE, "PALETTE_OT_sort", "type", 2); + row = uiLayoutRow(layout, false); + uiItemEnumO_value(row, IFACE_("Value"), ICON_NONE, "PALETTE_OT_sort", "type", 3); + row = uiLayoutRow(layout, false); + uiItemEnumO_value(row, IFACE_("Luminance"), ICON_NONE, "PALETTE_OT_sort", "type", 4); +} + void uiTemplatePalette(uiLayout *layout, PointerRNA *ptr, const char *propname, @@ -5396,6 +5411,8 @@ void uiTemplatePalette(uiLayout *layout, PaletteColor *color; uiBlock *block; uiLayout *col; + uiBut *but = NULL; + int row_cols = 0, col_id = 0; int cols_per_row = MAX2(uiLayoutGetWidth(layout) / UI_UNIT_X, 1); @@ -5437,6 +5454,37 @@ void uiTemplatePalette(uiLayout *layout, UI_UNIT_X, UI_UNIT_Y, NULL); + if (color) { + but = uiDefIconButO(block, + UI_BTYPE_BUT, + "PALETTE_OT_color_move", + WM_OP_INVOKE_DEFAULT, + ICON_TRIA_UP, + 0, + 0, + UI_UNIT_X, + UI_UNIT_Y, + NULL); + UI_but_operator_ptr_get(but); + RNA_enum_set(but->opptr, "type", -1); + + but = uiDefIconButO(block, + UI_BTYPE_BUT, + "PALETTE_OT_color_move", + WM_OP_INVOKE_DEFAULT, + ICON_TRIA_DOWN, + 0, + 0, + UI_UNIT_X, + UI_UNIT_Y, + NULL); + UI_but_operator_ptr_get(but); + RNA_enum_set(but->opptr, "type", 1); + + /* Menu. */ + uiDefIconMenuBut( + block, ui_template_palette_menu, NULL, ICON_SORTSIZE, 0, 0, UI_UNIT_X, UI_UNIT_Y, ""); + } col = uiLayoutColumn(layout, true); uiLayoutRow(col, true); diff --git a/source/blender/editors/object/object_add.c b/source/blender/editors/object/object_add.c index 06f532f99e3..17b6bfdb956 100644 --- a/source/blender/editors/object/object_add.c +++ b/source/blender/editors/object/object_add.c @@ -1537,7 +1537,7 @@ static int object_delete_exec(bContext *C, wmOperator *op) * Will also remove parent from grease pencil from other scenes, * even when use_global is false... */ for (bGPdata *gpd = bmain->gpencils.first; gpd; gpd = gpd->id.next) { - for (bGPDlayer *gpl = gpd->layers.first; gpl; gpl = gpl->next) { + LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd->layers) { if (gpl->parent != NULL) { if (gpl->parent == ob) { gpl->parent = NULL; @@ -2394,7 +2394,7 @@ static int convert_exec(bContext *C, wmOperator *op) * Nurbs Surface are not supported. */ ushort local_view_bits = (v3d && v3d->localvd) ? v3d->local_view_uuid : 0; - gpencil_ob = ED_gpencil_add_object(C, scene, ob->loc, local_view_bits); + gpencil_ob = ED_gpencil_add_object(C, ob->loc, local_view_bits); copy_v3_v3(gpencil_ob->rot, ob->rot); copy_v3_v3(gpencil_ob->scale, ob->scale); BKE_gpencil_convert_curve(bmain, scene, gpencil_ob, ob, false, false, true); diff --git a/source/blender/editors/object/object_edit.c b/source/blender/editors/object/object_edit.c index f9ac0474d44..def93db2537 100644 --- a/source/blender/editors/object/object_edit.c +++ b/source/blender/editors/object/object_edit.c @@ -1370,7 +1370,8 @@ static const EnumPropertyItem *object_mode_set_itemsf(bContext *C, OB_MODE_EDIT_GPENCIL, OB_MODE_PAINT_GPENCIL, OB_MODE_SCULPT_GPENCIL, - OB_MODE_WEIGHT_GPENCIL) && + OB_MODE_WEIGHT_GPENCIL, + OB_MODE_VERTEX_GPENCIL) && (ob->type == OB_GPENCIL)) || (input->value == OB_MODE_OBJECT)) { RNA_enum_item_add(&item, &totitem, input); diff --git a/source/blender/editors/object/object_gpencil_modifier.c b/source/blender/editors/object/object_gpencil_modifier.c index 9138e65dd2f..4543f1a19b3 100644 --- a/source/blender/editors/object/object_gpencil_modifier.c +++ b/source/blender/editors/object/object_gpencil_modifier.c @@ -91,6 +91,11 @@ GpencilModifierData *ED_object_gpencil_modifier_add( /* make sure modifier data has unique name */ BKE_gpencil_modifier_unique_name(&ob->greasepencil_modifiers, new_md); + /* Enable edit mode visible by default. */ + if (mti->flags & eGpencilModifierTypeFlag_SupportsEditmode) { + new_md->mode |= eGpencilModifierMode_Editmode; + } + bGPdata *gpd = ob->data; DEG_id_tag_update(&gpd->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY); @@ -362,7 +367,7 @@ void OBJECT_OT_gpencil_modifier_add(wmOperatorType *ot) PropertyRNA *prop; /* identifiers */ - ot->name = "Add Grease Pencil Modifier"; + ot->name = "Add Modifier"; ot->description = "Add a procedural operation/effect to the active grease pencil object"; ot->idname = "OBJECT_OT_gpencil_modifier_add"; diff --git a/source/blender/editors/object/object_modes.c b/source/blender/editors/object/object_modes.c index 80e7e6312aa..edc2f15813c 100644 --- a/source/blender/editors/object/object_modes.c +++ b/source/blender/editors/object/object_modes.c @@ -88,6 +88,9 @@ static const char *object_mode_op_string(eObjectMode mode) if (mode == OB_MODE_WEIGHT_GPENCIL) { return "GPENCIL_OT_weightmode_toggle"; } + if (mode == OB_MODE_VERTEX_GPENCIL) { + return "GPENCIL_OT_vertexmode_toggle"; + } return NULL; } @@ -129,7 +132,7 @@ bool ED_object_mode_compat_test(const Object *ob, eObjectMode mode) break; case OB_GPENCIL: if (mode & (OB_MODE_EDIT | OB_MODE_EDIT_GPENCIL | OB_MODE_PAINT_GPENCIL | - OB_MODE_SCULPT_GPENCIL | OB_MODE_WEIGHT_GPENCIL)) { + OB_MODE_SCULPT_GPENCIL | OB_MODE_WEIGHT_GPENCIL | OB_MODE_VERTEX_GPENCIL)) { return true; } break; diff --git a/source/blender/editors/object/object_transform.c b/source/blender/editors/object/object_transform.c index 039714ca3ec..3b1fb49c383 100644 --- a/source/blender/editors/object/object_transform.c +++ b/source/blender/editors/object/object_transform.c @@ -696,7 +696,7 @@ static int apply_objects_internal(bContext *C, /* Unsupported configuration */ bool has_unparented_layers = false; - for (bGPDlayer *gpl = gpd->layers.first; gpl; gpl = gpl->next) { + LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd->layers) { /* Parented layers aren't supported as we can't easily re-evaluate * the scene to sample parent movement */ if (gpl->parent == NULL) { @@ -1394,13 +1394,13 @@ static int object_origin_set_exec(bContext *C, wmOperator *op) /* recalculate all strokes * (all layers are considered without evaluating lock attributes) */ - for (bGPDlayer *gpl = gpd->layers.first; gpl; gpl = gpl->next) { + LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd->layers) { /* calculate difference matrix */ - ED_gpencil_parent_location(depsgraph, obact, gpd, gpl, diff_mat); + BKE_gpencil_parent_matrix_get(depsgraph, obact, gpl, diff_mat); /* undo matrix */ invert_m4_m4(inverse_diff_mat, diff_mat); - for (bGPDframe *gpf = gpl->frames.first; gpf; gpf = gpf->next) { - for (bGPDstroke *gps = gpf->strokes.first; gps; gps = gps->next) { + LISTBASE_FOREACH (bGPDframe *, gpf, &gpl->frames) { + LISTBASE_FOREACH (bGPDstroke *, gps, &gpf->strokes) { for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) { float mpt[3]; mul_v3_m4v3(mpt, inverse_diff_mat, &pt->x); diff --git a/source/blender/editors/screen/area.c b/source/blender/editors/screen/area.c index e72818479c4..444413a757f 100644 --- a/source/blender/editors/screen/area.c +++ b/source/blender/editors/screen/area.c @@ -1723,9 +1723,81 @@ static void ed_default_handlers( wm->defaultconf, "Grease Pencil Stroke Paint (Fill)", 0, 0); WM_event_add_keymap_handler(handlers, keymap_paint_fill); + wmKeyMap *keymap_paint_tint = WM_keymap_ensure( + wm->defaultconf, "Grease Pencil Stroke Paint (Tint)", 0, 0); + WM_event_add_keymap_handler(handlers, keymap_paint_tint); + wmKeyMap *keymap_sculpt = WM_keymap_ensure( wm->defaultconf, "Grease Pencil Stroke Sculpt Mode", 0, 0); WM_event_add_keymap_handler(handlers, keymap_sculpt); + + wmKeyMap *keymap_vertex = WM_keymap_ensure( + wm->defaultconf, "Grease Pencil Stroke Vertex Mode", 0, 0); + WM_event_add_keymap_handler(handlers, keymap_vertex); + + wmKeyMap *keymap_vertex_draw = WM_keymap_ensure( + wm->defaultconf, "Grease Pencil Stroke Vertex (Draw)", 0, 0); + WM_event_add_keymap_handler(handlers, keymap_vertex_draw); + + wmKeyMap *keymap_vertex_blur = WM_keymap_ensure( + wm->defaultconf, "Grease Pencil Stroke Vertex (Blur)", 0, 0); + WM_event_add_keymap_handler(handlers, keymap_vertex_blur); + + wmKeyMap *keymap_vertex_average = WM_keymap_ensure( + wm->defaultconf, "Grease Pencil Stroke Vertex (Average)", 0, 0); + WM_event_add_keymap_handler(handlers, keymap_vertex_average); + + wmKeyMap *keymap_vertex_smear = WM_keymap_ensure( + wm->defaultconf, "Grease Pencil Stroke Vertex (Smear)", 0, 0); + WM_event_add_keymap_handler(handlers, keymap_vertex_smear); + + wmKeyMap *keymap_vertex_replace = WM_keymap_ensure( + wm->defaultconf, "Grease Pencil Stroke Vertex (Replace)", 0, 0); + WM_event_add_keymap_handler(handlers, keymap_vertex_replace); + + wmKeyMap *keymap_sculpt_smooth = WM_keymap_ensure( + wm->defaultconf, "Grease Pencil Stroke Sculpt (Smooth)", 0, 0); + WM_event_add_keymap_handler(handlers, keymap_sculpt_smooth); + + wmKeyMap *keymap_sculpt_thickness = WM_keymap_ensure( + wm->defaultconf, "Grease Pencil Stroke Sculpt (Thickness)", 0, 0); + WM_event_add_keymap_handler(handlers, keymap_sculpt_thickness); + + wmKeyMap *keymap_sculpt_strength = WM_keymap_ensure( + wm->defaultconf, "Grease Pencil Stroke Sculpt (Strength)", 0, 0); + WM_event_add_keymap_handler(handlers, keymap_sculpt_strength); + + wmKeyMap *keymap_sculpt_grab = WM_keymap_ensure( + wm->defaultconf, "Grease Pencil Stroke Sculpt (Grab)", 0, 0); + WM_event_add_keymap_handler(handlers, keymap_sculpt_grab); + + wmKeyMap *keymap_sculpt_push = WM_keymap_ensure( + wm->defaultconf, "Grease Pencil Stroke Sculpt (Push)", 0, 0); + WM_event_add_keymap_handler(handlers, keymap_sculpt_push); + + wmKeyMap *keymap_sculpt_twist = WM_keymap_ensure( + wm->defaultconf, "Grease Pencil Stroke Sculpt (Twist)", 0, 0); + WM_event_add_keymap_handler(handlers, keymap_sculpt_twist); + + wmKeyMap *keymap_sculpt_pinch = WM_keymap_ensure( + wm->defaultconf, "Grease Pencil Stroke Sculpt (Pinch)", 0, 0); + WM_event_add_keymap_handler(handlers, keymap_sculpt_pinch); + + wmKeyMap *keymap_sculpt_randomize = WM_keymap_ensure( + wm->defaultconf, "Grease Pencil Stroke Sculpt (Randomize)", 0, 0); + WM_event_add_keymap_handler(handlers, keymap_sculpt_randomize); + + wmKeyMap *keymap_sculpt_clone = WM_keymap_ensure( + wm->defaultconf, "Grease Pencil Stroke Sculpt (Clone)", 0, 0); + WM_event_add_keymap_handler(handlers, keymap_sculpt_clone); + + wmKeyMap *keymap_weight = WM_keymap_ensure( + wm->defaultconf, "Grease Pencil Stroke Weight Mode", 0, 0); + WM_event_add_keymap_handler(handlers, keymap_weight); + + wmKeyMap *keymap_weight_draw = WM_keymap_ensure( + wm->defaultconf, "Grease Pencil Stroke Weight (Draw)", 0, 0); + WM_event_add_keymap_handler(handlers, keymap_weight_draw); } } diff --git a/source/blender/editors/screen/screen_context.c b/source/blender/editors/screen/screen_context.c index a840d199823..540a4c05247 100644 --- a/source/blender/editors/screen/screen_context.c +++ b/source/blender/editors/screen/screen_context.c @@ -555,7 +555,7 @@ int ed_screen_context(const bContext *C, const char *member, bContextDataResult bGPdata *gpd = ED_gpencil_data_get_active_direct(sa, obact); if (gpd) { - bGPDlayer *gpl = BKE_gpencil_layer_getactive(gpd); + bGPDlayer *gpl = BKE_gpencil_layer_active_get(gpd); if (gpl) { CTX_data_pointer_set(result, &gpd->id, &RNA_GPencilLayer, gpl); @@ -567,7 +567,7 @@ int ed_screen_context(const bContext *C, const char *member, bContextDataResult bGPdata *gpd = ED_annotation_data_get_active_direct((ID *)sc, sa, scene); if (gpd) { - bGPDlayer *gpl = BKE_gpencil_layer_getactive(gpd); + bGPDlayer *gpl = BKE_gpencil_layer_active_get(gpd); if (gpl) { CTX_data_pointer_set(result, &gpd->id, &RNA_GPencilLayer, gpl); @@ -579,7 +579,7 @@ int ed_screen_context(const bContext *C, const char *member, bContextDataResult bGPdata *gpd = ED_gpencil_data_get_active_direct(sa, obact); if (gpd) { - bGPDlayer *gpl = BKE_gpencil_layer_getactive(gpd); + bGPDlayer *gpl = BKE_gpencil_layer_active_get(gpd); if (gpl) { CTX_data_pointer_set(result, &gpd->id, &RNA_GPencilLayer, gpl->actframe); @@ -609,7 +609,7 @@ int ed_screen_context(const bContext *C, const char *member, bContextDataResult bGPDlayer *gpl; for (gpl = gpd->layers.first; gpl; gpl = gpl->next) { - if (gpencil_layer_is_editable(gpl)) { + if (BKE_gpencil_layer_is_editable(gpl)) { CTX_data_list_add(result, &gpd->id, &RNA_GPencilLayer, gpl); } } @@ -625,7 +625,7 @@ int ed_screen_context(const bContext *C, const char *member, bContextDataResult bGPDlayer *gpl; for (gpl = gpd->layers.first; gpl; gpl = gpl->next) { - if (gpencil_layer_is_editable(gpl) && (gpl->actframe)) { + if (BKE_gpencil_layer_is_editable(gpl) && (gpl->actframe)) { bGPDframe *gpf; bGPDstroke *gps; bGPDframe *init_gpf = gpl->actframe; diff --git a/source/blender/editors/sculpt_paint/paint_ops.c b/source/blender/editors/sculpt_paint/paint_ops.c index ee1cb09ae94..6fe1ba0b817 100644 --- a/source/blender/editors/sculpt_paint/paint_ops.c +++ b/source/blender/editors/sculpt_paint/paint_ops.c @@ -26,6 +26,9 @@ #include "BLI_math_vector.h" #include "BLI_string.h" +#include "IMB_imbuf_types.h" +#include "IMB_imbuf.h" + #include "DNA_customdata_types.h" #include "DNA_object_types.h" #include "DNA_scene_types.h" @@ -33,9 +36,11 @@ #include "BKE_brush.h" #include "BKE_context.h" +#include "BKE_image.h" #include "BKE_lib_id.h" #include "BKE_main.h" #include "BKE_paint.h" +#include "BKE_report.h" #include "ED_paint.h" #include "ED_screen.h" @@ -294,6 +299,315 @@ static void PALETTE_OT_color_delete(wmOperatorType *ot) ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; } +/* --- Extract Palette from Image. */ +static bool palette_extract_img_poll(bContext *C) +{ + SpaceLink *sl = CTX_wm_space_data(C); + if (sl->spacetype == SPACE_IMAGE) { + return true; + } + + return false; +} + +static int palette_extract_img_exec(bContext *C, wmOperator *op) +{ + const int threshold = RNA_int_get(op->ptr, "threshold"); + + Main *bmain = CTX_data_main(C); + bool done = false; + + SpaceImage *sima = CTX_wm_space_image(C); + Image *image = sima->image; + ImageUser iuser = sima->iuser; + void *lock; + ImBuf *ibuf; + GHash *color_table = BLI_ghash_int_new(__func__); + + ibuf = BKE_image_acquire_ibuf(image, &iuser, &lock); + + if (ibuf->rect) { + /* Extract all colors. */ + for (int row = 0; row < ibuf->y; row++) { + for (int col = 0; col < ibuf->x; col++) { + float color[4]; + IMB_sampleImageAtLocation(ibuf, (float)col, (float)row, false, color); + const float range = pow(10.0f, threshold); + color[0] = truncf(color[0] * range) / range; + color[1] = truncf(color[1] * range) / range; + color[2] = truncf(color[2] * range) / range; + + uint key = rgb_to_cpack(color[0], color[1], color[2]); + if (!BLI_ghash_haskey(color_table, POINTER_FROM_INT(key))) { + BLI_ghash_insert(color_table, POINTER_FROM_INT(key), POINTER_FROM_INT(key)); + } + } + } + + done = BKE_palette_from_hash(bmain, color_table, image->id.name + 2, false); + } + + /* Free memory. */ + BLI_ghash_free(color_table, NULL, NULL); + BKE_image_release_ibuf(image, ibuf, lock); + + if (done) { + BKE_reportf(op->reports, RPT_INFO, "Palette created"); + } + + return OPERATOR_FINISHED; +} + +static void PALETTE_OT_extract_from_image(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Extract Palette from Image"; + ot->idname = "PALETTE_OT_extract_from_image"; + ot->description = "Extract all colors used in Image and create a Palette"; + + /* api callbacks */ + ot->exec = palette_extract_img_exec; + ot->poll = palette_extract_img_poll; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + /* properties */ + RNA_def_int(ot->srna, "threshold", 1, 1, 4, "Threshold", "", 1, 4); +} + +/* Sort Palette color by Hue and Saturation. */ +static bool palette_sort_poll(bContext *C) +{ + Paint *paint = BKE_paint_get_active_from_context(C); + Palette *palette = paint->palette; + if (palette) { + return true; + } + + return false; +} + +static int palette_sort_exec(bContext *C, wmOperator *op) +{ + const int type = RNA_enum_get(op->ptr, "type"); + + Paint *paint = BKE_paint_get_active_from_context(C); + Palette *palette = paint->palette; + + if (palette == NULL) { + return OPERATOR_CANCELLED; + } + + tPaletteColorHSV *color_array = NULL; + tPaletteColorHSV *col_elm = NULL; + + const int totcol = BLI_listbase_count(&palette->colors); + + if (totcol > 0) { + color_array = MEM_calloc_arrayN(totcol, sizeof(tPaletteColorHSV), __func__); + /* Put all colors in an array. */ + int t = 0; + for (PaletteColor *color = palette->colors.first; color; color = color->next) { + float h, s, v; + rgb_to_hsv(color->rgb[0], color->rgb[1], color->rgb[2], &h, &s, &v); + col_elm = &color_array[t]; + copy_v3_v3(col_elm->rgb, color->rgb); + col_elm->value = color->value; + col_elm->h = h; + col_elm->s = s; + col_elm->v = v; + t++; + } + /* Sort */ + if (type == 1) { + BKE_palette_sort_hsv(color_array, totcol); + } + else if (type == 2) { + BKE_palette_sort_svh(color_array, totcol); + } + else if (type == 3) { + BKE_palette_sort_vhs(color_array, totcol); + } + else { + BKE_palette_sort_luminance(color_array, totcol); + } + + /* Clear old color swatches. */ + PaletteColor *color_next = NULL; + for (PaletteColor *color = palette->colors.first; color; color = color_next) { + color_next = color->next; + BKE_palette_color_remove(palette, color); + } + + /* Recreate swatches sorted. */ + for (int i = 0; i < totcol; i++) { + col_elm = &color_array[i]; + PaletteColor *palcol = BKE_palette_color_add(palette); + if (palcol) { + copy_v3_v3(palcol->rgb, col_elm->rgb); + } + } + } + + /* Free memory. */ + if (totcol > 0) { + MEM_SAFE_FREE(color_array); + } + + WM_event_add_notifier(C, NC_BRUSH | NA_EDITED, NULL); + + return OPERATOR_FINISHED; +} + +static void PALETTE_OT_sort(wmOperatorType *ot) +{ + static const EnumPropertyItem sort_type[] = { + {1, "HSV", 0, "Hue, Saturation, Value", ""}, + {2, "SVH", 0, "Saturation, Value, Hue", ""}, + {3, "VHS", 0, "Value, Hue, Saturation", ""}, + {4, "LUMINANCE", 0, "Luminance", ""}, + {0, NULL, 0, NULL, NULL}, + }; + + /* identifiers */ + ot->name = "Sort Palette"; + ot->idname = "PALETTE_OT_sort"; + ot->description = "Sort Palette Colors"; + + /* api callbacks */ + ot->exec = palette_sort_exec; + ot->poll = palette_sort_poll; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + ot->prop = RNA_def_enum(ot->srna, "type", sort_type, 1, "Type", ""); +} + +/* Move colors in palette. */ +static int palette_color_move_exec(bContext *C, wmOperator *op) +{ + Paint *paint = BKE_paint_get_active_from_context(C); + Palette *palette = paint->palette; + PaletteColor *palcolor = BLI_findlink(&palette->colors, palette->active_color); + + if (palcolor == NULL) { + return OPERATOR_CANCELLED; + } + + const int direction = RNA_enum_get(op->ptr, "type"); + + BLI_assert(ELEM(direction, -1, 0, 1)); /* we use value below */ + if (BLI_listbase_link_move(&palette->colors, palcolor, direction)) { + palette->active_color += direction; + WM_event_add_notifier(C, NC_BRUSH | NA_EDITED, NULL); + } + + return OPERATOR_FINISHED; +} + +static void PALETTE_OT_color_move(wmOperatorType *ot) +{ + static const EnumPropertyItem slot_move[] = { + {-1, "UP", 0, "Up", ""}, + {1, "DOWN", 0, "Down", ""}, + {0, NULL, 0, NULL, NULL}, + }; + + /* identifiers */ + ot->name = "Move Palette Color"; + ot->idname = "PALETTE_OT_color_move"; + ot->description = "Move the active Color up/down in the list"; + + /* api callbacks */ + ot->exec = palette_color_move_exec; + ot->poll = palette_sort_poll; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + ot->prop = RNA_def_enum(ot->srna, "type", slot_move, 0, "Type", ""); +} + +/* Join Palette swatches. */ +static bool palette_join_poll(bContext *C) +{ + Paint *paint = BKE_paint_get_active_from_context(C); + Palette *palette = paint->palette; + if (palette) { + return true; + } + + return false; +} + +static int palette_join_exec(bContext *C, wmOperator *op) +{ + Main *bmain = CTX_data_main(C); + Paint *paint = BKE_paint_get_active_from_context(C); + Palette *palette = paint->palette; + Palette *palette_join = NULL; + bool done = false; + + char name[MAX_ID_NAME - 2]; + RNA_string_get(op->ptr, "palette", name); + + if ((palette == NULL) || (name[0] == '\0')) { + return OPERATOR_CANCELLED; + } + + palette_join = (Palette *)BKE_libblock_find_name(bmain, ID_PAL, name); + if (palette_join == NULL) { + return OPERATOR_CANCELLED; + } + + const int totcol = BLI_listbase_count(&palette_join->colors); + + if (totcol > 0) { + for (PaletteColor *color = palette_join->colors.first; color; color = color->next) { + PaletteColor *palcol = BKE_palette_color_add(palette); + if (palcol) { + copy_v3_v3(palcol->rgb, color->rgb); + palcol->value = color->value; + done = true; + } + } + } + + if (done) { + /* Clear old color swatches. */ + PaletteColor *color_next = NULL; + for (PaletteColor *color = palette_join->colors.first; color; color = color_next) { + color_next = color->next; + BKE_palette_color_remove(palette_join, color); + } + + /* Notifier. */ + WM_event_add_notifier(C, NC_BRUSH | NA_EDITED, NULL); + } + + return OPERATOR_FINISHED; +} + +static void PALETTE_OT_join(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Join Palette Swatches"; + ot->idname = "PALETTE_OT_join"; + ot->description = "Join Palette Swatches"; + + /* api callbacks */ + ot->exec = palette_join_exec; + ot->poll = palette_join_poll; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + /* properties */ + RNA_def_string(ot->srna, "palette", NULL, MAX_ID_NAME - 2, "Palette", "Name of the Palette"); +} + static int brush_reset_exec(bContext *C, wmOperator *UNUSED(op)) { Paint *paint = BKE_paint_get_active_from_context(C); @@ -456,6 +770,9 @@ static const ePaintMode brush_select_paint_modes[] = { PAINT_MODE_WEIGHT, PAINT_MODE_TEXTURE_3D, PAINT_MODE_GPENCIL, + PAINT_MODE_VERTEX_GPENCIL, + PAINT_MODE_SCULPT_GPENCIL, + PAINT_MODE_WEIGHT_GPENCIL, }; static int brush_select_exec(bContext *C, wmOperator *op) @@ -965,6 +1282,11 @@ void ED_operatortypes_paint(void) WM_operatortype_append(PALETTE_OT_color_add); WM_operatortype_append(PALETTE_OT_color_delete); + WM_operatortype_append(PALETTE_OT_extract_from_image); + WM_operatortype_append(PALETTE_OT_sort); + WM_operatortype_append(PALETTE_OT_color_move); + WM_operatortype_append(PALETTE_OT_join); + /* paint curve */ WM_operatortype_append(PAINTCURVE_OT_new); WM_operatortype_append(PAINTCURVE_OT_add_point); diff --git a/source/blender/editors/space_action/action_edit.c b/source/blender/editors/space_action/action_edit.c index e01986181bc..e17bf00106a 100644 --- a/source/blender/editors/space_action/action_edit.c +++ b/source/blender/editors/space_action/action_edit.c @@ -803,7 +803,7 @@ static void insert_gpencil_keys(bAnimContext *ac, short mode) /* insert gp frames */ for (ale = anim_data.first; ale; ale = ale->next) { bGPDlayer *gpl = (bGPDlayer *)ale->data; - BKE_gpencil_layer_getframe(gpl, CFRA, add_frame_mode); + BKE_gpencil_layer_frame_get(gpl, CFRA, add_frame_mode); } ANIM_animdata_update(ac, &anim_data); diff --git a/source/blender/editors/space_action/action_select.c b/source/blender/editors/space_action/action_select.c index 458c8690e3c..4322e4277ad 100644 --- a/source/blender/editors/space_action/action_select.c +++ b/source/blender/editors/space_action/action_select.c @@ -1759,8 +1759,8 @@ static int mouse_action_keys(bAnimContext *ac, gpl->flag |= GP_LAYER_SELECT; /* Update other layer status. */ - if (BKE_gpencil_layer_getactive(gpd) != gpl) { - BKE_gpencil_layer_setactive(gpd, gpl); + if (BKE_gpencil_layer_active_get(gpd) != gpl) { + BKE_gpencil_layer_active_set(gpd, gpl); BKE_gpencil_layer_autolock_set(gpd, false); WM_main_add_notifier(NC_GPENCIL | ND_DATA | NA_EDITED, NULL); } diff --git a/source/blender/editors/space_outliner/outliner_draw.c b/source/blender/editors/space_outliner/outliner_draw.c index 1c26dc8f834..3e88a7a7b88 100644 --- a/source/blender/editors/space_outliner/outliner_draw.c +++ b/source/blender/editors/space_outliner/outliner_draw.c @@ -817,7 +817,7 @@ static void namebutton_cb(bContext *C, void *tsep, char *oldname) bGPDlayer *gpl = te->directdata; /* always make layer active */ - BKE_gpencil_layer_setactive(gpd, gpl); + BKE_gpencil_layer_active_set(gpd, gpl); // XXX: name needs translation stuff BLI_uniquename( @@ -2252,6 +2252,9 @@ TreeElementIcon tree_element_get_icon(TreeStoreElem *tselem, TreeElement *te) case eGpencilModifierType_Armature: data.icon = ICON_MOD_ARMATURE; break; + case eGpencilModifierType_Vertexcolor: + data.icon = ICON_MOD_NORMALEDIT; + break; /* Default */ default: diff --git a/source/blender/editors/space_outliner/outliner_select.c b/source/blender/editors/space_outliner/outliner_select.c index 6d449e98c7e..8ddacfc84c2 100644 --- a/source/blender/editors/space_outliner/outliner_select.c +++ b/source/blender/editors/space_outliner/outliner_select.c @@ -561,7 +561,7 @@ static eOLDrawState tree_element_active_gplayer(bContext *C, */ if (set != OL_SETSEL_NONE) { if (gpl) { - BKE_gpencil_layer_setactive(gpd, gpl); + BKE_gpencil_layer_active_set(gpd, gpl); DEG_id_tag_update(&gpd->id, ID_RECALC_GEOMETRY); WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_SELECTED, gpd); } diff --git a/source/blender/editors/space_view3d/space_view3d.c b/source/blender/editors/space_view3d/space_view3d.c index a8514967748..8c884783913 100644 --- a/source/blender/editors/space_view3d/space_view3d.c +++ b/source/blender/editors/space_view3d/space_view3d.c @@ -1215,6 +1215,9 @@ void ED_view3d_buttons_region_layout_ex(const bContext *C, case CTX_MODE_WEIGHT_GPENCIL: ARRAY_SET_ITEMS(contexts, ".greasepencil_weight"); break; + case CTX_MODE_VERTEX_GPENCIL: + ARRAY_SET_ITEMS(contexts, ".greasepencil_vertex"); + break; default: break; } @@ -1232,6 +1235,9 @@ void ED_view3d_buttons_region_layout_ex(const bContext *C, case CTX_MODE_EDIT_GPENCIL: ARRAY_SET_ITEMS(contexts, ".greasepencil_edit"); break; + case CTX_MODE_VERTEX_GPENCIL: + ARRAY_SET_ITEMS(contexts, ".greasepencil_vertex"); + break; default: break; } diff --git a/source/blender/editors/space_view3d/view3d_gizmo_ruler.c b/source/blender/editors/space_view3d/view3d_gizmo_ruler.c index 6fbae1d8cb1..83539900f36 100644 --- a/source/blender/editors/space_view3d/view3d_gizmo_ruler.c +++ b/source/blender/editors/space_view3d/view3d_gizmo_ruler.c @@ -409,7 +409,7 @@ static bool view3d_ruler_item_mousemove(struct Depsgraph *depsgraph, /* Helper: Find the layer created as ruler. */ static bGPDlayer *view3d_ruler_layer_get(bGPdata *gpd) { - for (bGPDlayer *gpl = gpd->layers.first; gpl; gpl = gpl->next) { + LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd->layers) { if (gpl->flag & GP_LAYER_IS_RULER) { return gpl; } @@ -445,7 +445,7 @@ static bool view3d_ruler_to_gpencil(bContext *C, wmGizmoGroup *gzgroup) gpl->flag |= GP_LAYER_HIDE | GP_LAYER_IS_RULER; } - gpf = BKE_gpencil_layer_getframe(gpl, CFRA, GP_GETFRAME_ADD_NEW); + gpf = BKE_gpencil_layer_frame_get(gpl, CFRA, GP_GETFRAME_ADD_NEW); BKE_gpencil_free_strokes(gpf); for (ruler_item = gzgroup->gizmos.first; ruler_item; @@ -477,9 +477,10 @@ static bool view3d_ruler_to_gpencil(bContext *C, wmGizmoGroup *gzgroup) } gps->flag = GP_STROKE_3DSPACE; gps->thickness = 3; - gps->gradient_f = 1.0f; - gps->gradient_s[0] = 1.0f; - gps->gradient_s[1] = 1.0f; + gps->hardeness = 1.0f; + gps->fill_opacity_fac = 1.0f; + copy_v2_fl(gps->aspect_ratio, 1.0f); + gps->uv_scale = 1.0f; BLI_addtail(&gpf->strokes, gps); changed = true; @@ -498,7 +499,7 @@ static bool view3d_ruler_from_gpencil(const bContext *C, wmGizmoGroup *gzgroup) gpl = view3d_ruler_layer_get(scene->gpd); if (gpl) { bGPDframe *gpf; - gpf = BKE_gpencil_layer_getframe(gpl, CFRA, GP_GETFRAME_USE_PREV); + gpf = BKE_gpencil_layer_frame_get(gpl, CFRA, GP_GETFRAME_USE_PREV); if (gpf) { bGPDstroke *gps; for (gps = gpf->strokes.first; gps; gps = gps->next) { diff --git a/source/blender/editors/space_view3d/view3d_select.c b/source/blender/editors/space_view3d/view3d_select.c index 7649bd45a1a..bd92193206f 100644 --- a/source/blender/editors/space_view3d/view3d_select.c +++ b/source/blender/editors/space_view3d/view3d_select.c @@ -2265,7 +2265,8 @@ static bool ed_object_select_pick(bContext *C, if (ELEM(basact->object->mode, OB_MODE_PAINT_GPENCIL, OB_MODE_SCULPT_GPENCIL, - OB_MODE_WEIGHT_GPENCIL)) { + OB_MODE_WEIGHT_GPENCIL, + OB_MODE_VERTEX_GPENCIL)) { ED_gpencil_toggle_brush_cursor(C, true, NULL); } else { diff --git a/source/blender/editors/transform/transform_convert.c b/source/blender/editors/transform/transform_convert.c index 009164057ce..f93a3ec260b 100644 --- a/source/blender/editors/transform/transform_convert.c +++ b/source/blender/editors/transform/transform_convert.c @@ -887,7 +887,7 @@ static void posttrans_gpd_clean(bGPdata *gpd) for (gpf = gpl->frames.first; gpf; gpf = gpfn) { gpfn = gpf->next; if (gpfn && gpf->framenum == gpfn->framenum) { - BKE_gpencil_layer_delframe(gpl, gpf); + BKE_gpencil_layer_frame_delete(gpl, gpf); } } } @@ -2740,9 +2740,11 @@ void createTransData(bContext *C, TransInfo *t) has_transform_context = false; } } - else if ((ob) && - (ELEM( - ob->mode, OB_MODE_PAINT_GPENCIL, OB_MODE_SCULPT_GPENCIL, OB_MODE_WEIGHT_GPENCIL))) { + else if ((ob) && (ELEM(ob->mode, + OB_MODE_PAINT_GPENCIL, + OB_MODE_SCULPT_GPENCIL, + OB_MODE_WEIGHT_GPENCIL, + OB_MODE_VERTEX_GPENCIL))) { /* In grease pencil all transformations must be canceled if not Object or Edit. */ has_transform_context = false; } diff --git a/source/blender/editors/transform/transform_convert_gpencil.c b/source/blender/editors/transform/transform_convert_gpencil.c index c61961e46d1..17e69ff38b8 100644 --- a/source/blender/editors/transform/transform_convert_gpencil.c +++ b/source/blender/editors/transform/transform_convert_gpencil.c @@ -83,6 +83,8 @@ void createTransGPencil(bContext *C, TransInfo *t) const bool is_prop_edit = (t->flag & T_PROP_EDIT) != 0; const bool is_prop_edit_connected = (t->flag & T_PROP_CONNECTED) != 0; + const bool is_scale_thickness = ((t->mode == TFM_GPENCIL_SHRINKFATTEN) || + (ts->gp_sculpt.flag & GP_SCULPT_SETT_FLAG_SCALE_THICKNESS)); TransDataContainer *tc = TRANS_DATA_CONTAINER_FIRST_SINGLE(t); @@ -108,7 +110,7 @@ void createTransGPencil(bContext *C, TransInfo *t) */ for (gpl = gpd->layers.first; gpl; gpl = gpl->next) { /* only editable and visible layers are considered */ - if (gpencil_layer_is_editable(gpl) && (gpl->actframe != NULL)) { + if (BKE_gpencil_layer_is_editable(gpl) && (gpl->actframe != NULL)) { bGPDframe *gpf; bGPDstroke *gps; bGPDframe *init_gpf = gpl->actframe; @@ -180,7 +182,7 @@ void createTransGPencil(bContext *C, TransInfo *t) /* Second Pass: Build transdata array */ for (gpl = gpd->layers.first; gpl; gpl = gpl->next) { /* only editable and visible layers are considered */ - if (gpencil_layer_is_editable(gpl) && (gpl->actframe != NULL)) { + if (BKE_gpencil_layer_is_editable(gpl) && (gpl->actframe != NULL)) { const int cfra = (gpl->flag & GP_LAYER_FRAMELOCK) ? gpl->actframe->framenum : cfra_scene; bGPDframe *gpf = gpl->actframe; bGPDstroke *gps; @@ -196,11 +198,11 @@ void createTransGPencil(bContext *C, TransInfo *t) int f_end = 0; if (use_multiframe_falloff) { - BKE_gpencil_get_range_selected(gpl, &f_init, &f_end); + BKE_gpencil_frame_range_selected(gpl, &f_init, &f_end); } /* calculate difference matrix */ - ED_gpencil_parent_location(depsgraph, obact, gpd, gpl, diff_mat); + BKE_gpencil_parent_matrix_get(depsgraph, obact, gpl, diff_mat); /* undo matrix */ invert_m4_m4(inverse_diff_mat, diff_mat); @@ -312,9 +314,9 @@ void createTransGPencil(bContext *C, TransInfo *t) } /* for other transform modes (e.g. shrink-fatten), need to additional data - * but never for scale or mirror + * but never for mirror */ - if ((t->mode != TFM_RESIZE) && (t->mode != TFM_MIRROR)) { + if ((t->mode != TFM_MIRROR) && (is_scale_thickness)) { if (t->mode != TFM_GPENCIL_OPACITY) { td->val = &pt->pressure; td->ival = pt->pressure; diff --git a/source/blender/editors/transform/transform_generics.c b/source/blender/editors/transform/transform_generics.c index 3fe1b99adfb..bb4d50fcf54 100644 --- a/source/blender/editors/transform/transform_generics.c +++ b/source/blender/editors/transform/transform_generics.c @@ -1149,12 +1149,15 @@ static void recalcData_sequencer(TransInfo *t) static void recalcData_gpencil_strokes(TransInfo *t) { TransDataContainer *tc = TRANS_DATA_CONTAINER_FIRST_SINGLE(t); + bGPDstroke *gps_prev = NULL; TransData *td = tc->data; for (int i = 0; i < tc->data_len; i++, td++) { bGPDstroke *gps = td->extra; - if (gps != NULL) { - gps->flag |= GP_STROKE_RECALC_GEOMETRY; + if ((gps != NULL) && (gps != gps_prev)) { + /* Calc geometry data. */ + BKE_gpencil_stroke_geometry_update(gps); + gps_prev = gps; } } } diff --git a/source/blender/editors/transform/transform_gizmo_3d.c b/source/blender/editors/transform/transform_gizmo_3d.c index 02767156ef4..0548cc4e503 100644 --- a/source/blender/editors/transform/transform_gizmo_3d.c +++ b/source/blender/editors/transform/transform_gizmo_3d.c @@ -792,13 +792,13 @@ int ED_transform_calc_gizmo_stats(const bContext *C, if (is_gp_edit) { float diff_mat[4][4]; const bool use_mat_local = true; - for (bGPDlayer *gpl = gpd->layers.first; gpl; gpl = gpl->next) { + LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd->layers) { /* only editable and visible layers are considered */ - if (gpencil_layer_is_editable(gpl) && (gpl->actframe != NULL)) { + if (BKE_gpencil_layer_is_editable(gpl) && (gpl->actframe != NULL)) { /* calculate difference matrix */ - ED_gpencil_parent_location(depsgraph, ob, gpd, gpl, diff_mat); + BKE_gpencil_parent_matrix_get(depsgraph, ob, gpl, diff_mat); for (bGPDstroke *gps = gpl->actframe->strokes.first; gps; gps = gps->next) { /* skip strokes that are invalid for current view */ diff --git a/source/blender/editors/undo/ed_undo.c b/source/blender/editors/undo/ed_undo.c index 5b5d1338637..cb4f4dfa46c 100644 --- a/source/blender/editors/undo/ed_undo.c +++ b/source/blender/editors/undo/ed_undo.c @@ -202,7 +202,8 @@ static int ed_undo_step_impl( if (ELEM(obact->mode, OB_MODE_PAINT_GPENCIL, OB_MODE_SCULPT_GPENCIL, - OB_MODE_WEIGHT_GPENCIL)) { + OB_MODE_WEIGHT_GPENCIL, + OB_MODE_VERTEX_GPENCIL)) { ED_gpencil_toggle_brush_cursor(C, true, NULL); } else { -- cgit v1.2.3