diff options
author | Antonio Vazquez <blendergit@gmail.com> | 2016-08-04 00:31:48 +0300 |
---|---|---|
committer | Julian Eisel <eiseljulian@gmail.com> | 2016-08-04 00:39:36 +0300 |
commit | eaea4ea51f665945e44ff2ffa534a594e9fb1938 (patch) | |
tree | 0791ec0d78a4506eebf3bcf3800a5b1423143494 /source/blender/editors/gpencil | |
parent | 9d4ea8427770e8ca68149fc7f7760fe2273e5ce3 (diff) |
Grease Pencil v2 Branch
Improve current Grease Pencil in order to get a better 2D animation tool.
More info in WIKI pages: https://wiki.blender.org/index.php/User:Antoniov
Reviewed By: Severin, aligorith, campbellbarton
Patch by @antoniov, with edits by @Severin.
Differential Revision: https://developer.blender.org/D2115
Diffstat (limited to 'source/blender/editors/gpencil')
-rw-r--r-- | source/blender/editors/gpencil/drawgpencil.c | 561 | ||||
-rw-r--r-- | source/blender/editors/gpencil/gpencil_brush.c | 399 | ||||
-rw-r--r-- | source/blender/editors/gpencil/gpencil_convert.c | 45 | ||||
-rw-r--r-- | source/blender/editors/gpencil/gpencil_data.c | 1799 | ||||
-rw-r--r-- | source/blender/editors/gpencil/gpencil_edit.c | 327 | ||||
-rw-r--r-- | source/blender/editors/gpencil/gpencil_intern.h | 120 | ||||
-rw-r--r-- | source/blender/editors/gpencil/gpencil_ops.c | 74 | ||||
-rw-r--r-- | source/blender/editors/gpencil/gpencil_paint.c | 434 | ||||
-rw-r--r-- | source/blender/editors/gpencil/gpencil_select.c | 119 | ||||
-rw-r--r-- | source/blender/editors/gpencil/gpencil_undo.c | 4 | ||||
-rw-r--r-- | source/blender/editors/gpencil/gpencil_utils.c | 373 |
11 files changed, 3786 insertions, 469 deletions
diff --git a/source/blender/editors/gpencil/drawgpencil.c b/source/blender/editors/gpencil/drawgpencil.c index 79a2c494239..bd09616243b 100644 --- a/source/blender/editors/gpencil/drawgpencil.c +++ b/source/blender/editors/gpencil/drawgpencil.c @@ -18,7 +18,7 @@ * The Original Code is Copyright (C) 2008, Blender Foundation * This is a new part of Blender * - * Contributor(s): Joshua Leung + * Contributor(s): Joshua Leung, Antonio Vazquez * * ***** END GPL LICENSE BLOCK ***** */ @@ -52,6 +52,7 @@ #include "DNA_space_types.h" #include "DNA_view3d_types.h" #include "DNA_userdef_types.h" +#include "DNA_object_types.h" #include "BKE_context.h" #include "BKE_global.h" @@ -94,9 +95,32 @@ typedef enum eDrawStrokeFlags { #define GP_DRAWTHICKNESS_SPECIAL 3 /* ----- Tool Buffer Drawing ------ */ +/* helper function to set color of buffer point */ +static void gp_set_tpoint_color(tGPspoint *pt, float ink[4]) +{ + float alpha = ink[3] * pt->strength; + CLAMP(alpha, GPENCIL_STRENGTH_MIN, 1.0f); + glColor4f(ink[0], ink[1], ink[2], alpha); +} + +/* helper function to set color of point */ +static void gp_set_point_color(bGPDspoint *pt, float ink[4]) +{ + float alpha = ink[3] * pt->strength; + CLAMP(alpha, GPENCIL_STRENGTH_MIN, 1.0f); + glColor4f(ink[0], ink[1], ink[2], alpha); +} + +/* helper function to set color and point */ +static void gp_set_color_and_tpoint(tGPspoint *pt, float ink[4]) +{ + gp_set_tpoint_color(pt, ink); + glVertex2iv(&pt->x); +} /* draw stroke defined in buffer (simple ogl lines/points for now, as dotted lines) */ -static void gp_draw_stroke_buffer(tGPspoint *points, int totpoints, short thickness, short dflag, short sflag) +static void gp_draw_stroke_buffer(tGPspoint *points, int totpoints, short thickness, + short dflag, short sflag, float ink[4]) { tGPspoint *pt; int i; @@ -113,7 +137,8 @@ static void gp_draw_stroke_buffer(tGPspoint *points, int totpoints, short thickn /* if drawing a single point, draw it larger */ glPointSize((float)(thickness + 2) * points->pressure); glBegin(GL_POINTS); - glVertex2iv(&points->x); + + gp_set_color_and_tpoint(points, ink); glEnd(); } else if (sflag & GP_STROKE_ERASER) { @@ -138,15 +163,18 @@ static void gp_draw_stroke_buffer(tGPspoint *points, int totpoints, short thickn glBegin(GL_LINE_STRIP); /* need to roll-back one point to ensure that there are no gaps in the stroke */ - if (i != 0) glVertex2iv(&(pt - 1)->x); + if (i != 0) { + gp_set_color_and_tpoint((pt - 1), ink); + } /* now the point we want... */ - glVertex2iv(&pt->x); + gp_set_color_and_tpoint(pt, ink); oldpressure = pt->pressure; } - else - glVertex2iv(&pt->x); + else { + gp_set_color_and_tpoint(pt, ink); + } } glEnd(); @@ -155,37 +183,35 @@ static void gp_draw_stroke_buffer(tGPspoint *points, int totpoints, short thickn } /* --------- 2D Stroke Drawing Helpers --------- */ - -/* helper function to calculate x-y drawing coordinates for 2D points */ -static void gp_calc_2d_stroke_xy(bGPDspoint *pt, short sflag, int offsx, int offsy, int winx, int winy, float r_co[2]) +/* change in parameter list */ +static void gp_calc_2d_stroke_fxy(float pt[3], short sflag, int offsx, int offsy, int winx, int winy, float r_co[2]) { if (sflag & GP_STROKE_2DSPACE) { - r_co[0] = pt->x; - r_co[1] = pt->y; + r_co[0] = pt[0]; + r_co[1] = pt[1]; } else if (sflag & GP_STROKE_2DIMAGE) { - const float x = (float)((pt->x * winx) + offsx); - const float y = (float)((pt->y * winy) + offsy); - + const float x = (float)((pt[0] * winx) + offsx); + const float y = (float)((pt[1] * winy) + offsy); + r_co[0] = x; r_co[1] = y; } else { - const float x = (float)(pt->x / 100 * winx) + offsx; - const float y = (float)(pt->y / 100 * winy) + offsy; - + const float x = (float)(pt[0] / 100 * winx) + offsx; + const float y = (float)(pt[1] / 100 * winy) + offsy; + r_co[0] = x; r_co[1] = y; } } - /* ----------- Volumetric Strokes --------------- */ /* draw a 2D buffer stroke in "volumetric" style * NOTE: the stroke buffer doesn't have any coordinate offsets/transforms */ static void gp_draw_stroke_volumetric_buffer(tGPspoint *points, int totpoints, short thickness, - short dflag, short UNUSED(sflag)) + short dflag, short UNUSED(sflag), float ink[4]) { GLUquadricObj *qobj = gluNewQuadric(); float modelview[4][4]; @@ -216,6 +242,7 @@ static void gp_draw_stroke_volumetric_buffer(tGPspoint *points, int totpoints, s glLoadMatrixf((float *)modelview); /* draw the disk using the current state... */ + gp_set_tpoint_color(pt, ink); gluDisk(qobj, 0.0, pt->pressure * thickness, 32, 1); @@ -229,7 +256,8 @@ static void gp_draw_stroke_volumetric_buffer(tGPspoint *points, int totpoints, s /* draw a 2D strokes in "volumetric" style */ static void gp_draw_stroke_volumetric_2d(bGPDspoint *points, int totpoints, short thickness, short dflag, short sflag, - int offsx, int offsy, int winx, int winy) + int offsx, int offsy, int winx, int winy, + float diff_mat[4][4], float ink[4]) { GLUquadricObj *qobj = gluNewQuadric(); float modelview[4][4]; @@ -238,7 +266,7 @@ static void gp_draw_stroke_volumetric_2d(bGPDspoint *points, int totpoints, shor bGPDspoint *pt; int i; - + float fpt[3]; /* HACK: We need a scale factor for the drawing in the image editor, * which seems to use 1 unit as it's maximum size, whereas everything @@ -256,10 +284,14 @@ static void gp_draw_stroke_volumetric_2d(bGPDspoint *points, int totpoints, shor glPushMatrix(); for (i = 0, pt = points; i < totpoints; i++, pt++) { + /* color of point */ + gp_set_point_color(pt, ink); + /* set the transformed position */ float co[2]; - gp_calc_2d_stroke_xy(pt, sflag, offsx, offsy, winx, winy, co); + mul_v3_m4v3(fpt, diff_mat, &pt->x); + gp_calc_2d_stroke_fxy(fpt, sflag, offsx, offsy, winx, winy, co); translate_m4(modelview, co[0], co[1], 0.0f); glLoadMatrixf((float *)modelview); @@ -276,8 +308,9 @@ static void gp_draw_stroke_volumetric_2d(bGPDspoint *points, int totpoints, shor } /* draw a 3D stroke in "volumetric" style */ -static void gp_draw_stroke_volumetric_3d(bGPDspoint *points, int totpoints, short thickness, - short UNUSED(dflag), short UNUSED(sflag)) +static void gp_draw_stroke_volumetric_3d( + bGPDspoint *points, int totpoints, short thickness, + short UNUSED(dflag), short UNUSED(sflag), float diff_mat[4][4], float ink[4]) { GLUquadricObj *qobj = gluNewQuadric(); @@ -286,7 +319,7 @@ static void gp_draw_stroke_volumetric_3d(bGPDspoint *points, int totpoints, shor bGPDspoint *pt; int i; - + float fpt[3]; /* Get the basic modelview matrix we use for performing calculations */ glGetFloatv(GL_MODELVIEW_MATRIX, (float *)base_modelview); @@ -305,8 +338,13 @@ static void gp_draw_stroke_volumetric_3d(bGPDspoint *points, int totpoints, shor glPushMatrix(); for (i = 0, pt = points; i < totpoints && pt; i++, pt++) { + /* color of point */ + gp_set_point_color(pt, ink); + + mul_v3_m4v3(fpt, diff_mat, &pt->x); + /* apply translation to base_modelview, so that the translated point is put in the right place */ - translate_m4(base_modelview, pt->x, pt->y, pt->z); + translate_m4(base_modelview, fpt[0], fpt[1], fpt[2]); /* copy the translation component to the billboard matrix we're going to use, * then reset the base matrix to the original values so that we can do the same @@ -378,9 +416,9 @@ static void gp_stroke_2d_flat(bGPDspoint *points, int totpoints, float(*points2d static void gp_triangulate_stroke_fill(bGPDstroke *gps) { BLI_assert(gps->totpoints >= 3); - gps->tot_triangles = gps->totpoints - 2; - + /* allocate memory for temporary areas */ + gps->tot_triangles = gps->totpoints - 2; unsigned int (*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"); @@ -390,6 +428,8 @@ static void gp_triangulate_stroke_fill(bGPDstroke *gps) gp_stroke_2d_flat(gps->points, gps->totpoints, points2d, &direction); BLI_polyfill_calc((const float(*)[2])points2d, (unsigned int)gps->totpoints, direction, (unsigned int(*)[3])tmp_triangles); + /* Number of triangles */ + gps->tot_triangles = gps->totpoints - 2; /* save triangulation data in stroke cache */ if (gps->tot_triangles > 0) { if (gps->triangles == NULL) { @@ -399,9 +439,7 @@ static void gp_triangulate_stroke_fill(bGPDstroke *gps) gps->triangles = MEM_recallocN(gps->triangles, sizeof(*gps->triangles) * gps->tot_triangles); } - int i; - - for (i = 0; i < gps->tot_triangles; i++) { + for (int i = 0; i < gps->tot_triangles; i++) { bGPDtriangle *stroke_triangle = &gps->triangles[i]; stroke_triangle->v1 = tmp_triangles[i][0]; stroke_triangle->v2 = tmp_triangles[i][1]; @@ -428,21 +466,27 @@ static void gp_triangulate_stroke_fill(bGPDstroke *gps) /* draw fills for shapes */ -static void gp_draw_stroke_fill(bGPDstroke *gps, short UNUSED(thickness), short dflag, int offsx, int offsy, int winx, int winy) +static void gp_draw_stroke_fill( + bGPdata *gpd, bGPDstroke *gps, + int offsx, int offsy, int winx, int winy, float diff_mat[4][4]) { + bGPDpalettecolor *palcolor; + int i; + float fpt[3]; + BLI_assert(gps->totpoints >= 3); - + + palcolor = ED_gpencil_stroke_getcolor(gpd, gps); + /* Triangulation fill if high quality flag is enabled */ - if (dflag & GP_DRAWDATA_HQ_FILL) { + if (palcolor->flag & PC_COLOR_HQ_FILL) { bGPDtriangle *stroke_triangle; bGPDspoint *pt; - int i; - + /* Calculate triangles cache for filling area (must be done only after changes) */ if ((gps->flag & GP_STROKE_RECALC_CACHES) || (gps->tot_triangles == 0) || (gps->triangles == NULL)) { gp_triangulate_stroke_fill(gps); } - /* Draw all triangles for filling the polygon (cache must be calculated before) */ BLI_assert(gps->tot_triangles >= 1); glBegin(GL_TRIANGLES); @@ -450,32 +494,33 @@ static void gp_draw_stroke_fill(bGPDstroke *gps, short UNUSED(thickness), short if (gps->flag & GP_STROKE_3DSPACE) { /* vertex 1 */ pt = &gps->points[stroke_triangle->v1]; - glVertex3fv(&pt->x); - + mul_v3_m4v3(fpt, diff_mat, &pt->x); + glVertex3fv(fpt); /* vertex 2 */ pt = &gps->points[stroke_triangle->v2]; - glVertex3fv(&pt->x); - + mul_v3_m4v3(fpt, diff_mat, &pt->x); + glVertex3fv(fpt); /* vertex 3 */ pt = &gps->points[stroke_triangle->v3]; - glVertex3fv(&pt->x); + mul_v3_m4v3(fpt, diff_mat, &pt->x); + glVertex3fv(fpt); } else { float co[2]; - /* vertex 1 */ pt = &gps->points[stroke_triangle->v1]; - gp_calc_2d_stroke_xy(pt, gps->flag, offsx, offsy, winx, winy, co); + mul_v3_m4v3(fpt, diff_mat, &pt->x); + gp_calc_2d_stroke_fxy(fpt, gps->flag, offsx, offsy, winx, winy, co); glVertex2fv(co); - /* vertex 2 */ pt = &gps->points[stroke_triangle->v2]; - gp_calc_2d_stroke_xy(pt, gps->flag, offsx, offsy, winx, winy, co); + mul_v3_m4v3(fpt, diff_mat, &pt->x); + gp_calc_2d_stroke_fxy(fpt, gps->flag, offsx, offsy, winx, winy, co); glVertex2fv(co); - /* vertex 3 */ pt = &gps->points[stroke_triangle->v3]; - gp_calc_2d_stroke_xy(pt, gps->flag, offsx, offsy, winx, winy, co); + mul_v3_m4v3(fpt, diff_mat, &pt->x); + gp_calc_2d_stroke_fxy(fpt, gps->flag, offsx, offsy, winx, winy, co); glVertex2fv(co); } } @@ -483,30 +528,31 @@ static void gp_draw_stroke_fill(bGPDstroke *gps, short UNUSED(thickness), short } else { /* As an initial implementation, we use the OpenGL filled polygon drawing - * here since it's the easiest option to implement for this case. It does - * come with limitations (notably for concave shapes), though it works well - * enough for many simple situations. - * - * We keep this legacy implementation around despite now having the high quality - * fills, as this is necessary for keeping everything working nicely for files - * created using old versions of Blender which may have depended on the artifacts - * the old fills created. - */ + * here since it's the easiest option to implement for this case. It does + * come with limitations (notably for concave shapes), though it shouldn't + * be much of an issue in most cases. + * + * We keep this legacy implementation around despite now having the high quality + * fills, as this is necessary for keeping everything working nicely for files + * created using old versions of Blender which may have depended on the artifacts + * the old fills created. + */ bGPDspoint *pt; - int i; - + glBegin(GL_POLYGON); for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) { if (gps->flag & GP_STROKE_3DSPACE) { - glVertex3fv(&pt->x); + mul_v3_m4v3(fpt, diff_mat, &pt->x); + glVertex3fv(fpt); } else { float co[2]; - - gp_calc_2d_stroke_xy(pt, gps->flag, offsx, offsy, winx, winy, co); + mul_v3_m4v3(fpt, diff_mat, &pt->x); + gp_calc_2d_stroke_fxy(fpt, gps->flag, offsx, offsy, winx, winy, co); glVertex2fv(co); } } + glEnd(); } } @@ -514,23 +560,33 @@ static void gp_draw_stroke_fill(bGPDstroke *gps, short UNUSED(thickness), short /* ----- Existing Strokes Drawing (3D and Point) ------ */ /* draw a given stroke - just a single dot (only one point) */ -static void gp_draw_stroke_point(bGPDspoint *points, short thickness, short dflag, short sflag, - int offsx, int offsy, int winx, int winy) +static void gp_draw_stroke_point( + bGPDspoint *points, short thickness, short dflag, short sflag, + int offsx, int offsy, int winx, int winy, float diff_mat[4][4], float ink[4]) { + float fpt[3]; + bGPDspoint *pt = &points[0]; + + /* color of point */ + gp_set_point_color(pt, ink); + /* set point thickness (since there's only one of these) */ glPointSize((float)(thickness + 2) * points->pressure); + /* get final position using parent matrix */ + mul_v3_m4v3(fpt, diff_mat, &pt->x); + /* draw point */ if (sflag & GP_STROKE_3DSPACE) { glBegin(GL_POINTS); - glVertex3fv(&points->x); + glVertex3fv(fpt); glEnd(); } else { float co[2]; /* get coordinates of point */ - gp_calc_2d_stroke_xy(points, sflag, offsx, offsy, winx, winy, co); + gp_calc_2d_stroke_fxy(fpt, sflag, offsx, offsy, winx, winy, co); /* if thickness is less than GP_DRAWTHICKNESS_SPECIAL, simple dot looks ok * - also mandatory in if Image Editor 'image-based' dot @@ -559,16 +615,21 @@ static void gp_draw_stroke_point(bGPDspoint *points, short thickness, short dfla } /* draw a given stroke in 3d (i.e. in 3d-space), using simple ogl lines */ -static void gp_draw_stroke_3d(bGPDspoint *points, int totpoints, short thickness, bool debug, short UNUSED(sflag)) +static void gp_draw_stroke_3d(bGPDspoint *points, int totpoints, short thickness, bool debug, + short UNUSED(sflag), float diff_mat[4][4], float ink[4], bool cyclic) { - bGPDspoint *pt; + bGPDspoint *pt, *pt2; float curpressure = points[0].pressure; int i; - + float fpt[3]; + float cyclic_fpt[3]; + /* draw stroke curve */ glLineWidth(max_ff(curpressure * thickness, 1.0f)); glBegin(GL_LINE_STRIP); for (i = 0, pt = points; i < totpoints && pt; i++, pt++) { + gp_set_point_color(pt, ink); + /* if there was a significant pressure change, stop the curve, change the thickness of the stroke, * and continue drawing again (since line-width cannot change in middle of GL_LINE_STRIP) * Note: we want more visible levels of pressures when thickness is bigger. @@ -580,15 +641,29 @@ static void gp_draw_stroke_3d(bGPDspoint *points, int totpoints, short thickness glBegin(GL_LINE_STRIP); /* need to roll-back one point to ensure that there are no gaps in the stroke */ - if (i != 0) glVertex3fv(&(pt - 1)->x); + if (i != 0) { + pt2 = pt - 1; + mul_v3_m4v3(fpt, diff_mat, &pt2->x); + glVertex3fv(fpt); + } /* now the point we want... */ - glVertex3fv(&pt->x); + mul_v3_m4v3(fpt, diff_mat, &pt->x); + glVertex3fv(fpt); } else { - glVertex3fv(&pt->x); + mul_v3_m4v3(fpt, diff_mat, &pt->x); + glVertex3fv(fpt); + } + /* saves first point to use in cyclic */ + if (i == 0) { + copy_v3_v3(cyclic_fpt, fpt); } } + /* if cyclic draw line to first point */ + if (cyclic) { + glVertex3fv(cyclic_fpt); + } glEnd(); /* draw debug points of curve on top? */ @@ -597,9 +672,12 @@ static void gp_draw_stroke_3d(bGPDspoint *points, int totpoints, short thickness glPointSize((float)(thickness + 2)); glBegin(GL_POINTS); - for (i = 0, pt = points; i < totpoints && pt; i++, pt++) - glVertex3fv(&pt->x); + for (i = 0, pt = points; i < totpoints && pt; i++, pt++) { + mul_v3_m4v3(fpt, diff_mat, &pt->x); + glVertex3fv(fpt); + } glEnd(); + } } @@ -607,7 +685,7 @@ static void gp_draw_stroke_3d(bGPDspoint *points, int totpoints, short thickness /* draw a given stroke in 2d */ static void gp_draw_stroke_2d(bGPDspoint *points, int totpoints, short thickness_s, short dflag, short sflag, - bool debug, int offsx, int offsy, int winx, int winy) + bool debug, int offsx, int offsy, int winx, int winy, float diff_mat[4][4], float ink[4]) { /* otherwise thickness is twice that of the 3D view */ float thickness = (float)thickness_s * 0.5f; @@ -625,6 +703,7 @@ static void gp_draw_stroke_2d(bGPDspoint *points, int totpoints, short thickness bGPDspoint *pt1, *pt2; float pm[2]; int i; + float fpt[3]; glShadeModel(GL_FLAT); glBegin(GL_QUADS); @@ -635,10 +714,13 @@ static void gp_draw_stroke_2d(bGPDspoint *points, int totpoints, short thickness float m1[2], m2[2]; /* gradient and normal */ float mt[2], sc[2]; /* gradient for thickness, point for end-cap */ float pthick; /* thickness at segment point */ - + /* get x and y coordinates from points */ - gp_calc_2d_stroke_xy(pt1, sflag, offsx, offsy, winx, winy, s0); - gp_calc_2d_stroke_xy(pt2, sflag, offsx, offsy, winx, winy, s1); + mul_v3_m4v3(fpt, diff_mat, &pt1->x); + gp_calc_2d_stroke_fxy(fpt, sflag, offsx, offsy, winx, winy, s0); + + mul_v3_m4v3(fpt, diff_mat, &pt2->x); + gp_calc_2d_stroke_fxy(fpt, sflag, offsx, offsy, winx, winy, s1); /* calculate gradient and normal - 'angle'=(ny/nx) */ m1[1] = s1[1] - s0[1]; @@ -650,6 +732,9 @@ static void gp_draw_stroke_2d(bGPDspoint *points, int totpoints, short thickness /* always use pressure from first point here */ pthick = (pt1->pressure * thickness * scalefac); + /* color of point */ + gp_set_point_color(pt1, ink); + /* if the first segment, start of segment is segment's normal */ if (i == 0) { /* draw start cap first @@ -725,6 +810,9 @@ static void gp_draw_stroke_2d(bGPDspoint *points, int totpoints, short thickness /* for once, we use second point's pressure (otherwise it won't be drawn) */ pthick = (pt2->pressure * thickness * scalefac); + /* color of point */ + gp_set_point_color(pt2, ink); + /* calculate points for end of segment */ mt[0] = m2[0] * pthick; mt[1] = m2[1] * pthick; @@ -770,14 +858,15 @@ static void gp_draw_stroke_2d(bGPDspoint *points, int totpoints, short thickness if (debug) { bGPDspoint *pt; int i; - + float fpt[3]; + glPointSize((float)(thickness_s + 2)); glBegin(GL_POINTS); for (i = 0, pt = points; i < totpoints && pt; i++, pt++) { float co[2]; - - gp_calc_2d_stroke_xy(pt, sflag, offsx, offsy, winx, winy, co); + mul_v3_m4v3(fpt, diff_mat, &pt->x); + gp_calc_2d_stroke_fxy(fpt, sflag, offsx, offsy, winx, winy, co); glVertex2fv(co); } glEnd(); @@ -818,26 +907,45 @@ static bool gp_can_draw_stroke(const bGPDstroke *gps, const int dflag) } /* draw a set of strokes */ -static void gp_draw_strokes(bGPDframe *gpf, int offsx, int offsy, int winx, int winy, int dflag, - bool debug, short lthick, const float color[4], const float fill_color[4]) +static void gp_draw_strokes( + bGPdata *gpd, bGPDframe *gpf, int offsx, int offsy, int winx, int winy, int dflag, + bool debug, short lthick, const float opacity, const float tintcolor[4], + const bool onion, const bool custonion, float diff_mat[4][4]) { bGPDstroke *gps; - + float tcolor[4]; + float tfill[4]; + short sthickness; + float ink[4]; + for (gps = gpf->strokes.first; gps; gps = gps->next) { /* check if stroke can be drawn */ - if (gp_can_draw_stroke(gps, dflag) == false) + if (gp_can_draw_stroke(gps, dflag) == false) { continue; - + } + /* check if the color is visible */ + bGPDpalettecolor *palcolor = ED_gpencil_stroke_getcolor(gpd, gps); + if ((palcolor == NULL) || + (palcolor->flag & PC_COLOR_HIDE) || + /* if onion and ghost flag do not draw*/ + (onion && (palcolor->flag & PC_COLOR_ONIONSKIN))) + { + continue; + } + + /* calculate thickness */ + sthickness = gps->thickness + lthick; + /* check which stroke-drawer to use */ if (dflag & GP_DRAWDATA_ONLY3D) { const int no_xray = (dflag & GP_DRAWDATA_NO_XRAY); int mask_orig = 0; - + if (no_xray) { glGetIntegerv(GL_DEPTH_WRITEMASK, &mask_orig); glDepthMask(0); glEnable(GL_DEPTH_TEST); - + /* first arg is normally rv3d->dist, but this isn't * available here and seems to work quite well without */ bglPolygonOffset(1.0f, 1.0f); @@ -846,34 +954,65 @@ static void gp_draw_strokes(bGPDframe *gpf, int offsx, int offsy, int winx, int glPolygonOffset(-1.0f, -1.0f); #endif } - + /* 3D Fill */ - if ((dflag & GP_DRAWDATA_FILL) && (gps->totpoints >= 3)) { - glColor4fv(fill_color); - gp_draw_stroke_fill(gps, lthick, dflag, offsx, offsy, winx, winy); + //if ((dflag & GP_DRAWDATA_FILL) && (gps->totpoints >= 3)) { + if (gps->totpoints >= 3) { + /* set color using palette, tint color and opacity */ + interp_v3_v3v3(tfill, palcolor->fill, tintcolor, tintcolor[3]); + tfill[3] = palcolor->fill[3] * opacity; + if (tfill[3] > GPENCIL_ALPHA_OPACITY_THRESH) { + if (!onion) { + glColor4fv(tfill); + } + else { + if (custonion) { + glColor4fv(tintcolor); + } + else { + ARRAY_SET_ITEMS(tfill, UNPACK3(palcolor->fill), tintcolor[3]); + glColor4fv(tfill); + } + } + gp_draw_stroke_fill(gpd, gps, offsx, offsy, winx, winy, diff_mat); + } } - + /* 3D Stroke */ - glColor4fv(color); - - if (dflag & GP_DRAWDATA_VOLUMETRIC) { + /* set color using palette, tint color and opacity */ + if (!onion) { + interp_v3_v3v3(tcolor, palcolor->color, tintcolor, tintcolor[3]); + tcolor[3] = palcolor->color[3] * opacity; + copy_v4_v4(ink, tcolor); + } + else { + if (custonion) { + copy_v4_v4(ink, tintcolor); + } + else { + ARRAY_SET_ITEMS(tcolor, palcolor->color[0], palcolor->color[1], palcolor->color[2], opacity); + copy_v4_v4(ink, tcolor); + } + } + if (palcolor->flag & PC_COLOR_VOLUMETRIC) { /* volumetric stroke drawing */ - gp_draw_stroke_volumetric_3d(gps->points, gps->totpoints, lthick, dflag, gps->flag); + gp_draw_stroke_volumetric_3d(gps->points, gps->totpoints, sthickness, dflag, gps->flag, diff_mat, ink); } else { /* 3D Lines - OpenGL primitives-based */ if (gps->totpoints == 1) { - gp_draw_stroke_point(gps->points, lthick, dflag, gps->flag, offsx, offsy, winx, winy); + gp_draw_stroke_point(gps->points, sthickness, dflag, gps->flag, offsx, offsy, winx, winy, + diff_mat, ink); } else { - gp_draw_stroke_3d(gps->points, gps->totpoints, lthick, debug, gps->flag); + gp_draw_stroke_3d(gps->points, gps->totpoints, sthickness, debug, gps->flag, + diff_mat, ink, gps->flag & GP_STROKE_CYCLIC); } } - if (no_xray) { glDepthMask(mask_orig); glDisable(GL_DEPTH_TEST); - + bglPolygonOffset(0.0, 0.0); #if 0 glDisable(GL_POLYGON_OFFSET_LINE); @@ -883,25 +1022,58 @@ static void gp_draw_strokes(bGPDframe *gpf, int offsx, int offsy, int winx, int } else { /* 2D - Fill */ - if ((dflag & GP_DRAWDATA_FILL) && (gps->totpoints >= 3)) { - glColor4fv(fill_color); - gp_draw_stroke_fill(gps, lthick, dflag, offsx, offsy, winx, winy); + if (gps->totpoints >= 3) { + /* set color using palette, tint color and opacity */ + interp_v3_v3v3(tfill, palcolor->fill, tintcolor, tintcolor[3]); + tfill[3] = palcolor->fill[3] * opacity; + if (tfill[3] > GPENCIL_ALPHA_OPACITY_THRESH) { + if (!onion) { + glColor4fv(tfill); + } + else { + if (custonion) { + glColor4fv(tintcolor); + } + else { + ARRAY_SET_ITEMS(tfill, palcolor->fill[0], palcolor->fill[1], palcolor->fill[2], + tintcolor[3]); + glColor4fv(tfill); + } + } + gp_draw_stroke_fill(gpd, gps, offsx, offsy, winx, winy, diff_mat); + } } - + /* 2D Strokes... */ - glColor4fv(color); - - if (dflag & GP_DRAWDATA_VOLUMETRIC) { + /* set color using palette, tint color and opacity */ + if (!onion) { + interp_v3_v3v3(tcolor, palcolor->color, tintcolor, tintcolor[3]); + tcolor[3] = palcolor->color[3] * opacity; + copy_v4_v4(ink, tcolor); + } + else { + if (custonion) { + copy_v4_v4(ink, tintcolor); + } + else { + ARRAY_SET_ITEMS(tcolor, palcolor->color[0], palcolor->color[1], palcolor->color[2], opacity); + copy_v4_v4(ink, tcolor); + } + } + if (palcolor->flag & PC_COLOR_VOLUMETRIC) { /* blob/disk-based "volumetric" drawing */ - gp_draw_stroke_volumetric_2d(gps->points, gps->totpoints, lthick, dflag, gps->flag, offsx, offsy, winx, winy); + gp_draw_stroke_volumetric_2d(gps->points, gps->totpoints, sthickness, dflag, gps->flag, + offsx, offsy, winx, winy, diff_mat, ink); } else { /* normal 2D strokes */ if (gps->totpoints == 1) { - gp_draw_stroke_point(gps->points, lthick, dflag, gps->flag, offsx, offsy, winx, winy); + gp_draw_stroke_point(gps->points, sthickness, dflag, gps->flag, offsx, offsy, winx, winy, + diff_mat, ink); } else { - gp_draw_stroke_2d(gps->points, gps->totpoints, lthick, dflag, gps->flag, debug, offsx, offsy, winx, winy); + gp_draw_stroke_2d(gps->points, gps->totpoints, sthickness, dflag, gps->flag, debug, + offsx, offsy, winx, winy, diff_mat, ink); } } } @@ -909,13 +1081,19 @@ static void gp_draw_strokes(bGPDframe *gpf, int offsx, int offsy, int winx, int } /* Draw selected verts for strokes being edited */ -static void gp_draw_strokes_edit(bGPDframe *gpf, int offsx, int offsy, int winx, int winy, short dflag, const float tcolor[3]) +static void gp_draw_strokes_edit( + bGPdata *gpd, bGPDframe *gpf, int offsx, int offsy, int winx, int winy, short dflag, + short lflag, float diff_mat[4][4], float alpha) { bGPDstroke *gps; + /* if alpha 0 do not draw */ + if (alpha == 0.0f) + return; + const bool no_xray = (dflag & GP_DRAWDATA_NO_XRAY) != 0; int mask_orig = 0; - + /* set up depth masks... */ if (dflag & GP_DRAWDATA_ONLY3D) { if (no_xray) { @@ -939,7 +1117,8 @@ static void gp_draw_strokes_edit(bGPDframe *gpf, int offsx, int offsy, int winx, bGPDspoint *pt; float vsize, bsize; int i; - + float fpt[3]; + /* check if stroke can be drawn */ if (gp_can_draw_stroke(gps, dflag) == false) continue; @@ -951,6 +1130,19 @@ static void gp_draw_strokes_edit(bGPDframe *gpf, int offsx, int offsy, int winx, if ((gps->flag & GP_STROKE_SELECT) == 0) continue; + /* verify palette color lock */ + { + bGPDpalettecolor *palcolor = ED_gpencil_stroke_getcolor(gpd, gps); + if (palcolor != NULL) { + if (palcolor->flag & PC_COLOR_HIDE) { + continue; + } + if (((lflag & GP_LAYER_UNLOCK_COLOR) == 0) && (palcolor->flag & PC_COLOR_LOCKED)) { + continue; + } + } + } + /* Get size of verts: * - The selected state needs to be larger than the unselected state so that * they stand out more. @@ -966,25 +1158,23 @@ static void gp_draw_strokes_edit(bGPDframe *gpf, int offsx, int offsy, int winx, } /* First Pass: Draw all the verts (i.e. these become the unselected state) */ - if (tcolor != NULL) { - /* for now, we assume that the base color of the points is not too close to the real color */ - glColor3fv(tcolor); - } - else { - /* this doesn't work well with the default theme and black strokes... */ - UI_ThemeColor(TH_GP_VERTEX); - } + /* for now, we assume that the base color of the points is not too close to the real color */ + /* set color using palette */ + bGPDpalettecolor *palcolor = ED_gpencil_stroke_getcolor(gpd, gps); + glColor3fv(palcolor->color); + glPointSize(bsize); glBegin(GL_POINTS); for (i = 0, pt = gps->points; i < gps->totpoints && pt; i++, pt++) { if (gps->flag & GP_STROKE_3DSPACE) { - glVertex3fv(&pt->x); + mul_v3_m4v3(fpt, diff_mat, &pt->x); + glVertex3fv(fpt); } else { float co[2]; - - gp_calc_2d_stroke_xy(pt, gps->flag, offsx, offsy, winx, winy, co); + mul_v3_m4v3(fpt, diff_mat, &pt->x); + gp_calc_2d_stroke_fxy(fpt, gps->flag, offsx, offsy, winx, winy, co); glVertex2fv(co); } } @@ -992,24 +1182,54 @@ static void gp_draw_strokes_edit(bGPDframe *gpf, int offsx, int offsy, int winx, /* Second Pass: Draw only verts which are selected */ - UI_ThemeColor(TH_GP_VERTEX_SELECT); + float curColor[4]; + UI_GetThemeColor3fv(TH_GP_VERTEX_SELECT, curColor); + glColor4f(curColor[0], curColor[1], curColor[2], alpha); + glPointSize(vsize); glBegin(GL_POINTS); for (i = 0, pt = gps->points; i < gps->totpoints && pt; i++, pt++) { if (pt->flag & GP_SPOINT_SELECT) { if (gps->flag & GP_STROKE_3DSPACE) { - glVertex3fv(&pt->x); + mul_v3_m4v3(fpt, diff_mat, &pt->x); + glVertex3fv(fpt); } else { float co[2]; - gp_calc_2d_stroke_xy(pt, gps->flag, offsx, offsy, winx, winy, co); + mul_v3_m4v3(fpt, diff_mat, &pt->x); + gp_calc_2d_stroke_fxy(fpt, gps->flag, offsx, offsy, winx, winy, co); glVertex2fv(co); } } } glEnd(); + + /* Draw start and end point if enabled stroke direction hint */ + if ((gpd->flag & GP_DATA_SHOW_DIRECTION) && (gps->totpoints > 1)) { + bGPDspoint *p; + + glPointSize(vsize + 4); + glBegin(GL_POINTS); + + /* start point in green bigger */ + glColor3f(0.0f, 1.0f, 0.0f); + p = &gps->points[0]; + mul_v3_m4v3(fpt, diff_mat, &p->x); + glVertex3fv(fpt); + glEnd(); + + /* end point in red smaller */ + glPointSize(vsize + 1); + glBegin(GL_POINTS); + + glColor3f(1.0f, 0.0f, 0.0f); + p = &gps->points[gps->totpoints - 1]; + mul_v3_m4v3(fpt, diff_mat, &p->x); + glVertex3fv(fpt); + glEnd(); + } } @@ -1031,18 +1251,20 @@ static void gp_draw_strokes_edit(bGPDframe *gpf, int offsx, int offsy, int winx, /* ----- General Drawing ------ */ /* draw onion-skinning for a layer */ -static void gp_draw_onionskins(bGPDlayer *gpl, bGPDframe *gpf, int offsx, int offsy, int winx, int winy, - int UNUSED(cfra), int dflag, bool debug, short lthick) +static void gp_draw_onionskins( + bGPdata *gpd, bGPDlayer *gpl, bGPDframe *gpf, int offsx, int offsy, int winx, int winy, + int UNUSED(cfra), int dflag, bool debug, float diff_mat[4][4]) { - const float alpha = gpl->color[3]; + const float default_color[3] = {UNPACK3(U.gpencil_new_layer_col)}; + const float alpha = 1.0f; float color[4]; - + /* 1) Draw Previous Frames First */ if (gpl->flag & GP_LAYER_GHOST_PREVCOL) { copy_v3_v3(color, gpl->gcolor_prev); } else { - copy_v3_v3(color, gpl->color); + copy_v3_v3(color, default_color); } if (gpl->gstep > 0) { @@ -1056,7 +1278,8 @@ static void gp_draw_onionskins(bGPDlayer *gpl, bGPDframe *gpf, int offsx, int of /* alpha decreases with distance from curframe index */ fac = 1.0f - ((float)(gpf->framenum - gf->framenum) / (float)(gpl->gstep + 1)); color[3] = alpha * fac * 0.66f; - gp_draw_strokes(gf, offsx, offsy, winx, winy, dflag, debug, lthick, color, color); + gp_draw_strokes(gpd, gf, offsx, offsy, winx, winy, dflag, debug, gpl->thickness, 1.0f, color, + true, gpl->flag & GP_LAYER_GHOST_PREVCOL, diff_mat); } else break; @@ -1066,7 +1289,8 @@ static void gp_draw_onionskins(bGPDlayer *gpl, bGPDframe *gpf, int offsx, int of /* draw the strokes for the ghost frames (at half of the alpha set by user) */ if (gpf->prev) { color[3] = (alpha / 7); - gp_draw_strokes(gpf->prev, offsx, offsy, winx, winy, dflag, debug, lthick, color, color); + gp_draw_strokes(gpd, gpf->prev, offsx, offsy, winx, winy, dflag, debug, gpl->thickness, 1.0f, color, + true, gpl->flag & GP_LAYER_GHOST_PREVCOL, diff_mat); } } else { @@ -1079,7 +1303,7 @@ static void gp_draw_onionskins(bGPDlayer *gpl, bGPDframe *gpf, int offsx, int of copy_v3_v3(color, gpl->gcolor_next); } else { - copy_v3_v3(color, gpl->color); + copy_v3_v3(color, default_color); } if (gpl->gstep_next > 0) { @@ -1093,7 +1317,8 @@ static void gp_draw_onionskins(bGPDlayer *gpl, bGPDframe *gpf, int offsx, int of /* alpha decreases with distance from curframe index */ fac = 1.0f - ((float)(gf->framenum - gpf->framenum) / (float)(gpl->gstep_next + 1)); color[3] = alpha * fac * 0.66f; - gp_draw_strokes(gf, offsx, offsy, winx, winy, dflag, debug, lthick, color, color); + gp_draw_strokes(gpd, gf, offsx, offsy, winx, winy, dflag, debug, gpl->thickness, 1.0f, color, + true, gpl->flag & GP_LAYER_GHOST_NEXTCOL, diff_mat); } else break; @@ -1103,27 +1328,31 @@ static void gp_draw_onionskins(bGPDlayer *gpl, bGPDframe *gpf, int offsx, int of /* draw the strokes for the ghost frames (at half of the alpha set by user) */ if (gpf->next) { color[3] = (alpha / 4); - gp_draw_strokes(gpf->next, offsx, offsy, winx, winy, dflag, debug, lthick, color, color); + gp_draw_strokes(gpd, gpf->next, offsx, offsy, winx, winy, dflag, debug, gpl->thickness, 1.0f, color, + true, gpl->flag & GP_LAYER_GHOST_NEXTCOL, diff_mat); } } else { /* don't draw - disabled */ } - /* 3) restore alpha */ - glColor4fv(gpl->color); } /* loop over gpencil data layers, drawing them */ -static void gp_draw_data_layers(bGPdata *gpd, int offsx, int offsy, int winx, int winy, int cfra, int dflag) +static void gp_draw_data_layers( + bGPDbrush *brush, float alpha, bGPdata *gpd, + int offsx, int offsy, int winx, int winy, int cfra, int dflag) { bGPDlayer *gpl; - + float diff_mat[4][4]; + for (gpl = gpd->layers.first; gpl; gpl = gpl->next) { bGPDframe *gpf; - + /* calculate parent position */ + ED_gpencil_parent_location(gpl, diff_mat); + bool debug = (gpl->flag & GP_LAYER_DRAWDEBUG) ? true : false; - short lthick = gpl->thickness; + short lthick = brush->thickness + gpl->thickness; /* don't draw layer if hidden */ if (gpl->flag & GP_LAYER_HIDE) @@ -1155,9 +1384,6 @@ static void gp_draw_data_layers(bGPdata *gpd, int offsx, int offsy, int winx, in /* HQ fills... */ GP_DRAWFLAG_APPLY((gpl->flag & GP_LAYER_HQ_FILL), GP_DRAWDATA_HQ_FILL); - /* fill strokes... */ - // XXX: this is not a very good limit - GP_DRAWFLAG_APPLY((gpl->fill[3] > GPENCIL_ALPHA_OPACITY_THRESH), GP_DRAWDATA_FILL); #undef GP_DRAWFLAG_APPLY /* draw 'onionskins' (frame left + right) */ @@ -1165,11 +1391,12 @@ static void gp_draw_data_layers(bGPdata *gpd, int offsx, int offsy, int winx, in /* Drawing method - only immediately surrounding (gstep = 0), * or within a frame range on either side (gstep > 0) */ - gp_draw_onionskins(gpl, gpf, offsx, offsy, winx, winy, cfra, dflag, debug, lthick); + gp_draw_onionskins(gpd, gpl, gpf, offsx, offsy, winx, winy, cfra, dflag, debug, diff_mat); } /* draw the strokes already in active frame */ - gp_draw_strokes(gpf, offsx, offsy, winx, winy, dflag, debug, lthick, gpl->color, gpl->fill); + gp_draw_strokes(gpd, gpf, offsx, offsy, winx, winy, dflag, debug, gpl->thickness, + gpl->opacity, gpl->tintcolor, false, false, diff_mat); /* Draw verts of selected strokes * - when doing OpenGL renders, we don't want to be showing these, as that ends up flickering @@ -1183,8 +1410,7 @@ static void gp_draw_data_layers(bGPdata *gpd, int offsx, int offsy, int winx, in (gpl->flag & GP_LAYER_LOCKED) == 0 && (gpd->flag & GP_DATA_STROKE_EDITMODE)) { - gp_draw_strokes_edit(gpf, offsx, offsy, winx, winy, dflag, - (gpl->color[3] < 0.95f) ? gpl->color : NULL); + gp_draw_strokes_edit(gpd, gpf, offsx, offsy, winx, winy, dflag, gpl->flag, diff_mat, alpha); } /* Check if may need to draw the active stroke cache, only if this layer is the active layer @@ -1194,7 +1420,7 @@ static void gp_draw_data_layers(bGPdata *gpd, int offsx, int offsy, int winx, in (gpf->flag & GP_FRAME_PAINT)) { /* Set color for drawing buffer stroke - since this may not be set yet */ - glColor4fv(gpl->color); + // glColor4fv(gpl->color); /* Buffer stroke needs to be drawn with a different linestyle * to help differentiate them from normal strokes. @@ -1202,11 +1428,12 @@ static void gp_draw_data_layers(bGPdata *gpd, int offsx, int offsy, int winx, in * It should also be noted that sbuffer contains temporary point types * i.e. tGPspoints NOT bGPDspoints */ - if (gpl->flag & GP_LAYER_VOLUMETRIC) { - gp_draw_stroke_volumetric_buffer(gpd->sbuffer, gpd->sbuffer_size, lthick, dflag, gpd->sbuffer_sflag); + if (gpd->sflag & PC_COLOR_VOLUMETRIC) { + gp_draw_stroke_volumetric_buffer(gpd->sbuffer, gpd->sbuffer_size, lthick, + dflag, gpd->sbuffer_sflag, gpd->scolor); } else { - gp_draw_stroke_buffer(gpd->sbuffer, gpd->sbuffer_size, lthick, dflag, gpd->sbuffer_sflag); + gp_draw_stroke_buffer(gpd->sbuffer, gpd->sbuffer_size, lthick, dflag, gpd->sbuffer_sflag, gpd->scolor); } } } @@ -1258,7 +1485,9 @@ static void gp_draw_status_text(bGPdata *gpd, ARegion *ar) } /* draw grease-pencil datablock */ -static void gp_draw_data(bGPdata *gpd, int offsx, int offsy, int winx, int winy, int cfra, int dflag) +static void gp_draw_data( + bGPDbrush *brush, float alpha, bGPdata *gpd, + int offsx, int offsy, int winx, int winy, int cfra, int dflag) { /* reset line drawing style (in case previous user didn't reset) */ setlinestyle(0); @@ -1276,7 +1505,7 @@ static void gp_draw_data(bGPdata *gpd, int offsx, int offsy, int winx, int winy, glEnable(GL_BLEND); /* draw! */ - gp_draw_data_layers(gpd, offsx, offsy, winx, winy, cfra, dflag); + gp_draw_data_layers(brush, alpha, gpd, offsx, offsy, winx, winy, cfra, dflag); /* turn off alpha blending, then smooth lines */ glDisable(GL_BLEND); // alpha blending @@ -1303,14 +1532,25 @@ static void gp_draw_data_all(Scene *scene, bGPdata *gpd, int offsx, int offsy, i } if (gpd_source) { - gp_draw_data(gpd_source, offsx, offsy, winx, winy, cfra, dflag); + ToolSettings *ts = scene->toolsettings; + bGPDbrush *brush = gpencil_brush_getactive(ts); + if (brush != NULL) { + gp_draw_data(brush, ts->gp_sculpt.alpha, gpd_source, + offsx, offsy, winx, winy, cfra, dflag); + } + } } /* scene/clip data has already been drawn, only object/track data is drawn here * if gpd_source == gpd, we don't have any object/track data and we can skip */ if (gpd_source == NULL || (gpd_source && gpd_source != gpd)) { - gp_draw_data(gpd, offsx, offsy, winx, winy, cfra, dflag); + ToolSettings *ts = scene->toolsettings; + bGPDbrush *brush = gpencil_brush_getactive(ts); + if (brush != NULL) { + gp_draw_data(brush, ts->gp_sculpt.alpha, gpd, + offsx, offsy, winx, winy, cfra, dflag); + } } } @@ -1479,6 +1719,7 @@ void ED_gpencil_draw_view3d(wmWindowManager *wm, Scene *scene, View3D *v3d, AReg /* draw it! */ gp_draw_data_all(scene, gpd, offsx, offsy, winx, winy, CFRA, dflag, v3d->spacetype); + } void ED_gpencil_draw_ex(Scene *scene, bGPdata *gpd, int winx, int winy, const int cfra, const char spacetype) diff --git a/source/blender/editors/gpencil/gpencil_brush.c b/source/blender/editors/gpencil/gpencil_brush.c index 0271afd6827..1bb3b7e1ae7 100644 --- a/source/blender/editors/gpencil/gpencil_brush.c +++ b/source/blender/editors/gpencil/gpencil_brush.c @@ -18,7 +18,7 @@ * The Original Code is Copyright (C) 2015, Blender Foundation * This is a new part of Blender * - * Contributor(s): Joshua Leung + * Contributor(s): Joshua Leung, Antonio Vazquez * * ***** END GPL LICENSE BLOCK ***** * @@ -51,6 +51,7 @@ #include "DNA_space_types.h" #include "DNA_view3d_types.h" #include "DNA_gpencil_types.h" +#include "DNA_object_types.h" #include "BKE_context.h" #include "BKE_global.h" @@ -223,9 +224,26 @@ static bool gp_brush_smooth_apply(tGP_BrushEditData *gso, bGPDstroke *gps, int i GP_EditBrush_Data *brush = gso->brush; float inf = gp_brush_influence_calc(gso, radius, co); bool affect_pressure = (brush->flag & GP_EDITBRUSH_FLAG_SMOOTH_PRESSURE) != 0; - + /* need one flag enabled by default */ + if ((gso->settings->flag & (GP_BRUSHEDIT_FLAG_APPLY_POSITION | + GP_BRUSHEDIT_FLAG_APPLY_STRENGTH | + GP_BRUSHEDIT_FLAG_APPLY_THICKNESS)) == 0) + { + gso->settings->flag |= GP_BRUSHEDIT_FLAG_APPLY_POSITION; + } + /* perform smoothing */ - return gp_smooth_stroke(gps, i, inf, affect_pressure); + if (gso->settings->flag & GP_BRUSHEDIT_FLAG_APPLY_POSITION) { + gp_smooth_stroke(gps, i, inf, affect_pressure); + } + if (gso->settings->flag & GP_BRUSHEDIT_FLAG_APPLY_STRENGTH) { + gp_smooth_stroke_strength(gps, i, inf); + } + if (gso->settings->flag & GP_BRUSHEDIT_FLAG_APPLY_THICKNESS) { + gp_smooth_stroke_thickness(gps, i, inf); + } + + return true; } /* ----------------------------------------------- */ @@ -268,6 +286,41 @@ static bool gp_brush_thickness_apply(tGP_BrushEditData *gso, bGPDstroke *gps, in /* ----------------------------------------------- */ +/* Color Strength Brush */ + +/* Make color more or less transparent by the specified amounts */ +static bool gp_brush_strength_apply( + tGP_BrushEditData *gso, bGPDstroke *gps, int i, + const int radius, const int co[2]) +{ + bGPDspoint *pt = gps->points + i; + float inf; + + /* Compute strength of effect + * - We divide the strength by 10, so that users can set "sane" values. + * Otherwise, good default values are in the range of 0.093 + */ + inf = gp_brush_influence_calc(gso, radius, co) / 10.0f; + + /* apply */ + // XXX: this is much too strong, and it should probably do some smoothing with the surrounding stuff + if (gp_brush_invert_check(gso)) { + /* make line thinner - reduce stroke pressure */ + pt->strength -= inf; + } + else { + /* make line thicker - increase stroke pressure */ + pt->strength += inf; + } + + /* Strength should stay within [0.0, 1.0] */ + CLAMP(pt->strength, 0.0f, 1.0f); + + return true; +} + + +/* ----------------------------------------------- */ /* Grab Brush */ /* Custom data per stroke for the Grab Brush @@ -373,11 +426,12 @@ static void gp_brush_grab_calc_dvec(tGP_BrushEditData *gso) } /* Apply grab transform to all relevant points of the affected strokes */ -static void gp_brush_grab_apply_cached(tGP_BrushEditData *gso, bGPDstroke *gps) +static void gp_brush_grab_apply_cached( + tGP_BrushEditData *gso, bGPDstroke *gps, bool parented, 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]]; @@ -385,9 +439,23 @@ static void gp_brush_grab_apply_cached(tGP_BrushEditData *gso, bGPDstroke *gps) /* adjust the amount of displacement to apply */ mul_v3_v3fl(delta, gso->dvec, data->weights[i]); + if (!parented) { + /* apply */ + add_v3_v3(&pt->x, delta); + } + else { + float fpt[3]; + /* apply transformation */ + mul_v3_m4v3(fpt, diff_mat, &pt->x); + /* apply */ + add_v3_v3(fpt, delta); + copy_v3_v3(&pt->x, fpt); + /* 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); + } - /* apply */ - add_v3_v3(&pt->x, delta); } } @@ -592,62 +660,92 @@ static bool gp_brush_randomize_apply(tGP_BrushEditData *gso, bGPDstroke *gps, in */ const float inf = gp_brush_influence_calc(gso, radius, co) / 2.0f; const float fac = BLI_frand() * inf; - - /* Jitter is applied perpendicular to the mouse movement vector - * - We compute all effects in screenspace (since it's easier) - * and then project these to get the points/distances in - * viewspace as needed - */ - float mvec[2], svec[2]; - - /* mouse movement in ints -> floats */ - mvec[0] = (float)(gso->mval[0] - gso->mval_prev[0]); - mvec[1] = (float)(gso->mval[1] - gso->mval_prev[1]); - - /* rotate mvec by 90 degrees... */ - svec[0] = -mvec[1]; - svec[1] = mvec[0]; - - //printf("svec = %f %f, ", svec[0], svec[1]); - - /* scale the displacement by the random displacement, and apply */ - if (BLI_frand() > 0.5f) { - mul_v2_fl(svec, -fac); - } - else { - mul_v2_fl(svec, fac); + /* need one flag enabled by default */ + if ((gso->settings->flag & (GP_BRUSHEDIT_FLAG_APPLY_POSITION | + GP_BRUSHEDIT_FLAG_APPLY_STRENGTH | + GP_BRUSHEDIT_FLAG_APPLY_THICKNESS)) == 0) + { + gso->settings->flag |= GP_BRUSHEDIT_FLAG_APPLY_POSITION; } - - //printf("%f %f (%f), nco = {%f %f}, co = %d %d\n", svec[0], svec[1], fac, nco[0], nco[1], co[0], co[1]); - - /* convert to dataspace */ - if (gps->flag & GP_STROKE_3DSPACE) { - /* 3D: Project to 3D space */ - if (gso->sa->spacetype == SPACE_VIEW3D) { - bool flip; - RegionView3D *rv3d = gso->ar->regiondata; - float zfac = ED_view3d_calc_zfac(rv3d, &pt->x, &flip); - if (flip == false) { - float dvec[3]; - ED_view3d_win_to_delta(gso->gsc.ar, svec, dvec, zfac); - add_v3_v3(&pt->x, dvec); + + /* apply random to position */ + if (gso->settings->flag & GP_BRUSHEDIT_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 + * viewspace 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_frand() > 0.5f) { + mul_v2_fl(svec, -fac); + } + else { + mul_v2_fl(svec, fac); + } + + //printf("%f %f (%f), nco = {%f %f}, co = %d %d\n", svec[0], svec[1], fac, nco[0], nco[1], co[0], co[1]); + + /* convert to dataspace */ + if (gps->flag & GP_STROKE_3DSPACE) { + /* 3D: Project to 3D space */ + if (gso->sa->spacetype == SPACE_VIEW3D) { + bool flip; + RegionView3D *rv3d = gso->ar->regiondata; + float zfac = ED_view3d_calc_zfac(rv3d, &pt->x, &flip); + if (flip == false) { + float dvec[3]; + ED_view3d_win_to_delta(gso->gsc.ar, svec, dvec, zfac); + add_v3_v3(&pt->x, dvec); + } + } + else { + /* ERROR */ + BLI_assert("3D stroke being sculpted in non-3D view"); } } else { - /* ERROR */ - BLI_assert("3D stroke being sculpted in non-3D view"); + /* 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); } } - 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_BRUSHEDIT_FLAG_APPLY_STRENGTH) { + if (BLI_frand() > 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_BRUSHEDIT_FLAG_APPLY_THICKNESS) { + if (BLI_frand() > 0.5f) { + pt->pressure += fac; + } + else { + pt->pressure -= fac; + } + /* only limit lower value */ + CLAMP_MIN(pt->pressure, 0.0f); + } + /* done */ return true; } @@ -1099,7 +1197,9 @@ static void gpsculpt_brush_init_stroke(tGP_BrushEditData *gso) /* Apply ----------------------------------------------- */ /* Apply brush operation to points in this stroke */ -static bool gpsculpt_brush_do_stroke(tGP_BrushEditData *gso, bGPDstroke *gps, GP_BrushApplyCb apply) +static bool gpsculpt_brush_do_stroke( + tGP_BrushEditData *gso, bGPDstroke *gps, bool parented, + float diff_mat[4][4], GP_BrushApplyCb apply) { GP_SpaceConversion *gsc = &gso->gsc; rcti *rect = &gso->brush_rect; @@ -1111,9 +1211,16 @@ static bool gpsculpt_brush_do_stroke(tGP_BrushEditData *gso, bGPDstroke *gps, GP int i; bool include_last = false; bool changed = false; - + if (gps->totpoints == 1) { - gp_point_to_xy(gsc, gps, gps->points, &pc1[0], &pc1[1]); + if (!parented) { + gp_point_to_xy(gsc, gps, gps->points, &pc1[0], &pc1[1]); + } + else { + bGPDspoint pt_temp; + gp_point_to_parent_space(gps->points, diff_mat, &pt_temp); + gp_point_to_xy(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])) { @@ -1140,10 +1247,20 @@ static bool gpsculpt_brush_do_stroke(tGP_BrushEditData *gso, bGPDstroke *gps, GP continue; } } - - gp_point_to_xy(gsc, gps, pt1, &pc1[0], &pc1[1]); - gp_point_to_xy(gsc, gps, pt2, &pc2[0], &pc2[1]); - + if (!parented) { + gp_point_to_xy(gsc, gps, pt1, &pc1[0], &pc1[1]); + gp_point_to_xy(gsc, gps, pt2, &pc2[0], &pc2[1]); + } + else { + 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]))) @@ -1228,76 +1345,105 @@ static bool gpsculpt_brush_apply_standard(bContext *C, tGP_BrushEditData *gso) /* Find visible strokes, and perform operations on those if hit */ - CTX_DATA_BEGIN(C, bGPDstroke *, gps, editable_gpencil_strokes) + float diff_mat[4][4]; + bool parented = false; + + CTX_DATA_BEGIN(C, bGPDlayer *, gpl, editable_gpencil_layers) { - switch (gso->brush_type) { - case GP_EDITBRUSH_TYPE_SMOOTH: /* Smooth strokes */ - { - changed |= gpsculpt_brush_do_stroke(gso, gps, gp_brush_smooth_apply); - break; - } - - case GP_EDITBRUSH_TYPE_THICKNESS: /* Adjust stroke thickness */ - { - changed |= gpsculpt_brush_do_stroke(gso, gps, gp_brush_thickness_apply); - break; + /* calculate difference matrix if parent object */ + if (gpl->parent != NULL) { + ED_gpencil_parent_location(gpl, diff_mat); + parented = true; + } + else { + parented = false; + } + + bGPDframe *gpf = gpl->actframe; + bGPDstroke *gps; + for (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(gpl, gps) == false) { + continue; } - - case GP_EDITBRUSH_TYPE_GRAB: /* Grab points */ - { - if (gso->first) { - /* First time this brush stroke is being applied: - * 1) Prepare data buffers (init/clear) for this stroke - * 2) Use the points now under the cursor - */ - gp_brush_grab_stroke_init(gso, gps); - changed |= gpsculpt_brush_do_stroke(gso, gps, gp_brush_grab_store_points); + + switch (gso->brush_type) { + case GP_EDITBRUSH_TYPE_SMOOTH: /* Smooth strokes */ + { + changed |= gpsculpt_brush_do_stroke(gso, gps, parented, diff_mat, gp_brush_smooth_apply); + break; } - else { - /* Apply effect to the stored points */ - gp_brush_grab_apply_cached(gso, gps); - changed |= true; + + case GP_EDITBRUSH_TYPE_THICKNESS: /* Adjust stroke thickness */ + { + changed |= gpsculpt_brush_do_stroke(gso, gps, parented, diff_mat, gp_brush_thickness_apply); + break; } - break; - } - - case GP_EDITBRUSH_TYPE_PUSH: /* Push points */ - { - changed |= gpsculpt_brush_do_stroke(gso, gps, gp_brush_push_apply); - break; - } - - case GP_EDITBRUSH_TYPE_PINCH: /* Pinch points */ - { - changed |= gpsculpt_brush_do_stroke(gso, gps, gp_brush_pinch_apply); - break; - } - - case GP_EDITBRUSH_TYPE_TWIST: /* Twist points around midpoint */ - { - changed |= gpsculpt_brush_do_stroke(gso, gps, gp_brush_twist_apply); - break; + + case GP_EDITBRUSH_TYPE_STRENGTH: /* Adjust stroke color strength */ + { + changed |= gpsculpt_brush_do_stroke(gso, gps, parented, diff_mat, gp_brush_strength_apply); + break; + } + + case GP_EDITBRUSH_TYPE_GRAB: /* Grab points */ + { + if (gso->first) { + /* First time this brush stroke is being applied: + * 1) Prepare data buffers (init/clear) for this stroke + * 2) Use the points now under the cursor + */ + gp_brush_grab_stroke_init(gso, gps); + changed |= gpsculpt_brush_do_stroke(gso, gps, parented, diff_mat, gp_brush_grab_store_points); + } + else { + /* Apply effect to the stored points */ + gp_brush_grab_apply_cached(gso, gps, parented, diff_mat); + changed |= true; + } + break; + } + + case GP_EDITBRUSH_TYPE_PUSH: /* Push points */ + { + changed |= gpsculpt_brush_do_stroke(gso, gps, parented, diff_mat, gp_brush_push_apply); + break; + } + + case GP_EDITBRUSH_TYPE_PINCH: /* Pinch points */ + { + changed |= gpsculpt_brush_do_stroke(gso, gps, parented, diff_mat, gp_brush_pinch_apply); + break; + } + + case GP_EDITBRUSH_TYPE_TWIST: /* Twist points around midpoint */ + { + changed |= gpsculpt_brush_do_stroke(gso, gps, parented, diff_mat, gp_brush_twist_apply); + break; + } + + case GP_EDITBRUSH_TYPE_RANDOMIZE: /* Apply jitter */ + { + changed |= gpsculpt_brush_do_stroke(gso, gps, parented, diff_mat, gp_brush_randomize_apply); + break; + } + + default: + printf("ERROR: Unknown type of GPencil Sculpt brush - %u\n", gso->brush_type); + break; } - - case GP_EDITBRUSH_TYPE_RANDOMIZE: /* Apply jitter */ - { - changed |= gpsculpt_brush_do_stroke(gso, gps, gp_brush_randomize_apply); - break; + /* Triangulation must be calculated if changed */ + if (changed) { + gps->flag |= GP_STROKE_RECALC_CACHES; + gps->tot_triangles = 0; } - - default: - printf("ERROR: Unknown type of GPencil Sculpt brush - %u\n", gso->brush_type); - break; - } - - /* Triangulation must be calculated if changed */ - if (changed) { - gps->flag |= GP_STROKE_RECALC_CACHES; - gps->tot_triangles = 0; } } CTX_DATA_END; - + return changed; } @@ -1441,6 +1587,11 @@ static int gpsculpt_brush_invoke(bContext *C, wmOperator *op, const wmEvent *eve needs_timer = true; break; + case GP_EDITBRUSH_TYPE_STRENGTH: + brush_rate = 0.01f; // XXX: hardcoded + needs_timer = true; + break; + case GP_EDITBRUSH_TYPE_PINCH: brush_rate = 0.001f; // XXX: hardcoded needs_timer = true; diff --git a/source/blender/editors/gpencil/gpencil_convert.c b/source/blender/editors/gpencil/gpencil_convert.c index c47985ebc1b..95ea13c399a 100644 --- a/source/blender/editors/gpencil/gpencil_convert.c +++ b/source/blender/editors/gpencil/gpencil_convert.c @@ -142,12 +142,31 @@ static EnumPropertyItem *rna_GPConvert_mode_items(bContext *UNUSED(C), PointerRN /* convert the coordinates from the given stroke point into 3d-coordinates * - assumes that the active space is the 3D-View */ -static void gp_strokepoint_convertcoords(bContext *C, bGPDstroke *gps, bGPDspoint *pt, float p3d[3], rctf *subrect) +static void gp_strokepoint_convertcoords( + bContext *C, bGPDlayer *gpl, bGPDstroke *gps, bGPDspoint *source_pt, + float p3d[3], const rctf *subrect) { Scene *scene = CTX_data_scene(C); View3D *v3d = CTX_wm_view3d(C); ARegion *ar = CTX_wm_region(C); - + bGPDspoint mypt, *pt; + + float diff_mat[4][4]; + pt = &mypt; + + /* calculate difference matrix if parent object */ + if (gpl->parent == NULL) { + copy_v3_v3(&pt->x, &source_pt->x); + } + else { + /* apply parent transform */ + float fpt[3]; + ED_gpencil_parent_location(gpl, diff_mat); + mul_v3_m4v3(fpt, diff_mat, &source_pt->x); + copy_v3_v3(&pt->x, fpt); + } + + if (gps->flag & GP_STROKE_3DSPACE) { /* directly use 3d-coordinates */ copy_v3_v3(p3d, &pt->x); @@ -628,7 +647,7 @@ static void gp_stroke_to_path(bContext *C, bGPDlayer *gpl, bGPDstroke *gps, Curv bp = &nu->bp[old_nbp - 1]; /* First point */ - gp_strokepoint_convertcoords(C, gps, gps->points, p, subrect); + gp_strokepoint_convertcoords(C, gpl, gps, gps->points, p, subrect); if (prev_bp) { interp_v3_v3v3(p1, bp->vec, prev_bp->vec, -GAP_DFAC); if (do_gtd) { @@ -649,7 +668,7 @@ static void gp_stroke_to_path(bContext *C, bGPDlayer *gpl, bGPDstroke *gps, Curv /* Second point */ /* Note dt2 is always negative, which marks the gap. */ if (gps->totpoints > 1) { - gp_strokepoint_convertcoords(C, gps, gps->points + 1, next_p, subrect); + gp_strokepoint_convertcoords(C, gpl, gps, gps->points + 1, next_p, subrect); interp_v3_v3v3(p2, p, next_p, -GAP_DFAC); if (do_gtd) { dt2 = interpf(gps->points[1].time, gps->points[0].time, -GAP_DFAC); @@ -670,9 +689,9 @@ static void gp_stroke_to_path(bContext *C, bGPDlayer *gpl, bGPDstroke *gps, Curv float p[3], next_p[3]; float dt = 0.0f; - gp_strokepoint_convertcoords(C, gps, gps->points, p, subrect); + gp_strokepoint_convertcoords(C, gpl, gps, gps->points, p, subrect); if (gps->totpoints > 1) { - gp_strokepoint_convertcoords(C, gps, gps->points + 1, next_p, subrect); + gp_strokepoint_convertcoords(C, gpl, gps, gps->points + 1, next_p, subrect); interp_v3_v3v3(p, p, next_p, -GAP_DFAC); if (do_gtd) { dt = interpf(gps->points[1].time, gps->points[0].time, -GAP_DFAC); @@ -701,10 +720,10 @@ static void gp_stroke_to_path(bContext *C, bGPDlayer *gpl, bGPDstroke *gps, Curv i++, pt++, bp++) { float p[3]; - float width = pt->pressure * gpl->thickness * WIDTH_CORR_FAC; + float width = pt->pressure * (gps->thickness + gpl->thickness) * WIDTH_CORR_FAC; /* get coordinates to add at */ - gp_strokepoint_convertcoords(C, gps, pt, p, subrect); + gp_strokepoint_convertcoords(C, gpl, gps, pt, p, subrect); gp_stroke_to_path_add_point(gtd, bp, p, (prev_bp) ? prev_bp->vec : p, do_gtd, gps->inittime, pt->time, width, rad_fac, minmax_weights); @@ -816,12 +835,12 @@ static void gp_stroke_to_bezier(bContext *C, bGPDlayer *gpl, bGPDstroke *gps, Cu /* get initial coordinates */ pt = gps->points; if (tot) { - gp_strokepoint_convertcoords(C, gps, pt, (stitch) ? p3d_prev : p3d_cur, subrect); + gp_strokepoint_convertcoords(C, gpl, gps, pt, (stitch) ? p3d_prev : p3d_cur, subrect); if (tot > 1) { - gp_strokepoint_convertcoords(C, gps, pt + 1, (stitch) ? p3d_cur : p3d_next, subrect); + gp_strokepoint_convertcoords(C, gpl, gps, pt + 1, (stitch) ? p3d_cur : p3d_next, subrect); } if (stitch && tot > 2) { - gp_strokepoint_convertcoords(C, gps, pt + 2, p3d_next, subrect); + gp_strokepoint_convertcoords(C, gpl, gps, pt + 2, p3d_next, subrect); } } @@ -940,7 +959,7 @@ static void gp_stroke_to_bezier(bContext *C, bGPDlayer *gpl, bGPDstroke *gps, Cu /* add points */ for (i = stitch ? 1 : 0, bezt = &nu->bezt[old_nbezt]; i < tot; i++, pt++, bezt++) { - float width = pt->pressure * gpl->thickness * WIDTH_CORR_FAC; + float width = pt->pressure * (gps->thickness + gpl->thickness) * WIDTH_CORR_FAC; if (i || old_nbezt) { interp_v3_v3v3(h1, p3d_cur, p3d_prev, BEZT_HANDLE_FAC); @@ -964,7 +983,7 @@ static void gp_stroke_to_bezier(bContext *C, bGPDlayer *gpl, bGPDstroke *gps, Cu copy_v3_v3(p3d_cur, p3d_next); if (i + 2 < tot) { - gp_strokepoint_convertcoords(C, gps, pt + 2, p3d_next, subrect); + gp_strokepoint_convertcoords(C, gpl, gps, pt + 2, p3d_next, subrect); } prev_bezt = bezt; diff --git a/source/blender/editors/gpencil/gpencil_data.c b/source/blender/editors/gpencil/gpencil_data.c index 746497f0ff5..e915446e461 100644 --- a/source/blender/editors/gpencil/gpencil_data.c +++ b/source/blender/editors/gpencil/gpencil_data.c @@ -18,7 +18,7 @@ * The Original Code is Copyright (C) 2008, Blender Foundation, Joshua Leung * This is a new part of Blender * - * Contributor(s): Joshua Leung + * Contributor(s): Joshua Leung, Antonio Vazquez * * ***** END GPL LICENSE BLOCK ***** * @@ -40,6 +40,8 @@ #include "BLI_blenlib.h" #include "BLI_utildefines.h" +#include "BLI_ghash.h" +#include "BLI_math.h" #include "BLT_translation.h" @@ -57,6 +59,7 @@ #include "BKE_report.h" #include "BKE_scene.h" #include "BKE_screen.h" +#include "BKE_colortools.h" #include "UI_interface.h" #include "UI_resources.h" @@ -72,6 +75,8 @@ #include "gpencil_intern.h" +/* maximum sizes of gp-session buffer */ +#define GP_STROKE_BUFFER_MAX 5000 /* ************************************************ */ /* Datablock Operators */ @@ -596,6 +601,61 @@ void GPENCIL_OT_layer_isolate(wmOperatorType *ot) "In addition to toggling the editability, also affect the visibility"); } +/* ********************** Merge Layer with the next layer **************************** */ + +static int gp_merge_layer_exec(bContext *C, wmOperator *op) +{ + bGPdata *gpd = ED_gpencil_data_get_active(C); + bGPDlayer *gpl_current = gpencil_layer_getactive(gpd); + bGPDlayer *gpl_next = gpl_current->next; + + if (ELEM(NULL, gpd, gpl_current, gpl_next)) { + BKE_report(op->reports, RPT_ERROR, "No layers to merge"); + return OPERATOR_CANCELLED; + } + + /* Collect frames of gpl_current in hash table to avoid O(n^2) lookups */ + GHash *gh_frames_cur = BLI_ghash_int_new_ex(__func__, 64); + for (bGPDframe *gpf = gpl_current->frames.first; gpf; gpf = gpf->next) { + BLI_ghash_insert(gh_frames_cur, SET_INT_IN_POINTER(gpf->framenum), gpf); + } + + /* read all frames from next layer */ + for (bGPDframe *gpf = gpl_next->frames.first; gpf; gpf = gpf->next) { + /* try to find frame in active layer */ + bGPDframe *frame = BLI_ghash_lookup(gh_frames_cur, SET_INT_IN_POINTER(gpf->framenum)); + if (!frame) { + /* nothing found, create new */ + frame = gpencil_frame_addnew(gpl_current, gpf->framenum); + } + /* add to tail all strokes */ + BLI_movelisttolist(&frame->strokes, &gpf->strokes); + } + /* Now delete next layer */ + gpencil_layer_delete(gpd, gpl_next); + BLI_ghash_free(gh_frames_cur, NULL, NULL); + + /* notifiers */ + WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL); + + return OPERATOR_FINISHED; +} + +void GPENCIL_OT_layer_merge(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Merge Down"; + ot->idname = "GPENCIL_OT_layer_merge"; + ot->description = "Merge the current layer with the layer below"; + + /* callbacks */ + ot->exec = gp_merge_layer_exec; + ot->poll = gp_active_layer_poll; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; +} + /* ********************** Change Layer ***************************** */ static int gp_layer_change_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(evt)) @@ -663,3 +723,1740 @@ void GPENCIL_OT_layer_change(wmOperatorType *ot) } /* ************************************************ */ + +/* ******************* Arrange Stroke Up/Down in drawing order ************************** */ + +enum { + GP_STROKE_MOVE_UP = -1, + GP_STROKE_MOVE_DOWN = 1, + GP_STROKE_MOVE_TOP = 2, + GP_STROKE_MOVE_BOTTOM = 3 +}; + +static int gp_stroke_arrange_exec(bContext *C, wmOperator *op) +{ + bGPdata *gpd = ED_gpencil_data_get_active(C); + bGPDlayer *gpl = gpencil_layer_getactive(gpd); + bGPDstroke *gps; + + /* sanity checks */ + if (ELEM(NULL, gpd, gpl, gpl->actframe)) { + return OPERATOR_CANCELLED; + } + + bGPDframe *gpf = gpl->actframe; + /* temp listbase to store selected strokes */ + ListBase selected = {NULL}; + const int direction = RNA_enum_get(op->ptr, "type"); + + /* verify if any selected stroke is in the extreme of the stack and select to move */ + for (gps = gpf->strokes.first; gps; gps = gps->next) { + /* only if selected */ + if (gps->flag & GP_STROKE_SELECT) { + /* 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(gpl, gps) == false) { + continue; + } + /* some stroke is already at front*/ + if ((direction == GP_STROKE_MOVE_TOP) || (direction == GP_STROKE_MOVE_UP)) { + if (gps == gpf->strokes.last) { + BKE_report(op->reports, RPT_ERROR, "Some selected stroke is already on top"); + return OPERATOR_CANCELLED; + } + } + /* some stroke is already at botom */ + if ((direction == GP_STROKE_MOVE_BOTTOM) || (direction == GP_STROKE_MOVE_DOWN)) { + if (gps == gpf->strokes.first) { + BKE_report(op->reports, RPT_ERROR, "Some selected stroke is already on bottom"); + return OPERATOR_CANCELLED; + } + } + /* add to list */ + BLI_addtail(&selected, BLI_genericNodeN(gps)); + } + } + + /* Now do the movement of the stroke */ + switch (direction) { + /* Bring to Front */ + case GP_STROKE_MOVE_TOP: + for (LinkData *link = selected.first; link; link = link->next) { + gps = link->data; + BLI_remlink(&gpf->strokes, gps); + BLI_insertlinkafter(&gpf->strokes, gpf->strokes.last, gps); + } + break; + /* Bring Forward */ + case GP_STROKE_MOVE_UP: + for (LinkData *link = selected.last; link; link = link->prev) { + gps = link->data; + BLI_remlink(&gpf->strokes, gps); + BLI_insertlinkafter(&gpf->strokes, gps->next, gps); + } + break; + /* Send Backward */ + case GP_STROKE_MOVE_DOWN: + for (LinkData *link = selected.first; link; link = link->next) { + gps = link->data; + BLI_remlink(&gpf->strokes, gps); + BLI_insertlinkbefore(&gpf->strokes, gps->prev, gps); + } + break; + /* Send to Back */ + case GP_STROKE_MOVE_BOTTOM: + for (LinkData *link = selected.last; link; link = link->prev) { + gps = link->data; + BLI_remlink(&gpf->strokes, gps); + BLI_insertlinkbefore(&gpf->strokes, gpf->strokes.first, gps); + } + break; + default: + BLI_assert(0); + break; + } + /* notifiers */ + WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL); + + return OPERATOR_FINISHED; +} + +void GPENCIL_OT_stroke_arrange(wmOperatorType *ot) +{ + static EnumPropertyItem slot_move[] = { + {GP_STROKE_MOVE_UP, "UP", 0, "Bring Forward", ""}, + {GP_STROKE_MOVE_DOWN, "DOWN", 0, "Send Backward", ""}, + {GP_STROKE_MOVE_TOP, "TOP", 0, "Bring to Front", ""}, + {GP_STROKE_MOVE_BOTTOM, "BOTTOM", 0, "Send to Back", ""}, + {0, NULL, 0, NULL, NULL } + }; + + /* identifiers */ + ot->name = "Arrange Stroke"; + ot->idname = "GPENCIL_OT_stroke_arrange"; + ot->description = "Arrange selected strokes up/down in the drawing order of the active layer"; + + /* api callbacks */ + ot->exec = gp_stroke_arrange_exec; + ot->poll = gp_active_layer_poll; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + ot->prop = RNA_def_enum(ot->srna, "direction", slot_move, GP_STROKE_MOVE_UP, "Direction", ""); +} +/* ******************* Move Stroke to new color ************************** */ + +static int gp_stroke_change_color_exec(bContext *C, wmOperator *UNUSED(op)) +{ + bGPdata *gpd = ED_gpencil_data_get_active(C); + bGPDpalette *palette; + bGPDpalettecolor *color; + + /* sanity checks */ + if (ELEM(NULL, gpd)) { + return OPERATOR_CANCELLED; + } + + palette = gpencil_palette_getactive(gpd); + color = gpencil_palettecolor_getactive(palette); + if (ELEM(NULL, palette, color)) { + return OPERATOR_CANCELLED; + } + + /* loop all strokes */ + for (bGPDlayer *gpl = gpd->layers.first; gpl; gpl = gpl->next) { + /* only editable and visible layers are considered */ + if (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) { + /* 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(gpl, gps) == false) + continue; + + /* asign new color (only if different) */ + if (STREQ(gps->colorname, color->info) == false) { + strcpy(gps->colorname, color->info); + gps->flag |= GP_STROKE_RECALC_COLOR; + } + } + } + } + } + /* notifiers */ + WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL); + + return OPERATOR_FINISHED; +} + +void GPENCIL_OT_stroke_change_color(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Change Stroke Color"; + ot->idname = "GPENCIL_OT_stroke_change_color"; + ot->description = "Move selected strokes to active color"; + + /* api callbacks */ + ot->exec = gp_stroke_change_color_exec; + ot->poll = gp_active_layer_poll; +} + +/* ******************* Lock color of non selected Strokes colors ************************** */ + +static int gp_stroke_lock_color_exec(bContext *C, wmOperator *UNUSED(op)) +{ + bGPdata *gpd = ED_gpencil_data_get_active(C); + bGPDpalette *palette; + + /* sanity checks */ + if (ELEM(NULL, gpd)) + return OPERATOR_CANCELLED; + + palette = gpencil_palette_getactive(gpd); + if (ELEM(NULL, palette)) + return OPERATOR_CANCELLED; + + /* first lock all colors */ + for (bGPDpalettecolor *palcolor = palette->colors.first; palcolor; palcolor = palcolor->next) { + palcolor->flag |= PC_COLOR_LOCKED; + } + + /* loop all selected strokes and unlock any color */ + for (bGPDlayer *gpl = gpd->layers.first; gpl; gpl = gpl->next) { + /* only editable and visible layers are considered */ + if (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) { + /* skip strokes that are invalid for current view */ + if (ED_gpencil_stroke_can_use(C, gps) == false) { + continue; + } + /* unlock color */ + if (gps->palcolor != NULL) { + gps->palcolor->flag &= ~PC_COLOR_LOCKED; + } + } + } + } + } + /* notifiers */ + WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL); + + return OPERATOR_FINISHED; +} + +void GPENCIL_OT_stroke_lock_color(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Lock Unused Colors"; + ot->idname = "GPENCIL_OT_stroke_lock_color"; + ot->description = "Lock any color not used in any selected stroke"; + + /* api callbacks */ + ot->exec = gp_stroke_lock_color_exec; + ot->poll = gp_active_layer_poll; +} + +/* ******************* Apply layer thickness change to Strokes ************************** */ + +static int gp_stroke_apply_thickness_exec(bContext *C, wmOperator *UNUSED(op)) +{ + bGPdata *gpd = ED_gpencil_data_get_active(C); + bGPDlayer *gpl = gpencil_layer_getactive(gpd); + + /* sanity checks */ + if (ELEM(NULL, gpd, gpl, gpl->frames.first)) + return OPERATOR_CANCELLED; + + /* loop all strokes */ + for (bGPDframe *gpf = gpl->frames.first; gpf; gpf = gpf->next) { + for (bGPDstroke *gps = gpf->strokes.first; gps; gps = gps->next) { + /* Apply thickness */ + gps->thickness = gps->thickness + gpl->thickness; + } + } + /* clear value */ + gpl->thickness = 0.0f; + + /* notifiers */ + WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL); + + return OPERATOR_FINISHED; +} + +void GPENCIL_OT_stroke_apply_thickness(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Apply Stroke Thickness"; + ot->idname = "GPENCIL_OT_stroke_apply_thickness"; + ot->description = "Apply the thickness change of the layer to its strokes"; + + /* api callbacks */ + ot->exec = gp_stroke_apply_thickness_exec; + ot->poll = gp_active_layer_poll; +} + +/* ******************* Close Strokes ************************** */ + +enum { + GP_STROKE_CYCLIC_CLOSE = 1, + GP_STROKE_CYCLIC_OPEN = 2, + GP_STROKE_CYCLIC_TOGGLE = 3 +}; + +static int gp_stroke_cyclical_set_exec(bContext *C, wmOperator *op) +{ + bGPdata *gpd = ED_gpencil_data_get_active(C); + const int type = RNA_enum_get(op->ptr, "type"); + + /* sanity checks */ + if (ELEM(NULL, gpd)) + return OPERATOR_CANCELLED; + + /* loop all selected strokes */ + CTX_DATA_BEGIN(C, bGPDlayer *, gpl, editable_gpencil_layers) + { + for (bGPDstroke *gps = gpl->actframe->strokes.last; gps; gps = gps->prev) { + bGPDpalettecolor *palcolor = gps->palcolor; + + /* skip strokes that are not selected or invalid for current view */ + if (((gps->flag & GP_STROKE_SELECT) == 0) || ED_gpencil_stroke_can_use(C, gps) == false) + continue; + /* skip hidden or locked colors */ + if (!palcolor || (palcolor->flag & PC_COLOR_HIDE) || (palcolor->flag & PC_COLOR_LOCKED)) + continue; + + switch (type) { + case GP_STROKE_CYCLIC_CLOSE: + /* Close all (enable) */ + gps->flag |= GP_STROKE_CYCLIC; + break; + case GP_STROKE_CYCLIC_OPEN: + /* Open all (disable) */ + gps->flag &= ~GP_STROKE_CYCLIC; + break; + case GP_STROKE_CYCLIC_TOGGLE: + /* Just toggle flag... */ + gps->flag ^= GP_STROKE_CYCLIC; + break; + default: + BLI_assert(0); + break; + } + } + } + CTX_DATA_END; + + /* notifiers */ + WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL); + + return OPERATOR_FINISHED; +} + +/** + * Similar to #CURVE_OT_cyclic_toggle or #MASK_OT_cyclic_toggle, but with + * option to force opened/closed strokes instead of just toggle behavior. + */ +void GPENCIL_OT_stroke_cyclical_set(wmOperatorType *ot) +{ + static EnumPropertyItem cyclic_type[] = { + {GP_STROKE_CYCLIC_CLOSE, "CLOSE", 0, "Close all", ""}, + {GP_STROKE_CYCLIC_OPEN, "OPEN", 0, "Open all", ""}, + {GP_STROKE_CYCLIC_TOGGLE, "TOGGLE", 0, "Toggle", ""}, + {0, NULL, 0, NULL, NULL} + }; + + /* identifiers */ + ot->name = "Set Cyclical State"; + ot->idname = "GPENCIL_OT_stroke_cyclical_set"; + ot->description = "Close or open the selected stroke adding an edge from last to first point"; + + /* api callbacks */ + ot->exec = gp_stroke_cyclical_set_exec; + ot->poll = gp_active_layer_poll; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + ot->prop = RNA_def_enum(ot->srna, "type", cyclic_type, GP_STROKE_CYCLIC_TOGGLE, "Type", ""); +} + +/* ******************* Stroke join ************************** */ + +/* Helper: flip stroke */ +static void gpencil_flip_stroke(bGPDstroke *gps) +{ + bGPDspoint pt, *point, *point2; + int end = gps->totpoints - 1; + + for (int i = 0; i < gps->totpoints / 2; i++) { + /* save first point */ + point = &gps->points[i]; + pt.x = point->x; + pt.y = point->y; + pt.z = point->z; + pt.flag = point->flag; + pt.pressure = point->pressure; + pt.strength = point->strength; + pt.time = point->time; + + /* replace first point with last point */ + point2 = &gps->points[end]; + point->x = point2->x; + point->y = point2->y; + point->z = point2->z; + point->flag = point2->flag; + point->pressure = point2->pressure; + point->strength = point2->strength; + point->time = point2->time; + + /* replace last point with first saved before */ + point = &gps->points[end]; + point->x = pt.x; + point->y = pt.y; + point->z = pt.z; + point->flag = pt.flag; + point->pressure = pt.pressure; + point->strength = pt.strength; + point->time = pt.time; + + end--; + } +} + +/* Helper: copy point between strokes */ +static void gpencil_stroke_copy_point(bGPDstroke *gps, bGPDspoint *point, float delta[3], + float pressure, float strength, float deltatime) +{ + bGPDspoint *newpoint; + + gps->points = MEM_reallocN(gps->points, sizeof(bGPDspoint) * (gps->totpoints + 1)); + gps->totpoints++; + + newpoint = &gps->points[gps->totpoints - 1]; + newpoint->x = point->x * delta[0]; + newpoint->y = point->y * delta[1]; + newpoint->z = point->z * delta[2]; + newpoint->flag = point->flag; + newpoint->pressure = pressure; + newpoint->strength = strength; + newpoint->time = point->time + deltatime; +} + +/* Helper: join two strokes using the shortest distance (reorder stroke if necessary ) */ +static void gpencil_stroke_join_strokes(bGPDstroke *gps_a, bGPDstroke *gps_b) +{ + bGPDspoint point, *pt; + int i; + float delta[3] = {1.0f, 1.0f, 1.0f}; + float deltatime = 0.0f; + + /* sanity checks */ + if (ELEM(NULL, gps_a, gps_b)) + return; + + if ((gps_a->totpoints == 0) || (gps_b->totpoints == 0)) + return; + + /* define start and end points of each stroke */ + float sa[3], sb[3], ea[3], eb[3]; + pt = &gps_a->points[0]; + copy_v3_v3(sa, &pt->x); + + pt = &gps_a->points[gps_a->totpoints - 1]; + copy_v3_v3(ea, &pt->x); + + pt = &gps_b->points[0]; + copy_v3_v3(sb, &pt->x); + + pt = &gps_b->points[gps_b->totpoints - 1]; + copy_v3_v3(eb, &pt->x); + /* review if need flip stroke B */ + float ea_sb = len_squared_v3v3(ea, sb); + float ea_eb = len_squared_v3v3(ea, eb); + /* flip if distance to end point is shorter */ + if (ea_eb < ea_sb) { + gpencil_flip_stroke(gps_b); + } + + /* 1st: add one tail point to start invisible area */ + point = gps_a->points[gps_a->totpoints - 1]; + deltatime = point.time; + gpencil_stroke_copy_point(gps_a, &point, delta, 0.0f, 0.0f, 0.0f); + + /* 2nd: add one head point to finish invisible area */ + point = gps_b->points[0]; + gpencil_stroke_copy_point(gps_a, &point, delta, 0.0f, 0.0f, deltatime); + + /* 3rd: add all points */ + for (i = 0, pt = gps_b->points; i < gps_b->totpoints && pt; i++, pt++) { + /* check if still room in buffer */ + if (gps_a->totpoints <= GP_STROKE_BUFFER_MAX - 2) { + gpencil_stroke_copy_point(gps_a, pt, delta, pt->pressure, pt->strength, deltatime); + } + } +} + +enum { + GP_STROKE_JOIN = -1, + GP_STROKE_JOINCOPY = 1 +}; + +static int gp_stroke_join_exec(bContext *C, wmOperator *op) +{ + bGPdata *gpd = ED_gpencil_data_get_active(C); + bGPDlayer *activegpl = gpencil_layer_getactive(gpd); + bGPDstroke *gps, *gpsn; + bGPDpalette *palette = gpencil_palette_getactive(gpd); + bGPDpalettecolor *palcolor = gpencil_palettecolor_getactive(palette); + + bGPDframe *gpf_a = NULL; + bGPDstroke *stroke_a = NULL; + bGPDstroke *stroke_b = NULL; + bGPDstroke *new_stroke = NULL; + + int type = RNA_enum_get(op->ptr, "type"); + + /* sanity checks */ + if (ELEM(NULL, gpd)) + return OPERATOR_CANCELLED; + + if (activegpl->flag & GP_LAYER_LOCKED) + return OPERATOR_CANCELLED; + + BLI_assert(ELEM(type, GP_STROKE_JOIN, GP_STROKE_JOINCOPY)); + + + /* read all selected strokes */ + bool first = false; + CTX_DATA_BEGIN(C, bGPDlayer *, gpl, editable_gpencil_layers) + { + bGPDframe *gpf = gpl->actframe; + for (gps = gpf->strokes.first; gps; gps = gpsn) { + gpsn = gps->next; + if (gps->flag & GP_STROKE_SELECT) { + /* 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(gpl, gps) == false) { + continue; + } + /* to join strokes, cyclic must be disabled */ + gps->flag &= ~GP_STROKE_CYCLIC; + /* saves first frame and stroke */ + if (!first) { + first = true; + gpf_a = gpf; + stroke_a = gps; + } + else { + stroke_b = gps; + /* 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); + new_stroke->triangles = NULL; + new_stroke->tot_triangles = 0; + new_stroke->flag |= GP_STROKE_RECALC_CACHES; + /* if new, set current color */ + if (type == GP_STROKE_JOINCOPY) { + new_stroke->palcolor = palcolor; + strcpy(new_stroke->colorname, palcolor->info); + new_stroke->flag |= GP_STROKE_RECALC_COLOR; + } + } + /* join new_stroke and stroke B. New stroke will contain all the previous data */ + gpencil_stroke_join_strokes(new_stroke, stroke_b); + + /* if join only, delete old strokes */ + if (type == GP_STROKE_JOIN) { + if (stroke_a) { + BLI_insertlinkbefore(&gpf_a->strokes, stroke_a, new_stroke); + BLI_remlink(&gpf->strokes, stroke_a); + free_gpencil_stroke(stroke_a); + stroke_a = NULL; + } + if (stroke_b) { + BLI_remlink(&gpf->strokes, stroke_b); + free_gpencil_stroke(stroke_b); + stroke_b = NULL; + } + } + } + } + } + } + CTX_DATA_END; + /* add new stroke if was not added before */ + if (type == GP_STROKE_JOINCOPY) { + if (new_stroke) { + /* Add a new frame if needed */ + if (activegpl->actframe == NULL) + activegpl->actframe = gpencil_frame_addnew(activegpl, gpf_a->framenum); + + BLI_addtail(&activegpl->actframe->strokes, new_stroke); + } + } + + /* notifiers */ + WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL); + + return OPERATOR_FINISHED; +} + +void GPENCIL_OT_stroke_join(wmOperatorType *ot) +{ + static EnumPropertyItem join_type[] = { + {GP_STROKE_JOIN, "JOIN", 0, "Join", ""}, + {GP_STROKE_JOINCOPY, "JOINCOPY", 0, "Join and Copy", ""}, + {0, NULL, 0, NULL, NULL} + }; + + /* identifiers */ + ot->name = "Join Strokes"; + ot->idname = "GPENCIL_OT_stroke_join"; + ot->description = "Join selected strokes (optionally as new stroke)"; + + /* api callbacks */ + ot->exec = gp_stroke_join_exec; + ot->poll = gp_active_layer_poll; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + ot->prop = RNA_def_enum(ot->srna, "type", join_type, GP_STROKE_JOIN, "Type", ""); +} + +/* ******************* Stroke flip ************************** */ + +static int gp_stroke_flip_exec(bContext *C, wmOperator *UNUSED(op)) +{ + bGPdata *gpd = ED_gpencil_data_get_active(C); + + /* sanity checks */ + if (ELEM(NULL, gpd)) + return OPERATOR_CANCELLED; + + /* read all selected strokes */ + CTX_DATA_BEGIN(C, bGPDlayer *, gpl, editable_gpencil_layers) + { + bGPDframe *gpf = gpl->actframe; + for (bGPDstroke *gps = gpf->strokes.first; gps; gps = gps->next) { + if (gps->flag & GP_STROKE_SELECT) { + /* 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(gpl, gps) == false) { + continue; + } + /* flip stroke */ + gpencil_flip_stroke(gps); + } + } + } + CTX_DATA_END; + + /* notifiers */ + WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL); + + return OPERATOR_FINISHED; +} + +void GPENCIL_OT_stroke_flip(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Flip Stroke"; + ot->idname = "GPENCIL_OT_stroke_flip"; + ot->description = "Change drawing direction of selected strokes"; + + /* api callbacks */ + ot->exec = gp_stroke_flip_exec; + ot->poll = gp_active_layer_poll; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; +} + +/* ************************************************ */ +/* Drawing Brushes Operators */ + +/* ******************* Add New Brush ************************ */ + +/* add new brush - wrapper around API */ +static int gp_brush_add_exec(bContext *C, wmOperator *op) +{ + ToolSettings *ts = CTX_data_tool_settings(C); + + /* if there's no existing container */ + if (ts == NULL) { + BKE_report(op->reports, RPT_ERROR, "Nowhere for brush data to go"); + return OPERATOR_CANCELLED; + } + /* add new brush now */ + gpencil_brush_addnew(ts, DATA_("GP_Brush"), true); + + /* notifiers */ + WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL); + + return OPERATOR_FINISHED; +} + +void GPENCIL_OT_brush_add(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Add Brush"; + ot->idname = "GPENCIL_OT_brush_add"; + ot->description = "Add new Grease Pencil drawing brush for the active Grease Pencil datablock"; + + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + /* callbacks */ + ot->exec = gp_brush_add_exec; + ot->poll = gp_add_poll; +} + +/* ******************* Remove Active Brush ************************* */ + +static int gp_brush_remove_exec(bContext *C, wmOperator *op) +{ + ToolSettings *ts = CTX_data_tool_settings(C); + bGPDbrush *brush = gpencil_brush_getactive(ts); + + /* sanity checks */ + if (ELEM(NULL, ts, brush)) + return OPERATOR_CANCELLED; + + if (BLI_listbase_count(&ts->gp_brushes) < 2) { + BKE_report(op->reports, RPT_ERROR, "Grease Pencil needs a brush. Unable to delete brush"); + return OPERATOR_CANCELLED; + } + + + /* make the brush before this the new active brush + * - use the one after if this is the first + * - if this is the only brush, this naturally becomes NULL + */ + if (brush->prev) + gpencil_brush_setactive(ts, brush->prev); + else + gpencil_brush_setactive(ts, brush->next); + + /* delete the brush now... */ + gpencil_brush_delete(ts, brush); + + /* notifiers */ + WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL); + + return OPERATOR_FINISHED; +} + +void GPENCIL_OT_brush_remove(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Remove brush"; + ot->idname = "GPENCIL_OT_brush_remove"; + ot->description = "Remove active Grease Pencil drawing brush"; + + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + /* callbacks */ + ot->exec = gp_brush_remove_exec; + ot->poll = gp_active_brush_poll; +} + +/* ********************** Change Brush ***************************** */ + +static int gp_brush_change_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(evt)) +{ + uiPopupMenu *pup; + uiLayout *layout; + + /* call the menu, which will call this operator again, hence the canceled */ + pup = UI_popup_menu_begin(C, op->type->name, ICON_NONE); + layout = UI_popup_menu_layout(pup); + uiItemsEnumO(layout, "GPENCIL_OT_brush_change", "brush"); + UI_popup_menu_end(C, pup); + + return OPERATOR_INTERFACE; +} + +static int gp_brush_change_exec(bContext *C, wmOperator *op) +{ + ToolSettings *ts = CTX_data_tool_settings(C); + bGPDbrush *brush = NULL; + int brush_num = RNA_enum_get(op->ptr, "brush"); + + /* Get brush or create new one */ + if (brush_num == -1) { + /* Create brush */ + brush = gpencil_brush_addnew(ts, DATA_("GP_Brush"), true); + } + else { + /* Try to get brush */ + brush = BLI_findlink(&ts->gp_brushes, brush_num); + + if (brush == NULL) { + BKE_reportf(op->reports, RPT_ERROR, "Cannot change to non-existent brush (index = %d)", brush_num); + return OPERATOR_CANCELLED; + } + } + + /* Set active brush */ + gpencil_brush_setactive(ts, brush); + + /* updates */ + WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL); + + return OPERATOR_FINISHED; +} + +void GPENCIL_OT_brush_change(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Change Brush"; + ot->idname = "GPENCIL_OT_brush_change"; + ot->description = "Change active Grease Pencil drawing brush"; + + /* callbacks */ + ot->invoke = gp_brush_change_invoke; + ot->exec = gp_brush_change_exec; + ot->poll = gp_active_brush_poll; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + /* gp brush to use (dynamic enum) */ + ot->prop = RNA_def_enum(ot->srna, "brush", DummyRNA_DEFAULT_items, 0, "Grease Pencil Brush", ""); + RNA_def_enum_funcs(ot->prop, ED_gpencil_brushes_enum_itemf); +} + +/* ******************* Move Brush Up/Down ************************** */ + +enum { + GP_BRUSH_MOVE_UP = -1, + GP_BRUSH_MOVE_DOWN = 1 +}; + +static int gp_brush_move_exec(bContext *C, wmOperator *op) +{ + ToolSettings *ts = CTX_data_tool_settings(C); + bGPDbrush *brush = gpencil_brush_getactive(ts); + + int direction = RNA_enum_get(op->ptr, "type"); + + /* sanity checks */ + if (ELEM(NULL, ts, brush)) { + return OPERATOR_CANCELLED; + } + + /* up or down? */ + if (direction == GP_BRUSH_MOVE_UP) { + /* up */ + BLI_remlink(&ts->gp_brushes, brush); + BLI_insertlinkbefore(&ts->gp_brushes, brush->prev, brush); + } + else if (direction == GP_BRUSH_MOVE_DOWN) { + /* down */ + BLI_remlink(&ts->gp_brushes, brush); + BLI_insertlinkafter(&ts->gp_brushes, brush->next, brush); + } + else { + BLI_assert(0); + } + + /* notifiers */ + WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL); + + return OPERATOR_FINISHED; +} + +void GPENCIL_OT_brush_move(wmOperatorType *ot) +{ + static EnumPropertyItem slot_move[] = { + {GP_BRUSH_MOVE_UP, "UP", 0, "Up", ""}, + {GP_BRUSH_MOVE_DOWN, "DOWN", 0, "Down", ""}, + {0, NULL, 0, NULL, NULL } + }; + + /* identifiers */ + ot->name = "Move Brush"; + ot->idname = "GPENCIL_OT_brush_move"; + ot->description = "Move the active Grease Pencil drawing brush up/down in the list"; + + /* api callbacks */ + ot->exec = gp_brush_move_exec; + ot->poll = gp_active_brush_poll; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + ot->prop = RNA_def_enum(ot->srna, "type", slot_move, GP_BRUSH_MOVE_UP, "Type", ""); +} + +/* ******************* Brush create presets ************************** */ + +static int gp_brush_presets_create_exec(bContext *C, wmOperator *UNUSED(op)) +{ + ToolSettings *ts = CTX_data_tool_settings(C); + gpencil_brush_init_presets(ts); + + /* notifiers */ + WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL); + + return OPERATOR_FINISHED; +} + +void GPENCIL_OT_brush_presets_create(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"; + + /* api callbacks */ + ot->exec = gp_brush_presets_create_exec; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + +} + +/* ***************** Copy Brush ************************ */ + +static int gp_brush_copy_exec(bContext *C, wmOperator *op) +{ + ToolSettings *ts = CTX_data_tool_settings(C); + + /* if there's no existing container */ + if (ts == NULL) { + BKE_report(op->reports, RPT_ERROR, "Nowhere for brush data to go"); + return OPERATOR_CANCELLED; + } + + bGPDbrush *brush = gpencil_brush_getactive(ts); + bGPDbrush *newbrush; + + /* sanity checks */ + if (ELEM(NULL, brush)) + return OPERATOR_CANCELLED; + + /* create a brush and duplicate data */ + newbrush = gpencil_brush_addnew(ts, brush->info, true); + newbrush->thickness = brush->thickness; + newbrush->draw_smoothfac = brush->draw_smoothfac; + newbrush->draw_smoothlvl = brush->draw_smoothlvl; + newbrush->sublevel = brush->sublevel; + newbrush->flag = brush->flag; + newbrush->draw_sensitivity = brush->draw_sensitivity; + newbrush->draw_strength = brush->draw_strength; + newbrush->draw_jitter = brush->draw_jitter; + newbrush->draw_angle = brush->draw_angle; + newbrush->draw_angle_factor = brush->draw_angle_factor; + newbrush->draw_random_press = brush->draw_random_press; + newbrush->draw_random_sub = brush->draw_random_sub; + + /* free automatic curves created by default (replaced by copy) */ + curvemapping_free(newbrush->cur_sensitivity); + curvemapping_free(newbrush->cur_strength); + curvemapping_free(newbrush->cur_jitter); + + /* make a copy of curves */ + newbrush->cur_sensitivity = curvemapping_copy(brush->cur_sensitivity); + newbrush->cur_strength = curvemapping_copy(brush->cur_strength); + newbrush->cur_jitter = curvemapping_copy(brush->cur_jitter); + + gpencil_brush_setactive(ts, newbrush); + /* notifiers */ + WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL); + + return OPERATOR_FINISHED; +} + +void GPENCIL_OT_brush_copy(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Copy Brush"; + ot->idname = "GPENCIL_OT_brush_copy"; + ot->description = "Copy current Grease Pencil drawing brush"; + + /* callbacks */ + ot->exec = gp_brush_copy_exec; + ot->poll = gp_active_brush_poll; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; +} + +/* ***************** Select Brush ************************ */ + +static int gp_brush_select_exec(bContext *C, wmOperator *op) +{ + ToolSettings *ts = CTX_data_tool_settings(C); + + /* if there's no existing container */ + if (ts == NULL) { + BKE_report(op->reports, RPT_ERROR, "Nowhere to go"); + return OPERATOR_CANCELLED; + } + + const int index = RNA_int_get(op->ptr, "index"); + bGPDbrush *brush = BLI_findlink(&ts->gp_brushes, index); + /* sanity checks */ + if (ELEM(NULL, brush)) { + return OPERATOR_CANCELLED; + } + + gpencil_brush_setactive(ts, brush); + + /* notifiers */ + WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL); + + return OPERATOR_FINISHED; +} + +void GPENCIL_OT_brush_select(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Select Brush"; + ot->idname = "GPENCIL_OT_brush_select"; + ot->description = "Select a Grease Pencil drawing brush"; + + /* callbacks */ + ot->exec = gp_brush_select_exec; + ot->poll = gp_active_brush_poll; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + /* properties */ + RNA_def_int(ot->srna, "index", 0, 0, INT_MAX, "Index", "Index of Drawing Brush", 0, INT_MAX); +} + +/* ************************************************ */ +/* Palette Operators */ + +/* ******************* Add New Palette ************************ */ + +/* add new palette - wrapper around API */ +static int gp_palette_add_exec(bContext *C, wmOperator *op) +{ + bGPdata **gpd_ptr = ED_gpencil_data_get_pointers(C, NULL); + + /* if there's no existing Grease-Pencil data there, add some */ + if (gpd_ptr == NULL) { + BKE_report(op->reports, RPT_ERROR, "Nowhere for grease pencil data to go"); + return OPERATOR_CANCELLED; + } + if (*gpd_ptr == NULL) + *gpd_ptr = gpencil_data_addnew(DATA_("GPencil")); + + /* add new palette now */ + gpencil_palette_addnew(*gpd_ptr, DATA_("GP_Palette"), true); + + /* notifiers */ + WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL); + + return OPERATOR_FINISHED; +} + +void GPENCIL_OT_palette_add(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Add Palette"; + ot->idname = "GPENCIL_OT_palette_add"; + ot->description = "Add new Grease Pencil palette for the active Grease Pencil datablock"; + + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + /* callbacks */ + ot->exec = gp_palette_add_exec; + ot->poll = gp_add_poll; +} + +/* ******************* Remove Active Palette ************************* */ + +static int gp_palette_remove_exec(bContext *C, wmOperator *op) +{ + bGPdata *gpd = ED_gpencil_data_get_active(C); + bGPDpalette *palette = gpencil_palette_getactive(gpd); + + /* sanity checks */ + if (ELEM(NULL, gpd, palette)) + return OPERATOR_CANCELLED; + + if (BLI_listbase_count(&gpd->palettes) < 2) { + BKE_report(op->reports, RPT_ERROR, "Grease Pencil needs a palette. Unable to delete palette"); + return OPERATOR_CANCELLED; + } + + + /* make the palette before this the new active palette + * - use the one after if this is the first + * - if this is the only palette, this naturally becomes NULL + */ + if (palette->prev) + gpencil_palette_setactive(gpd, palette->prev); + else + gpencil_palette_setactive(gpd, palette->next); + + /* delete the palette now... */ + gpencil_palette_delete(gpd, palette); + + /* notifiers */ + WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL); + + return OPERATOR_FINISHED; +} + +void GPENCIL_OT_palette_remove(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Remove palette"; + ot->idname = "GPENCIL_OT_palette_remove"; + ot->description = "Remove active Grease Pencil palette"; + + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + /* callbacks */ + ot->exec = gp_palette_remove_exec; + ot->poll = gp_active_palette_poll; +} + +/* ********************** Change Palette ***************************** */ + +static int gp_palette_change_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(evt)) +{ + uiPopupMenu *pup; + uiLayout *layout; + + /* call the menu, which will call this operator again, hence the canceled */ + pup = UI_popup_menu_begin(C, op->type->name, ICON_NONE); + layout = UI_popup_menu_layout(pup); + uiItemsEnumO(layout, "GPENCIL_OT_palette_change", "palette"); + UI_popup_menu_end(C, pup); + + return OPERATOR_INTERFACE; +} + +static int gp_palette_change_exec(bContext *C, wmOperator *op) +{ + bGPdata *gpd = CTX_data_gpencil_data(C); + bGPDpalette *palette = NULL; + int palette_num = RNA_enum_get(op->ptr, "palette"); + + /* Get palette or create new one */ + if (palette_num == -1) { + /* Create palette */ + palette = gpencil_palette_addnew(gpd, DATA_("GP_Palette"), true); + } + else { + /* Try to get palette */ + palette = BLI_findlink(&gpd->palettes, palette_num); + + if (palette == NULL) { + BKE_reportf(op->reports, RPT_ERROR, "Cannot change to non-existent palette (index = %d)", palette_num); + return OPERATOR_CANCELLED; + } + } + + /* Set active palette */ + gpencil_palette_setactive(gpd, palette); + + /* updates */ + WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL); + + return OPERATOR_FINISHED; +} + +void GPENCIL_OT_palette_change(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Change Palette"; + ot->idname = "GPENCIL_OT_palette_change"; + ot->description = "Change active Grease Pencil palette"; + + /* callbacks */ + ot->invoke = gp_palette_change_invoke; + ot->exec = gp_palette_change_exec; + ot->poll = gp_active_palette_poll; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + /* gp palette to use (dynamic enum) */ + ot->prop = RNA_def_enum(ot->srna, "palette", DummyRNA_DEFAULT_items, 0, "Grease Pencil Palette", ""); + RNA_def_enum_funcs(ot->prop, ED_gpencil_palettes_enum_itemf); +} + +/* ******************* Lock and hide any color non used in current layer ************************** */ + +static int gp_palette_lock_layer_exec(bContext *C, wmOperator *UNUSED(op)) +{ + bGPdata *gpd = ED_gpencil_data_get_active(C); + bGPDpalette *palette; + + /* sanity checks */ + if (ELEM(NULL, gpd)) + return OPERATOR_CANCELLED; + + palette = gpencil_palette_getactive(gpd); + if (ELEM(NULL, palette)) + return OPERATOR_CANCELLED; + + /* first lock and hide all colors */ + for (bGPDpalettecolor *palcolor = palette->colors.first; palcolor; palcolor = palcolor->next) { + palcolor->flag |= PC_COLOR_LOCKED; + palcolor->flag |= PC_COLOR_HIDE; + } + + /* loop all selected strokes and unlock any color used in active layer */ + for (bGPDlayer *gpl = gpd->layers.first; gpl; gpl = gpl->next) { + /* only editable and visible layers are considered */ + if (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 */ + if (ED_gpencil_stroke_can_use(C, gps) == false) + continue; + + /* unlock/unhide color if not unlocked before */ + if (gps->palcolor != NULL) { + gps->palcolor->flag &= ~PC_COLOR_LOCKED; + gps->palcolor->flag &= ~PC_COLOR_HIDE; + } + } + } + } + /* notifiers */ + WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL); + + return OPERATOR_FINISHED; +} + +void GPENCIL_OT_palette_lock_layer(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Disable Unused Layer Colors"; + ot->idname = "GPENCIL_OT_palette_lock_layer"; + ot->description = "Lock and hide any color not used in any layer"; + + /* api callbacks */ + ot->exec = gp_palette_lock_layer_exec; + ot->poll = gp_active_layer_poll; +} + +/* ************************************************ */ +/* Palette Colors Operators */ + +/* ******************* Add New Palette ************************ */ + +/* add new palette - wrapper around API */ +static int gp_palettecolor_add_exec(bContext *C, wmOperator *op) +{ + bGPdata **gpd_ptr = ED_gpencil_data_get_pointers(C, NULL); + + /* if there's no existing Grease-Pencil data there, add some */ + if (gpd_ptr == NULL) { + BKE_report(op->reports, RPT_ERROR, "Nowhere for grease pencil data to go"); + return OPERATOR_CANCELLED; + } + if (*gpd_ptr == NULL) + *gpd_ptr = gpencil_data_addnew(DATA_("GPencil")); + + /* verify palette */ + bGPDpalette *palette = gpencil_palette_getactive(*gpd_ptr); + if (palette == NULL) + palette = gpencil_palette_addnew(*gpd_ptr, DATA_("GP_Palette"), true); + + /* add new palette color now */ + gpencil_palettecolor_addnew(palette, DATA_("Color"), true); + + /* notifiers */ + WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL); + + return OPERATOR_FINISHED; +} + +void GPENCIL_OT_palettecolor_add(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Add Palette Color"; + ot->idname = "GPENCIL_OT_palettecolor_add"; + ot->description = "Add new Grease Pencil palette color for the active Grease Pencil datablock"; + + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + /* callbacks */ + ot->exec = gp_palettecolor_add_exec; + ot->poll = gp_add_poll; +} + +/* ******************* Remove Active Palette color ************************* */ + +static int gp_palettecolor_remove_exec(bContext *C, wmOperator *UNUSED(op)) +{ + bGPdata *gpd = ED_gpencil_data_get_active(C); + bGPDpalette *palette = gpencil_palette_getactive(gpd); + bGPDpalettecolor *color = gpencil_palettecolor_getactive(palette); + + /* sanity checks */ + if (ELEM(NULL, gpd, palette, color)) + return OPERATOR_CANCELLED; + + /* make the palette color before this the new active color + * - use the one after if this is the first + * - if this is the only color, this naturally becomes NULL + */ + if (color->prev) + gpencil_palettecolor_setactive(palette, color->prev); + else + gpencil_palettecolor_setactive(palette, color->next); + + /* delete the strokes */ + gpencil_palettecolor_delete_strokes(gpd, color->info); + + /* delete the palette color now... */ + gpencil_palettecolor_delete(palette, color); + + /* notifiers */ + WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL); + + return OPERATOR_FINISHED; +} + +void GPENCIL_OT_palettecolor_remove(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Remove palette color"; + ot->idname = "GPENCIL_OT_palettecolor_remove"; + ot->description = "Remove active Grease Pencil palette color"; + + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + /* callbacks */ + ot->exec = gp_palettecolor_remove_exec; + ot->poll = gp_active_palettecolor_poll; +} + +/* ********************** Isolate palette color **************************** */ + +static int gp_isolate_palettecolor_exec(bContext *C, wmOperator *op) +{ + bGPdata *gpd = ED_gpencil_data_get_active(C); + bGPDpalette *palette = gpencil_palette_getactive(gpd); + bGPDpalettecolor *active_color = gpencil_palettecolor_getactive(palette); + bGPDpalettecolor *palcolor; + + int flags = PC_COLOR_LOCKED; + bool isolate = false; + + if (RNA_boolean_get(op->ptr, "affect_visibility")) + flags |= PC_COLOR_HIDE; + + if (ELEM(NULL, gpd, active_color)) { + BKE_report(op->reports, RPT_ERROR, "No active color to isolate"); + return OPERATOR_CANCELLED; + } + + /* Test whether to isolate or clear all flags */ + for (palcolor = palette->colors.first; palcolor; palcolor = palcolor->next) { + /* Skip if this is the active one */ + if (palcolor == active_color) + continue; + + /* If the flags aren't set, that means that the color is + * not alone, so we have some colors to isolate still + */ + if ((palcolor->flag & flags) == 0) { + isolate = true; + break; + } + } + + /* Set/Clear flags as appropriate */ + if (isolate) { + /* Set flags on all "other" colors */ + for (palcolor = palette->colors.first; palcolor; palcolor = palcolor->next) { + if (palcolor == active_color) + continue; + else + palcolor->flag |= flags; + } + } + else { + /* Clear flags - Restore everything else */ + for (palcolor = palette->colors.first; palcolor; palcolor = palcolor->next) { + palcolor->flag &= ~flags; + } + } + + /* notifiers */ + WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL); + + return OPERATOR_FINISHED; +} + +void GPENCIL_OT_palettecolor_isolate(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Isolate Palette Color"; + ot->idname = "GPENCIL_OT_palettecolor_isolate"; + ot->description = "Toggle whether the active color is the only one that is editable and/or visible"; + + /* callbacks */ + ot->exec = gp_isolate_palettecolor_exec; + ot->poll = gp_active_palettecolor_poll; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + /* properties */ + RNA_def_boolean(ot->srna, "affect_visibility", false, "Affect Visibility", "In addition to toggling " + "the editability, also affect the visibility"); +} + +/* *********************** Hide Palette colors ******************************** */ + +static int gp_palettecolor_hide_exec(bContext *C, wmOperator *op) +{ + bGPdata *gpd = ED_gpencil_data_get_active(C); + bGPDpalette *palette = gpencil_palette_getactive(gpd); + bGPDpalettecolor *palcolor = gpencil_palettecolor_getactive(palette); + + bool unselected = RNA_boolean_get(op->ptr, "unselected"); + + /* sanity checks */ + if (ELEM(NULL, gpd, palette, palcolor)) + return OPERATOR_CANCELLED; + + if (unselected) { + bGPDpalettecolor *color; + + /* hide unselected */ + for (color = palette->colors.first; color; color = color->next) { + if (color != palcolor) { + color->flag |= PC_COLOR_HIDE; + } + } + } + else { + /* hide selected/active */ + palcolor->flag |= PC_COLOR_HIDE; + } + + /* notifiers */ + WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL); + + return OPERATOR_FINISHED; +} + +void GPENCIL_OT_palettecolor_hide(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Hide Color(s)"; + ot->idname = "GPENCIL_OT_palettecolor_hide"; + ot->description = "Hide selected/unselected Grease Pencil colors"; + + /* callbacks */ + ot->exec = gp_palettecolor_hide_exec; + ot->poll = gp_active_palettecolor_poll; /* NOTE: we need an active color to play with */ + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + /* props */ + RNA_def_boolean(ot->srna, "unselected", 0, "Unselected", "Hide unselected rather than selected colors"); +} + +/* ********************** Show All Colors ***************************** */ + +/* poll callback for showing colors */ +static int gp_palettecolor_reveal_poll(bContext *C) +{ + return ED_gpencil_data_get_active(C) != NULL; +} + +static int gp_palettecolor_reveal_exec(bContext *C, wmOperator *UNUSED(op)) +{ + bGPdata *gpd = ED_gpencil_data_get_active(C); + bGPDpalette *palette = gpencil_palette_getactive(gpd); + bGPDpalettecolor *palcolor; + + /* sanity checks */ + if (ELEM(NULL, gpd, palette)) + return OPERATOR_CANCELLED; + + /* make all colors visible */ + for (palcolor = palette->colors.first; palcolor; palcolor = palcolor->next) { + palcolor->flag &= ~PC_COLOR_HIDE; + } + + /* notifiers */ + WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL); + + return OPERATOR_FINISHED; +} + +void GPENCIL_OT_palettecolor_reveal(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Show All Colors"; + ot->idname = "GPENCIL_OT_palettecolor_reveal"; + ot->description = "Unhide all hidden Grease Pencil palette colors"; + + /* callbacks */ + ot->exec = gp_palettecolor_reveal_exec; + ot->poll = gp_palettecolor_reveal_poll; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; +} + +/* ***************** Lock/Unlock All Palette colors ************************ */ + +static int gp_palettecolor_lock_all_exec(bContext *C, wmOperator *UNUSED(op)) +{ + bGPdata *gpd = ED_gpencil_data_get_active(C); + bGPDpalette *palette = gpencil_palette_getactive(gpd); + bGPDpalettecolor *palcolor; + + /* sanity checks */ + if (ELEM(NULL, gpd, palette)) + return OPERATOR_CANCELLED; + + /* make all layers non-editable */ + for (palcolor = palette->colors.first; palcolor; palcolor = palcolor->next) { + palcolor->flag |= PC_COLOR_LOCKED; + } + + /* notifiers */ + WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL); + + return OPERATOR_FINISHED; +} + +void GPENCIL_OT_palettecolor_lock_all(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Lock All Colors"; + ot->idname = "GPENCIL_OT_palettecolor_lock_all"; + ot->description = "Lock all Grease Pencil colors to prevent them from being accidentally modified"; + + /* callbacks */ + ot->exec = gp_palettecolor_lock_all_exec; + ot->poll = gp_palettecolor_reveal_poll; /* XXX: could use dedicated poll later */ + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; +} + +/* -------------------------- */ + +static int gp_palettecolor_unlock_all_exec(bContext *C, wmOperator *UNUSED(op)) +{ + bGPdata *gpd = ED_gpencil_data_get_active(C); + bGPDpalette *palette = gpencil_palette_getactive(gpd); + bGPDpalettecolor *palcolor; + + /* sanity checks */ + if (ELEM(NULL, gpd, palette)) + return OPERATOR_CANCELLED; + + /* make all layers editable again*/ + for (palcolor = palette->colors.first; palcolor; palcolor = palcolor->next) { + palcolor->flag &= ~PC_COLOR_LOCKED; + } + + /* notifiers */ + WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL); + + return OPERATOR_FINISHED; +} + +void GPENCIL_OT_palettecolor_unlock_all(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Unlock All Colors"; + ot->idname = "GPENCIL_OT_palettecolor_unlock_all"; + ot->description = "Unlock all Grease Pencil colors so that they can be edited"; + + /* callbacks */ + ot->exec = gp_palettecolor_unlock_all_exec; + ot->poll = gp_palettecolor_reveal_poll; /* XXX: could use dedicated poll later */ + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; +} + +/* ******************* Move Color Up/Down ************************** */ + +enum { + GP_COLOR_MOVE_UP = -1, + GP_COLOR_MOVE_DOWN = 1 +}; + +static int gp_palettecolor_move_exec(bContext *C, wmOperator *op) +{ + bGPdata *gpd = ED_gpencil_data_get_active(C); + bGPDpalette *palette = gpencil_palette_getactive(gpd); + bGPDpalettecolor *palcolor = gpencil_palettecolor_getactive(palette); + + int direction = RNA_enum_get(op->ptr, "direction"); + + /* sanity checks */ + if (ELEM(NULL, gpd, palette, palcolor)) + return OPERATOR_CANCELLED; + + /* up or down? */ + if (direction == GP_COLOR_MOVE_UP) { + /* up */ + BLI_remlink(&palette->colors, palcolor); + BLI_insertlinkbefore(&palette->colors, palcolor->prev, palcolor); + } + else if (direction == GP_COLOR_MOVE_DOWN) { + /* down */ + BLI_remlink(&palette->colors, palcolor); + BLI_insertlinkafter(&palette->colors, palcolor->next, palcolor); + } + else { + BLI_assert(0); + } + + /* notifiers */ + WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL); + + return OPERATOR_FINISHED; +} + +void GPENCIL_OT_palettecolor_move(wmOperatorType *ot) +{ + static EnumPropertyItem slot_move[] = { + {GP_COLOR_MOVE_UP, "UP", 0, "Up", ""}, + {GP_COLOR_MOVE_DOWN, "DOWN", 0, "Down", ""}, + {0, NULL, 0, NULL, NULL} + }; + + /* identifiers */ + ot->name = "Move Palette color"; + ot->idname = "GPENCIL_OT_palettecolor_move"; + ot->description = "Move the active Grease Pencil palette color up/down in the list"; + + /* api callbacks */ + ot->exec = gp_palettecolor_move_exec; + ot->poll = gp_palettecolor_reveal_poll; /* XXX: could use dedicated poll later */ + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + ot->prop = RNA_def_enum(ot->srna, "direction", slot_move, GP_COLOR_MOVE_UP, "Direction", ""); +} + +/* ***************** Select all strokes using Palette color ************************ */ + +static int gp_palettecolor_select_exec(bContext *C, wmOperator *UNUSED(op)) +{ + bGPdata *gpd = ED_gpencil_data_get_active(C); + bGPDpalette *palette = gpencil_palette_getactive(gpd); + bGPDpalettecolor *palcolor = gpencil_palettecolor_getactive(palette); + + /* sanity checks */ + if (ELEM(NULL, gpd, palette, palcolor)) + return OPERATOR_CANCELLED; + + /* read all strokes and select*/ + for (bGPDlayer *gpl = gpd->layers.first; gpl; gpl = gpl->next) { + /* only editable and visible layers are considered */ + if (gpencil_layer_is_editable(gpl) && (gpl->actframe != NULL)) { + /* verify something to do */ + for (bGPDstroke *gps = gpl->actframe->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(gpl, gps) == false) + continue; + + /* select */ + if (strcmp(palcolor->info, gps->colorname) == 0) { + bGPDspoint *pt; + int i; + + gps->flag |= GP_STROKE_SELECT; + for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) { + pt->flag |= GP_SPOINT_SELECT; + } + } + } + } + } + /* notifiers */ + WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL); + + return OPERATOR_FINISHED; +} + +void GPENCIL_OT_palettecolor_select(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Select Color"; + ot->idname = "GPENCIL_OT_palettecolor_select"; + ot->description = "Select all Grease Pencil strokes using current color"; + + /* callbacks */ + ot->exec = gp_palettecolor_select_exec; + ot->poll = gp_palettecolor_reveal_poll; /* XXX: could use dedicated poll later */ + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; +} + +/* ***************** Copy Palette color ************************ */ + +static int gp_palettecolor_copy_exec(bContext *C, wmOperator *UNUSED(op)) +{ + bGPdata *gpd = ED_gpencil_data_get_active(C); + bGPDpalette *palette = gpencil_palette_getactive(gpd); + bGPDpalettecolor *palcolor = gpencil_palettecolor_getactive(palette); + bGPDpalettecolor *newcolor; + + /* sanity checks */ + if (ELEM(NULL, gpd, palette, palcolor)) + return OPERATOR_CANCELLED; + + /* create a new color and duplicate data */ + newcolor = gpencil_palettecolor_addnew(palette, palcolor->info, true); + copy_v4_v4(newcolor->color, palcolor->color); + copy_v4_v4(newcolor->fill, palcolor->fill); + newcolor->flag = palcolor->flag; + + /* notifiers */ + WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL); + + return OPERATOR_FINISHED; +} + +void GPENCIL_OT_palettecolor_copy(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Copy Color"; + ot->idname = "GPENCIL_OT_palettecolor_copy"; + ot->description = "Copy current Grease Pencil palette color"; + + /* callbacks */ + ot->exec = gp_palettecolor_copy_exec; + ot->poll = gp_active_palettecolor_poll; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; +} diff --git a/source/blender/editors/gpencil/gpencil_edit.c b/source/blender/editors/gpencil/gpencil_edit.c index ac49a51c716..621ebea6603 100644 --- a/source/blender/editors/gpencil/gpencil_edit.c +++ b/source/blender/editors/gpencil/gpencil_edit.c @@ -125,10 +125,48 @@ static int gp_stroke_edit_poll(bContext *C) return CTX_DATA_COUNT(C, editable_gpencil_strokes) != 0; } +/* ************ Stroke Hide selection Toggle ************** */ + +static int gpencil_hideselect_toggle_exec(bContext *C, wmOperator *UNUSED(op)) +{ + ToolSettings *ts = CTX_data_tool_settings(C); + + if (ts == NULL) + return OPERATOR_CANCELLED; + + /* Just toggle alpha... */ + if (ts->gp_sculpt.alpha > 0.0f) { + ts->gp_sculpt.alpha = 0.0f; + } + else { + ts->gp_sculpt.alpha = 1.0f; + } + + WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | ND_GPENCIL_EDITMODE, NULL); + WM_event_add_notifier(C, NC_SCENE | ND_MODE, NULL); + + return OPERATOR_FINISHED; +} + +void GPENCIL_OT_selection_opacity_toggle(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Hide Selection"; + ot->idname = "GPENCIL_OT_selection_opacity_toggle"; + ot->description = "Hide/Unhide selected points for Grease Pencil strokes setting alpha factor"; + + /* callbacks */ + ot->exec = gpencil_hideselect_toggle_exec; + ot->poll = gp_stroke_edit_poll; + + /* flags */ + ot->flag = OPTYPE_UNDO | OPTYPE_REGISTER; +} + /* ************** Duplicate Selected Strokes **************** */ /* Make copies of selected point segments in a selected stroke */ -static void gp_duplicate_points(const bGPDstroke *gps, ListBase *new_strokes) +static void gp_duplicate_points(const bGPDstroke *gps, ListBase *new_strokes, const char *layername) { bGPDspoint *pt; int i; @@ -169,6 +207,7 @@ static void gp_duplicate_points(const bGPDstroke *gps, ListBase *new_strokes) /* make a stupid copy first of the entire stroke (to get the flags too) */ gpsd = MEM_dupallocN(gps); + strcpy(gpsd->tmp_layerinfo, layername); /* saves original layer name */ /* initialize triangle memory - will be calculated on next redraw */ gpsd->triangles = NULL; @@ -216,8 +255,9 @@ static int gp_duplicate_exec(bContext *C, wmOperator *op) /* make copies of selected strokes, and deselect these once we're done */ for (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) + if (ED_gpencil_stroke_can_use(C, gps) == false) { continue; + } if (gps->flag & GP_STROKE_SELECT) { if (gps->totpoints == 1) { @@ -226,8 +266,9 @@ static int gp_duplicate_exec(bContext *C, wmOperator *op) /* make direct copies of the stroke and its points */ gpsd = MEM_dupallocN(gps); + strcpy(gpsd->tmp_layerinfo, gpl->info); gpsd->points = MEM_dupallocN(gps->points); - + /* triangle information - will be calculated on next redraw */ gpsd->flag |= GP_STROKE_RECALC_CACHES; gpsd->triangles = NULL; @@ -238,7 +279,7 @@ static int gp_duplicate_exec(bContext *C, wmOperator *op) } else { /* delegate to a helper, as there's too much to fit in here (for copying subsets)... */ - gp_duplicate_points(gps, &new_strokes); + gp_duplicate_points(gps, &new_strokes, gpl->info); } /* deselect original stroke, or else the originals get moved too @@ -345,6 +386,7 @@ static int gp_strokes_copy_exec(bContext *C, wmOperator *op) /* make direct copies of the stroke and its points */ gpsd = MEM_dupallocN(gps); + strcpy(gpsd->tmp_layerinfo, gpl->info); /* saves original layer name */ gpsd->points = MEM_dupallocN(gps->points); /* triangles cache - will be recalculated on next redraw */ @@ -358,7 +400,7 @@ static int gp_strokes_copy_exec(bContext *C, wmOperator *op) } else { /* delegate to a helper, as there's too much to fit in here (for copying subsets)... */ - gp_duplicate_points(gps, &gp_strokes_copypastebuf); + gp_duplicate_points(gps, &gp_strokes_copypastebuf, gpl->info); } } } @@ -395,13 +437,20 @@ static int gp_strokes_paste_poll(bContext *C) return (CTX_data_active_gpencil_layer(C) != NULL) && (!BLI_listbase_is_empty(&gp_strokes_copypastebuf)); } +enum { + GP_COPY_ONLY = -1, + GP_COPY_MERGE = 1 +}; + static int gp_strokes_paste_exec(bContext *C, wmOperator *op) { Scene *scene = CTX_data_scene(C); bGPdata *gpd = ED_gpencil_data_get_active(C); - bGPDlayer *gpl = CTX_data_active_gpencil_layer(C); + bGPDlayer *gpl = CTX_data_active_gpencil_layer(C); /* only use active for copy merge */ bGPDframe *gpf; - + + int type = RNA_enum_get(op->ptr, "type"); + /* check for various error conditions */ if (gpd == NULL) { BKE_report(op->reports, RPT_ERROR, "No Grease Pencil data"); @@ -415,7 +464,7 @@ static int gp_strokes_paste_exec(bContext *C, wmOperator *op) /* no active layer - let's just create one */ gpl = gpencil_layer_addnew(gpd, DATA_("GP_Layer"), true); } - else if (gpencil_layer_is_editable(gpl) == false) { + else if ((gpencil_layer_is_editable(gpl) == false) && (type == GP_COPY_MERGE)) { BKE_report(op->reports, RPT_ERROR, "Can not paste strokes when active layer is hidden or locked"); return OPERATOR_CANCELLED; } @@ -463,26 +512,34 @@ 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 = gpencil_layer_getframe(gpl, CFRA, true); - if (gpf) { bGPDstroke *gps; - /* 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 = MEM_dupallocN(gps); - - new_stroke->points = MEM_dupallocN(gps->points); - - new_stroke->flag |= GP_STROKE_RECALC_CACHES; - new_stroke->triangles = NULL; - - new_stroke->next = new_stroke->prev = NULL; - BLI_addtail(&gpf->strokes, new_stroke); + /* need to verify if layer exist nad frame */ + if (type != GP_COPY_MERGE) { + gpl = BLI_findstring(&gpd->layers, gps->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); + } + } + gpf = gpencil_layer_getframe(gpl, CFRA, true); + if (gpf) { + bGPDstroke *new_stroke = MEM_dupallocN(gps); + new_stroke->tmp_layerinfo[0] = '\0'; + + new_stroke->points = MEM_dupallocN(gps->points); + + new_stroke->flag |= GP_STROKE_RECALC_CACHES; + new_stroke->triangles = NULL; + + new_stroke->next = new_stroke->prev = NULL; + BLI_addtail(&gpf->strokes, new_stroke); + } } } - } /* updates */ WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL); @@ -492,10 +549,16 @@ static int gp_strokes_paste_exec(bContext *C, wmOperator *op) void GPENCIL_OT_paste(wmOperatorType *ot) { + static EnumPropertyItem copy_type[] = { + {GP_COPY_ONLY, "COPY", 0, "Copy", ""}, + {GP_COPY_MERGE, "MERGE", 0, "Merge", ""}, + {0, NULL, 0, NULL, NULL} + }; + /* identifiers */ ot->name = "Paste Strokes"; ot->idname = "GPENCIL_OT_paste"; - ot->description = "Paste previously copied strokes into active layer"; + ot->description = "Paste previously copied strokes or copy and merge in active layer"; /* callbacks */ ot->exec = gp_strokes_paste_exec; @@ -503,6 +566,8 @@ void GPENCIL_OT_paste(wmOperatorType *ot) /* flags */ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + ot->prop = RNA_def_enum(ot->srna, "type", copy_type, 0, "Type", ""); } /* ******************* Move To Layer ****************************** */ @@ -1069,7 +1134,7 @@ void GPENCIL_OT_delete(wmOperatorType *ot) }; /* identifiers */ - ot->name = "Delete..."; + ot->name = "Delete"; ot->idname = "GPENCIL_OT_delete"; ot->description = "Delete selected Grease Pencil strokes, vertices, or frames"; @@ -1124,25 +1189,65 @@ static int gp_snap_poll(bContext *C) static int gp_snap_to_grid(bContext *C, wmOperator *UNUSED(op)) { RegionView3D *rv3d = CTX_wm_region_data(C); - float gridf = rv3d->gridview; + const float gridf = rv3d->gridview; - CTX_DATA_BEGIN(C, bGPDstroke *, gps, editable_gpencil_strokes) - { - bGPDspoint *pt; - int i; - - // TOOD: if entire stroke is selected, offset entire stroke by same amount? - - for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) { - /* only if point is selected.. */ - if (pt->flag & GP_SPOINT_SELECT) { - pt->x = gridf * floorf(0.5f + pt->x / gridf); - pt->y = gridf * floorf(0.5f + pt->y / gridf); - pt->z = gridf * floorf(0.5f + pt->z / gridf); + bGPdata *gpd = ED_gpencil_data_get_active(C); + float diff_mat[4][4]; + + for (bGPDlayer *gpl = gpd->layers.first; gpl; gpl = gpl->next) { + /* only editable and visible layers are considered */ + if (gpencil_layer_is_editable(gpl) && (gpl->actframe != NULL)) { + + /* calculate difference matrix if parent object */ + if (gpl->parent != NULL) { + ED_gpencil_parent_location(gpl, diff_mat); + } + + bGPDframe *gpf = gpl->actframe; + bGPDstroke *gps; + for (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(gpl, gps) == false) { + continue; + } + + bGPDspoint *pt; + int i; + + // TOOD: if entire stroke is selected, offset entire stroke by same amount? + + for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) { + + /* only if point is selected.. */ + if (pt->flag & GP_SPOINT_SELECT) { + if (gpl->parent == NULL) { + pt->x = gridf * floorf(0.5f + pt->x / gridf); + pt->y = gridf * floorf(0.5f + pt->y / gridf); + pt->z = gridf * floorf(0.5f + pt->z / gridf); + } + else { + /* apply parent transformations */ + float fpt[3]; + mul_v3_m4v3(fpt, diff_mat, &pt->x); + + fpt[0] = gridf * floorf(0.5f + fpt[0] / gridf); + fpt[1] = gridf * floorf(0.5f + fpt[1] / gridf); + fpt[2] = gridf * floorf(0.5f + fpt[2] / gridf); + + /* return data */ + copy_v3_v3(&pt->x, fpt); + gp_apply_parent_point(gpl, pt); + } + + } + } + } } } - CTX_DATA_END; WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL); return OPERATOR_FINISHED; @@ -1169,41 +1274,68 @@ static int gp_snap_to_cursor(bContext *C, wmOperator *op) { Scene *scene = CTX_data_scene(C); View3D *v3d = CTX_wm_view3d(C); - + const bool use_offset = RNA_boolean_get(op->ptr, "use_offset"); const float *cursor_global = ED_view3d_cursor3d_get(scene, v3d); - - CTX_DATA_BEGIN(C, bGPDstroke *, gps, editable_gpencil_strokes) - { - bGPDspoint *pt; - int i; - - /* only continue if this stroke is selected (editable doesn't guarantee this)... */ - if ((gps->flag & GP_STROKE_SELECT) == 0) - continue; - - if (use_offset) { - float offset[3]; - - /* compute offset from first point of stroke to cursor */ - /* TODO: Allow using midpoint instead? */ - sub_v3_v3v3(offset, cursor_global, &gps->points->x); - - /* apply offset to all points in the stroke */ - for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) { - add_v3_v3(&pt->x, offset); + + bGPdata *gpd = ED_gpencil_data_get_active(C); + float diff_mat[4][4]; + + for (bGPDlayer *gpl = gpd->layers.first; gpl; gpl = gpl->next) { + /* only editable and visible layers are considered */ + if (gpencil_layer_is_editable(gpl) && (gpl->actframe != NULL)) { + + /* calculate difference matrix if parent object */ + if (gpl->parent != NULL) { + ED_gpencil_parent_location(gpl, diff_mat); } - } - else { - /* affect each selected point */ - for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) { - if (pt->flag & GP_SPOINT_SELECT) { - copy_v3_v3(&pt->x, cursor_global); + + bGPDframe *gpf = gpl->actframe; + bGPDstroke *gps; + for (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(gpl, gps) == false) { + continue; + } + + bGPDspoint *pt; + int i; + + /* only continue if this stroke is selected (editable doesn't guarantee this)... */ + if ((gps->flag & GP_STROKE_SELECT) == 0) + continue; + + if (use_offset) { + float offset[3]; + + /* compute offset from first point of stroke to cursor */ + /* TODO: Allow using midpoint instead? */ + sub_v3_v3v3(offset, cursor_global, &gps->points->x); + + /* apply offset to all points in the stroke */ + for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) { + add_v3_v3(&pt->x, offset); + } + } + else { + /* affect each selected point */ + for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) { + if (pt->flag & GP_SPOINT_SELECT) { + copy_v3_v3(&pt->x, cursor_global); + if (gpl->parent != NULL) { + gp_apply_parent_point(gpl, pt); + } + } + } } + + } } } - CTX_DATA_END; WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL); return OPERATOR_FINISHED; @@ -1243,24 +1375,57 @@ static int gp_snap_cursor_to_sel(bContext *C, wmOperator *UNUSED(op)) INIT_MINMAX(min, max); /* calculate midpoints from selected points */ - CTX_DATA_BEGIN(C, bGPDstroke *, gps, editable_gpencil_strokes) - { - bGPDspoint *pt; - int i; - - /* only continue if this stroke is selected (editable doesn't guarantee this)... */ - if ((gps->flag & GP_STROKE_SELECT) == 0) - continue; - - for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) { - if (pt->flag & GP_SPOINT_SELECT) { - add_v3_v3(centroid, &pt->x); - minmax_v3v3_v3(min, max, &pt->x); - count++; + bGPdata *gpd = ED_gpencil_data_get_active(C); + float diff_mat[4][4]; + + for (bGPDlayer *gpl = gpd->layers.first; gpl; gpl = gpl->next) { + /* only editable and visible layers are considered */ + if (gpencil_layer_is_editable(gpl) && (gpl->actframe != NULL)) { + + /* calculate difference matrix if parent object */ + if (gpl->parent != NULL) { + ED_gpencil_parent_location(gpl, diff_mat); + } + + bGPDframe *gpf = gpl->actframe; + bGPDstroke *gps; + for (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(gpl, gps) == false) { + continue; + } + + bGPDspoint *pt; + int i; + + /* only continue if this stroke is selected (editable doesn't guarantee this)... */ + if ((gps->flag & GP_STROKE_SELECT) == 0) + continue; + + for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) { + if (pt->flag & GP_SPOINT_SELECT) { + if (gpl->parent == NULL) { + add_v3_v3(centroid, &pt->x); + minmax_v3v3_v3(min, max, &pt->x); + } + else { + /* apply parent transformations */ + float fpt[3]; + mul_v3_m4v3(fpt, diff_mat, &pt->x); + + add_v3_v3(centroid, fpt); + minmax_v3v3_v3(min, max, fpt); + } + count++; + } + } + } } } - CTX_DATA_END; if (v3d->around == V3D_AROUND_CENTER_MEAN && count) { mul_v3_fl(centroid, 1.0f / (float)count); diff --git a/source/blender/editors/gpencil/gpencil_intern.h b/source/blender/editors/gpencil/gpencil_intern.h index 53fb33eeb9b..0ff0878d4ce 100644 --- a/source/blender/editors/gpencil/gpencil_intern.h +++ b/source/blender/editors/gpencil/gpencil_intern.h @@ -101,6 +101,23 @@ void gp_point_to_xy(GP_SpaceConversion *settings, struct bGPDstroke *gps, struct int *r_x, int *r_y); /** + * Convert point to parent space + * + * \param pt Original point + * \param diff_mat Matrix with the difference between original parent matrix + * \param[out] r_pt Pointer to new point after apply matrix + */ +void gp_point_to_parent_space(bGPDspoint *pt, float diff_mat[4][4], bGPDspoint *r_pt); +/** + * Change points position relative to parent object + */ +void gp_apply_parent(bGPDlayer *gpl, bGPDstroke *gps); +/** + * Change point position relative to parent object + */ +void gp_apply_parent_point(bGPDlayer *gpl, bGPDspoint *pt); + +/** * Convert a screenspace point to a 3D Grease Pencil coordinate. * * For use with editing tools where it is easier to perform the operations in 2D, @@ -116,6 +133,10 @@ bool gp_point_xy_to_3d(GP_SpaceConversion *gsc, struct Scene *scene, const float int gp_add_poll(struct bContext *C); int gp_active_layer_poll(struct bContext *C); +int gp_active_brush_poll(struct bContext *C); +int gp_active_palette_poll(struct bContext *C); +int gp_active_palettecolor_poll(struct bContext *C); +int gp_brush_crt_presets_poll(bContext *C); /* Copy/Paste Buffer --------------------------------- */ /* gpencil_edit.c */ @@ -137,17 +158,47 @@ void gp_stroke_delete_tagged_points(bGPDframe *gpf, bGPDstroke *gps, bGPDstroke bool gp_smooth_stroke(bGPDstroke *gps, int i, float inf, bool affect_pressure); /** +* Apply smooth for strength to stroke point +* \param gps Stroke to smooth +* \param i Point index +* \param inf Amount of smoothing to apply +*/ +bool gp_smooth_stroke_strength(bGPDstroke *gps, int i, float inf); + +/** +* Apply smooth for thickness to stroke point (use pressure) +* \param gps Stroke to smooth +* \param i Point index +* \param inf Amount of smoothing to apply +*/ +bool gp_smooth_stroke_thickness(bGPDstroke *gps, int i, float inf); + +/** * Subdivide a stroke once, by adding points at the midpoint between each pair of points * \param gps Stroke data * \param new_totpoints Total number of points (after subdividing) */ void gp_subdivide_stroke(bGPDstroke *gps, const int new_totpoints); +/** +* Add randomness to stroke +* \param gps Stroke data +* \param brsuh Brush data +*/ +void gp_randomize_stroke(bGPDstroke *gps, bGPDbrush *brush); + /* Layers Enums -------------------------------------- */ struct EnumPropertyItem *ED_gpencil_layers_enum_itemf(struct bContext *C, struct PointerRNA *ptr, struct PropertyRNA *prop, bool *r_free); struct EnumPropertyItem *ED_gpencil_layers_with_new_enum_itemf(struct bContext *C, struct PointerRNA *ptr, struct PropertyRNA *prop, bool *r_free); +/* Enums of GP Brushes */ +EnumPropertyItem *ED_gpencil_brushes_enum_itemf(bContext *C, PointerRNA *UNUSED(ptr), PropertyRNA *UNUSED(prop), + bool *r_free); + +/* Enums of GP palettes */ +EnumPropertyItem *ED_gpencil_palettes_enum_itemf(bContext *C, PointerRNA *UNUSED(ptr), PropertyRNA *UNUSED(prop), + bool *r_free); /* ***************************************************** */ /* Operator Defines */ @@ -166,6 +217,7 @@ typedef enum eGPencil_PaintModes { /* stroke editing ----- */ void GPENCIL_OT_editmode_toggle(struct wmOperatorType *ot); +void GPENCIL_OT_selection_opacity_toggle(struct wmOperatorType *ot); void GPENCIL_OT_select(struct wmOperatorType *ot); void GPENCIL_OT_select_all(struct wmOperatorType *ot); @@ -216,12 +268,45 @@ void GPENCIL_OT_lock_all(struct wmOperatorType *ot); void GPENCIL_OT_unlock_all(struct wmOperatorType *ot); void GPENCIL_OT_layer_isolate(struct wmOperatorType *ot); +void GPENCIL_OT_layer_merge(struct wmOperatorType *ot); void GPENCIL_OT_active_frame_delete(struct wmOperatorType *ot); void GPENCIL_OT_active_frames_delete_all(struct wmOperatorType *ot); void GPENCIL_OT_convert(struct wmOperatorType *ot); +void GPENCIL_OT_stroke_arrange(struct wmOperatorType *ot); +void GPENCIL_OT_stroke_change_color(struct wmOperatorType *ot); +void GPENCIL_OT_stroke_lock_color(struct wmOperatorType *ot); +void GPENCIL_OT_stroke_apply_thickness(struct wmOperatorType *ot); +void GPENCIL_OT_stroke_cyclical_set(struct wmOperatorType *ot); +void GPENCIL_OT_stroke_join(struct wmOperatorType *ot); +void GPENCIL_OT_stroke_flip(struct wmOperatorType *ot); + +void GPENCIL_OT_brush_add(struct wmOperatorType *ot); +void GPENCIL_OT_brush_remove(struct wmOperatorType *ot); +void GPENCIL_OT_brush_change(struct wmOperatorType *ot); +void GPENCIL_OT_brush_move(struct wmOperatorType *ot); +void GPENCIL_OT_brush_presets_create(struct wmOperatorType *ot); +void GPENCIL_OT_brush_copy(struct wmOperatorType *ot); +void GPENCIL_OT_brush_select(struct wmOperatorType *ot); + +void GPENCIL_OT_palette_add(struct wmOperatorType *ot); +void GPENCIL_OT_palette_remove(struct wmOperatorType *ot); +void GPENCIL_OT_palette_change(struct wmOperatorType *ot); +void GPENCIL_OT_palette_lock_layer(struct wmOperatorType *ot); +void GPENCIL_OT_palettecolor_add(struct wmOperatorType *ot); +void GPENCIL_OT_palettecolor_remove(struct wmOperatorType *ot); +void GPENCIL_OT_palettecolor_isolate(struct wmOperatorType *ot); + +void GPENCIL_OT_palettecolor_hide(struct wmOperatorType *ot); +void GPENCIL_OT_palettecolor_reveal(struct wmOperatorType *ot); +void GPENCIL_OT_palettecolor_lock_all(struct wmOperatorType *ot); +void GPENCIL_OT_palettecolor_unlock_all(struct wmOperatorType *ot); +void GPENCIL_OT_palettecolor_move(struct wmOperatorType *ot); +void GPENCIL_OT_palettecolor_select(struct wmOperatorType *ot); +void GPENCIL_OT_palettecolor_copy(struct wmOperatorType *ot); + /* undo stack ---------- */ void gpencil_undo_init(struct bGPdata *gpd); @@ -273,4 +358,39 @@ typedef enum ACTCONT_TYPES { ACTCONT_GPENCIL } ACTCONT_TYPES; +/** +* Iterate over all editable strokes in the current context, +* stopping on each usable layer + stroke pair (i.e. gpl and gps) +* to perform some operations on the stroke. +* +* \param gpl The identifier to use for the layer of the stroke being processed. +* Choose a suitable value to avoid name clashes. +* \param gps The identifier to use for current stroke being processed. +* Choose a suitable value to avoid name clashes. +*/ +#define GP_EDITABLE_STROKES_BEGIN(C, gpl, gps) \ +{ \ + CTX_DATA_BEGIN(C, bGPDlayer*, gpl, editable_gpencil_layers) \ + { \ + if (gpl->actframe == NULL) \ + continue; \ + /* calculate difference matrix if parent object */ \ + float diff_mat[4][4]; \ + ED_gpencil_parent_location(gpl, diff_mat); \ + /* loop over strokes */ \ + for (bGPDstroke *gps = gpl->actframe->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(gpl, gps) == false) \ + continue; \ + /* ... Do Stuff With Strokes ... */ + +#define GP_EDITABLE_STROKES_END \ + } \ + } \ + CTX_DATA_END; \ +} (void)0 + #endif /* __GPENCIL_INTERN_H__ */ diff --git a/source/blender/editors/gpencil/gpencil_ops.c b/source/blender/editors/gpencil/gpencil_ops.c index 65ee1122b56..6bbb8f7c965 100644 --- a/source/blender/editors/gpencil/gpencil_ops.c +++ b/source/blender/editors/gpencil/gpencil_ops.c @@ -18,7 +18,7 @@ * The Original Code is Copyright (C) 2009, Blender Foundation, Joshua Leung * This is a new part of Blender * - * Contributor(s): Joshua Leung + * Contributor(s): Joshua Leung, Antonio Vazquez * * ***** END GPL LICENSE BLOCK ***** */ @@ -140,8 +140,8 @@ static void ed_keymap_gpencil_editing(wmKeyConfig *keyconf) * that the only data being edited is that of the Grease Pencil strokes */ - /* FKEY = Eraser Radius */ - kmi = WM_keymap_add_item(keymap, "WM_OT_radial_control", FKEY, KM_PRESS, 0, 0); + /* CTRL + FKEY = Eraser Radius */ + kmi = WM_keymap_add_item(keymap, "WM_OT_radial_control", FKEY, KM_PRESS, KM_CTRL, 0); RNA_string_set(kmi->ptr, "data_path_primary", "user_preferences.edit.grease_pencil_eraser_radius"); @@ -169,8 +169,8 @@ static void ed_keymap_gpencil_editing(wmKeyConfig *keyconf) kmi = WM_keymap_add_item(keymap, "WM_OT_radial_control", FKEY, KM_PRESS, KM_SHIFT, 0); RNA_string_set(kmi->ptr, "data_path_primary", "tool_settings.gpencil_sculpt.brush.strength"); - /* Ctrl-FKEY = Sculpt Brush Size */ - kmi = WM_keymap_add_item(keymap, "WM_OT_radial_control", FKEY, KM_PRESS, KM_CTRL, 0); + /* FKEY = Sculpt Brush Size */ + kmi = WM_keymap_add_item(keymap, "WM_OT_radial_control", FKEY, KM_PRESS, 0, 0); RNA_string_set(kmi->ptr, "data_path_primary", "tool_settings.gpencil_sculpt.brush.size"); @@ -266,14 +266,37 @@ static void ed_keymap_gpencil_editing(wmKeyConfig *keyconf) kmi = WM_keymap_add_item(keymap, "GPENCIL_OT_hide", HKEY, KM_PRESS, KM_SHIFT, 0); RNA_boolean_set(kmi->ptr, "unselected", true); + + WM_keymap_add_item(keymap, "GPENCIL_OT_selection_opacity_toggle", HKEY, KM_PRESS, KM_CTRL, 0); /* Isolate Layer */ WM_keymap_add_item(keymap, "GPENCIL_OT_layer_isolate", PADASTERKEY, KM_PRESS, 0, 0); /* Move to Layer */ WM_keymap_add_item(keymap, "GPENCIL_OT_move_to_layer", MKEY, KM_PRESS, 0, 0); - - + + /* Select drawing brush using index */ + kmi = WM_keymap_add_item(keymap, "GPENCIL_OT_brush_select", ONEKEY, KM_PRESS, 0, 0); + RNA_int_set(kmi->ptr, "index", 0); + kmi = WM_keymap_add_item(keymap, "GPENCIL_OT_brush_select", TWOKEY, KM_PRESS, 0, 0); + RNA_int_set(kmi->ptr, "index", 1); + kmi = WM_keymap_add_item(keymap, "GPENCIL_OT_brush_select", THREEKEY, KM_PRESS, 0, 0); + RNA_int_set(kmi->ptr, "index", 2); + kmi = WM_keymap_add_item(keymap, "GPENCIL_OT_brush_select", FOURKEY, KM_PRESS, 0, 0); + RNA_int_set(kmi->ptr, "index", 3); + kmi = WM_keymap_add_item(keymap, "GPENCIL_OT_brush_select", FIVEKEY, KM_PRESS, 0, 0); + RNA_int_set(kmi->ptr, "index", 4); + kmi = WM_keymap_add_item(keymap, "GPENCIL_OT_brush_select", SIXKEY, KM_PRESS, 0, 0); + RNA_int_set(kmi->ptr, "index", 5); + kmi = WM_keymap_add_item(keymap, "GPENCIL_OT_brush_select", SEVENKEY, KM_PRESS, 0, 0); + RNA_int_set(kmi->ptr, "index", 6); + kmi = WM_keymap_add_item(keymap, "GPENCIL_OT_brush_select", EIGHTKEY, KM_PRESS, 0, 0); + RNA_int_set(kmi->ptr, "index", 7); + kmi = WM_keymap_add_item(keymap, "GPENCIL_OT_brush_select", NINEKEY, KM_PRESS, 0, 0); + RNA_int_set(kmi->ptr, "index", 8); + kmi = WM_keymap_add_item(keymap, "GPENCIL_OT_brush_select", ZEROKEY, KM_PRESS, 0, 0); + RNA_int_set(kmi->ptr, "index", 9); + /* Transform Tools */ kmi = WM_keymap_add_item(keymap, "TRANSFORM_OT_translate", GKEY, KM_PRESS, 0, 0); @@ -318,7 +341,8 @@ void ED_operatortypes_gpencil(void) /* Editing (Strokes) ------------ */ WM_operatortype_append(GPENCIL_OT_editmode_toggle); - + WM_operatortype_append(GPENCIL_OT_selection_opacity_toggle); + WM_operatortype_append(GPENCIL_OT_select); WM_operatortype_append(GPENCIL_OT_select_all); WM_operatortype_append(GPENCIL_OT_select_circle); @@ -362,12 +386,44 @@ void ED_operatortypes_gpencil(void) WM_operatortype_append(GPENCIL_OT_lock_all); WM_operatortype_append(GPENCIL_OT_unlock_all); WM_operatortype_append(GPENCIL_OT_layer_isolate); - + WM_operatortype_append(GPENCIL_OT_layer_merge); + WM_operatortype_append(GPENCIL_OT_active_frame_delete); WM_operatortype_append(GPENCIL_OT_active_frames_delete_all); WM_operatortype_append(GPENCIL_OT_convert); + WM_operatortype_append(GPENCIL_OT_stroke_arrange); + WM_operatortype_append(GPENCIL_OT_stroke_change_color); + WM_operatortype_append(GPENCIL_OT_stroke_lock_color); + WM_operatortype_append(GPENCIL_OT_stroke_apply_thickness); + WM_operatortype_append(GPENCIL_OT_stroke_cyclical_set); + WM_operatortype_append(GPENCIL_OT_stroke_join); + WM_operatortype_append(GPENCIL_OT_stroke_flip); + + WM_operatortype_append(GPENCIL_OT_palette_add); + WM_operatortype_append(GPENCIL_OT_palette_remove); + WM_operatortype_append(GPENCIL_OT_palette_change); + WM_operatortype_append(GPENCIL_OT_palette_lock_layer); + WM_operatortype_append(GPENCIL_OT_palettecolor_add); + WM_operatortype_append(GPENCIL_OT_palettecolor_remove); + WM_operatortype_append(GPENCIL_OT_palettecolor_isolate); + WM_operatortype_append(GPENCIL_OT_palettecolor_hide); + WM_operatortype_append(GPENCIL_OT_palettecolor_reveal); + WM_operatortype_append(GPENCIL_OT_palettecolor_lock_all); + WM_operatortype_append(GPENCIL_OT_palettecolor_unlock_all); + WM_operatortype_append(GPENCIL_OT_palettecolor_move); + WM_operatortype_append(GPENCIL_OT_palettecolor_select); + WM_operatortype_append(GPENCIL_OT_palettecolor_copy); + + WM_operatortype_append(GPENCIL_OT_brush_add); + WM_operatortype_append(GPENCIL_OT_brush_remove); + WM_operatortype_append(GPENCIL_OT_brush_change); + WM_operatortype_append(GPENCIL_OT_brush_move); + WM_operatortype_append(GPENCIL_OT_brush_presets_create); + WM_operatortype_append(GPENCIL_OT_brush_copy); + WM_operatortype_append(GPENCIL_OT_brush_select); + /* Editing (Time) --------------- */ } diff --git a/source/blender/editors/gpencil/gpencil_paint.c b/source/blender/editors/gpencil/gpencil_paint.c index a570d586f50..403d632b5da 100644 --- a/source/blender/editors/gpencil/gpencil_paint.c +++ b/source/blender/editors/gpencil/gpencil_paint.c @@ -18,7 +18,7 @@ * The Original Code is Copyright (C) 2008, Blender Foundation, Joshua Leung * This is a new part of Blender * - * Contributor(s): Joshua Leung + * Contributor(s): Joshua Leung, Antonio Vazquez * * ***** END GPL LICENSE BLOCK ***** */ @@ -39,21 +39,26 @@ #include "BLI_blenlib.h" #include "BLI_math.h" #include "BLI_utildefines.h" +#include "BLI_rand.h" #include "BLT_translation.h" #include "PIL_time.h" +#include "BKE_main.h" +#include "BKE_paint.h" #include "BKE_gpencil.h" #include "BKE_context.h" #include "BKE_global.h" #include "BKE_report.h" #include "BKE_screen.h" #include "BKE_tracking.h" +#include "BKE_colortools.h" #include "DNA_object_types.h" #include "DNA_scene_types.h" #include "DNA_gpencil_types.h" +#include "DNA_brush_types.h" #include "DNA_windowmanager_types.h" #include "UI_view2d.h" @@ -151,6 +156,10 @@ typedef struct tGPsdata { float custom_color[4]; /* custom color - hack for enforcing a particular color for track/mask editing */ void *erasercursor; /* radial cursor data for drawing eraser */ + + bGPDpalettecolor *palettecolor; /* current palette color */ + bGPDbrush *brush; /* current drawing brush */ + short straight[2]; /* 1: line horizontal, 2: line vertical, other: not defined, second element position */ } tGPsdata; /* ------ */ @@ -333,10 +342,90 @@ static void gp_stroke_convertcoords(tGPsdata *p, const int mval[2], float out[3] } } +/* apply jitter to stroke */ +static void gp_brush_jitter(bGPdata *gpd, bGPDbrush *brush, tGPspoint *pt, const int mval[2], int r_mval[2]) +{ + float pressure = pt->pressure; + float tmp_pressure = pt->pressure; + if (brush->draw_jitter > 0.0f) { + float curvef = curvemapping_evaluateF(brush->cur_jitter, 0, pressure); + tmp_pressure = curvef * brush->draw_sensitivity; + } + const float exfactor = (brush->draw_jitter + 2.0f) * (brush->draw_jitter + 2.0f); /* exponential value */ + const float fac = BLI_frand() * exfactor * tmp_pressure; + /* Jitter is applied perpendicular to the mouse movement vector (2D space) */ + float mvec[2], svec[2]; + /* mouse movement in ints -> floats */ + if (gpd->sbuffer_size > 1) { + mvec[0] = (float)(mval[0] - (pt - 1)->x); + mvec[1] = (float)(mval[1] - (pt - 1)->y); + 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_frand() > 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]; + +} + +/* apply pressure change depending of the angle of the stroke to simulate a pen with shape */ +static void gp_brush_angle(bGPdata *gpd, bGPDbrush *brush, tGPspoint *pt, const int mval[2]) +{ + float mvec[2]; + float sen = brush->draw_angle_factor; /* sensitivity */; + float fac; + float mpressure; + + float angle = brush->draw_angle; /* default angle of brush in radians */; + float v0[2] = { cos(angle), sin(angle) }; /* angle vector of the brush with full thickness */ + + /* Apply to first point (only if there are 2 points because before no data to do it ) */ + if (gpd->sbuffer_size == 1) { + mvec[0] = (float)(mval[0] - (pt - 1)->x); + mvec[1] = (float)(mval[1] - (pt - 1)->y); + normalize_v2(mvec); + + /* uses > 1.0f to get a smooth transition in first point */ + fac = 1.4f - fabs(dot_v2v2(v0, mvec)); /* 0.0 to 1.0 */ + (pt - 1)->pressure = (pt - 1)->pressure - (sen * fac); + + CLAMP((pt - 1)->pressure, GPENCIL_ALPHA_OPACITY_THRESH, 1.0f); + } + + /* apply from second point */ + if (gpd->sbuffer_size >= 1) { + mvec[0] = (float)(mval[0] - (pt - 1)->x); + mvec[1] = (float)(mval[1] - (pt - 1)->y); + normalize_v2(mvec); + + fac = 1.0f - fabs(dot_v2v2(v0, mvec)); /* 0.0 to 1.0 */ + /* interpolate with previous point for smoother transitions */ + mpressure = interpf(pt->pressure - (sen * fac), (pt - 1)->pressure, 0.3f); + pt->pressure = mpressure; + + CLAMP(pt->pressure, GPENCIL_ALPHA_OPACITY_THRESH, 1.0f); + } + +} + /* add current stroke-point to buffer (returns whether point was successfully added) */ static short gp_stroke_addpoint(tGPsdata *p, const int mval[2], float pressure, double curtime) { bGPdata *gpd = p->gpd; + bGPDbrush *brush = p->brush; tGPspoint *pt; /* check painting mode */ @@ -349,6 +438,7 @@ static short gp_stroke_addpoint(tGPsdata *p, const int mval[2], float pressure, /* store settings */ copy_v2_v2_int(&pt->x, mval); pt->pressure = 1.0f; /* T44932 - Pressure vals are unreliable, so ignore for now */ + pt->strength = 1.0f; pt->time = (float)(curtime - p->inittime); /* increment buffer size */ @@ -363,6 +453,7 @@ static short gp_stroke_addpoint(tGPsdata *p, const int mval[2], float pressure, /* store settings */ copy_v2_v2_int(&pt->x, mval); pt->pressure = 1.0f; /* T44932 - Pressure vals are unreliable, so ignore for now */ + pt->strength = 1.0f; pt->time = (float)(curtime - p->inittime); /* now the buffer has 2 points (and shouldn't be allowed to get any larger) */ @@ -381,8 +472,65 @@ static short gp_stroke_addpoint(tGPsdata *p, const int mval[2], float pressure, pt = ((tGPspoint *)(gpd->sbuffer) + gpd->sbuffer_size); /* store settings */ - copy_v2_v2_int(&pt->x, mval); - pt->pressure = pressure; + /* pressure */ + if (brush->flag & GP_BRUSH_USE_PRESSURE) { + float curvef = curvemapping_evaluateF(brush->cur_sensitivity, 0, pressure); + pt->pressure = curvef * brush->draw_sensitivity; + } + else { + pt->pressure = 1.0f; + } + /* Apply jitter to position */ + if (brush->draw_jitter > 0.0f) { + int r_mval[2]; + gp_brush_jitter(gpd, brush, pt, mval, r_mval); + copy_v2_v2_int(&pt->x, r_mval); + } + else { + copy_v2_v2_int(&pt->x, mval); + } + /* apply randomness to pressure */ + if ((brush->draw_random_press > 0.0f) && (brush->flag & GP_BRUSH_USE_RANDOM_PRESSURE)) { + float curvef = curvemapping_evaluateF(brush->cur_sensitivity, 0, pressure); + float tmp_pressure = curvef * brush->draw_sensitivity; + if (BLI_frand() > 0.5f) { + pt->pressure -= tmp_pressure * brush->draw_random_press * BLI_frand(); + } + else { + pt->pressure += tmp_pressure * brush->draw_random_press * BLI_frand(); + } + CLAMP(pt->pressure, GPENCIL_STRENGTH_MIN, 1.0f); + } + + /* apply angle of stroke to brush size */ + if (brush->draw_angle_factor > 0.0f) { + gp_brush_angle(gpd, brush, pt, mval); + } + + /* color strength */ + if (brush->flag & GP_BRUSH_USE_STENGTH_PRESSURE) { + float curvef = curvemapping_evaluateF(brush->cur_strength, 0, pressure); + float tmp_pressure = curvef * brush->draw_sensitivity; + + pt->strength = tmp_pressure * brush->draw_strength; + } + else { + pt->strength = brush->draw_strength; + } + CLAMP(pt->strength, GPENCIL_STRENGTH_MIN, 1.0f); + + /* apply randomness to color strength */ + if ((brush->draw_random_press > 0.0f) && (brush->flag & GP_BRUSH_USE_RANDOM_STRENGTH)) { + if (BLI_frand() > 0.5f) { + pt->strength -= pt->strength * brush->draw_random_press * BLI_frand(); + } + else { + pt->strength += pt->strength * brush->draw_random_press * BLI_frand(); + } + CLAMP(pt->strength, GPENCIL_STRENGTH_MIN, 1.0f); + } + + /* point time */ pt->time = (float)(curtime - p->inittime); /* increment counters */ @@ -395,12 +543,15 @@ static short gp_stroke_addpoint(tGPsdata *p, const int mval[2], float pressure, return GP_STROKEADD_NORMAL; } else if (p->paintmode == GP_PAINTMODE_DRAW_POLY) { + + bGPDlayer *gpl = gpencil_layer_getactive(gpd); /* get pointer to destination point */ pt = (tGPspoint *)(gpd->sbuffer); /* store settings */ copy_v2_v2_int(&pt->x, mval); pt->pressure = 1.0f; /* T44932 - Pressure vals are unreliable, so ignore for now */ + pt->strength = 1.0f; pt->time = (float)(curtime - p->inittime); /* if there's stroke for this poly line session add (or replace last) point @@ -433,10 +584,16 @@ static short gp_stroke_addpoint(tGPsdata *p, const int mval[2], float pressure, /* convert screen-coordinates to appropriate coordinates (and store them) */ gp_stroke_convertcoords(p, &pt->x, &pts->x, NULL); - + /* if parented change position relative to parent object */ + if (gpl->parent != NULL) { + gp_apply_parent_point(gpl, pts); + } /* copy pressure and time */ pts->pressure = pt->pressure; + pts->strength = pt->strength; pts->time = pt->time; + /* force fill recalc */ + gps->flag |= GP_STROKE_RECALC_CACHES; } /* increment counters */ @@ -534,6 +691,7 @@ static void gp_stroke_newfrombuffer(tGPsdata *p) bGPDstroke *gps; bGPDspoint *pt; tGPspoint *ptc; + bGPDbrush *brush = p->brush; int i, totelem; /* since strokes are so fine, when using their depth we need a margin otherwise they might get missed */ @@ -569,7 +727,7 @@ static void gp_stroke_newfrombuffer(tGPsdata *p) /* copy appropriate settings for stroke */ gps->totpoints = totelem; - gps->thickness = p->gpl->thickness; + gps->thickness = brush->thickness; gps->flag = gpd->sbuffer_sflag; gps->inittime = p->inittime; @@ -577,7 +735,7 @@ static void gp_stroke_newfrombuffer(tGPsdata *p) gps->flag |= GP_STROKE_RECALC_CACHES; /* allocate enough memory for a continuous array for storage points */ - int sublevel = gpl->sublevel; + int sublevel = brush->sublevel; int new_totpoints = gps->totpoints; for (i = 0; i < sublevel; i++) { @@ -600,9 +758,14 @@ static void gp_stroke_newfrombuffer(tGPsdata *p) /* convert screen-coordinates to appropriate coordinates (and store them) */ gp_stroke_convertcoords(p, &ptc->x, &pt->x, NULL); - + /* if parented change position relative to parent object */ + if (gpl->parent != NULL) { + gp_apply_parent_point(gpl, pt); + } /* copy pressure and time */ pt->pressure = ptc->pressure; + pt->strength = ptc->strength; + CLAMP(pt->strength, GPENCIL_STRENGTH_MIN, 1.0f); pt->time = ptc->time; pt++; @@ -614,9 +777,15 @@ static void gp_stroke_newfrombuffer(tGPsdata *p) /* convert screen-coordinates to appropriate coordinates (and store them) */ gp_stroke_convertcoords(p, &ptc->x, &pt->x, NULL); - + /* if parented change position relative to parent object */ + if (gpl->parent != NULL) { + gp_apply_parent_point(gpl, pt); + } + /* copy pressure and time */ pt->pressure = ptc->pressure; + pt->strength = ptc->strength; + CLAMP(pt->strength, GPENCIL_STRENGTH_MIN, 1.0f); pt->time = ptc->time; } } @@ -626,9 +795,14 @@ static void gp_stroke_newfrombuffer(tGPsdata *p) /* convert screen-coordinates to appropriate coordinates (and store them) */ gp_stroke_convertcoords(p, &ptc->x, &pt->x, NULL); - + /* if parented change position relative to parent object */ + if (gpl->parent != NULL) { + gp_apply_parent_point(gpl, pt); + } /* copy pressure and time */ pt->pressure = ptc->pressure; + pt->strength = ptc->strength; + CLAMP(pt->strength, GPENCIL_STRENGTH_MIN, 1.0f); pt->time = ptc->time; } else { @@ -703,6 +877,8 @@ static void gp_stroke_newfrombuffer(tGPsdata *p) /* copy pressure and time */ pt->pressure = ptc->pressure; + pt->strength = ptc->strength; + CLAMP(pt->strength, GPENCIL_STRENGTH_MIN, 1.0f); pt->time = ptc->time; } @@ -716,25 +892,38 @@ static void gp_stroke_newfrombuffer(tGPsdata *p) gp_subdivide_stroke(gps, totpoints); } } - + /* apply randomness to stroke */ + if (brush->draw_random_sub > 0.0f) { + gp_randomize_stroke(gps, brush); + } + /* smooth stroke after subdiv - only if there's something to do * for each iteration, the factor is reduced to get a better smoothing without changing too much * the original stroke */ - if (gpl->draw_smoothfac > 0.0f) { + if (brush->draw_smoothfac > 0.0f) { float reduce = 0.0f; - for (int r = 0; r < gpl->draw_smoothlvl; ++r) { + for (int r = 0; r < brush->draw_smoothlvl; ++r) { for (i = 0; i < gps->totpoints; i++) { /* NOTE: No pressure smoothing, or else we get annoying thickness changes while drawing... */ - gp_smooth_stroke(gps, i, gpl->draw_smoothfac - reduce, false); + gp_smooth_stroke(gps, i, brush->draw_smoothfac - reduce, false); } reduce += 0.25f; // reduce the factor } } - + /* if parented change position relative to parent object */ + if (gpl->parent != NULL) { + gp_apply_parent(gpl, gps); + } + if (depth_arr) MEM_freeN(depth_arr); } + /* Save palette color */ + bGPDpalette *palette = gpencil_palette_getactive(p->gpd); + bGPDpalettecolor *palcolor = gpencil_palettecolor_getactive(palette); + gps->palcolor = palcolor; + strcpy(gps->colorname, palcolor->info); /* add stroke to frame */ BLI_addtail(&p->gpf->strokes, gps); @@ -761,12 +950,21 @@ static bool gp_stroke_eraser_is_occluded(tGPsdata *p, const bGPDspoint *pt, cons (p->flags & GP_PAINTFLAG_V3D_ERASER_DEPTH)) { RegionView3D *rv3d = p->ar->regiondata; + bGPDlayer *gpl = p->gpl; + const int mval[2] = {x, y}; float mval_3d[3]; - + float fpt[3]; + + float diff_mat[4][4]; + /* calculate difference matrix if parent object */ + ED_gpencil_parent_location(gpl, diff_mat); + if (ED_view3d_autodist_simple(p->ar, mval, mval_3d, 0, NULL)) { const float depth_mval = view3d_point_depth(rv3d, mval_3d); - const float depth_pt = view3d_point_depth(rv3d, &pt->x); + + mul_v3_m4v3(fpt, diff_mat, &pt->x); + const float depth_pt = view3d_point_depth(rv3d, fpt); if (depth_pt > depth_mval) { return true; @@ -804,7 +1002,13 @@ static void gp_stroke_eraser_dostroke(tGPsdata *p, int pc1[2] = {0}; int pc2[2] = {0}; int i; - + float diff_mat[4][4]; + + /* calculate difference matrix if parent object */ + if (gpl->parent != NULL) { + ED_gpencil_parent_location(gpl, diff_mat); + } + if (gps->totpoints == 0) { /* just free stroke */ if (gps->points) @@ -816,8 +1020,14 @@ static void gp_stroke_eraser_dostroke(tGPsdata *p, else if (gps->totpoints == 1) { /* only process if it hasn't been masked out... */ if (!(p->flags & GP_PAINTFLAG_SELECTMASK) || (gps->points->flag & GP_SPOINT_SELECT)) { - gp_point_to_xy(&p->gsc, gps, gps->points, &pc1[0], &pc1[1]); - + if (gpl->parent == NULL) { + gp_point_to_xy(&p->gsc, gps, gps->points, &pc1[0], &pc1[1]); + } + else { + bGPDspoint pt_temp; + gp_point_to_parent_space(gps->points, 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])) { /* only check if point is inside */ @@ -826,7 +1036,7 @@ static void gp_stroke_eraser_dostroke(tGPsdata *p, // XXX: pressure sensitive eraser should apply here too? MEM_freeN(gps->points); if (gps->triangles) - MEM_freeN(gps->triangles); + MEM_freeN(gps->triangles); BLI_freelinkN(&gpf->strokes, gps); } } @@ -836,7 +1046,7 @@ static void gp_stroke_eraser_dostroke(tGPsdata *p, /* Pressure threshold at which stroke should be culled: Calculated as pressure value * below which we would have invisible strokes */ - const float cull_thresh = (gpl->thickness) ? 1.0f / ((float)gpl->thickness) : 1.0f; + const float cull_thresh = (gps->thickness) ? 1.0f / ((float)gps->thickness) : 1.0f; /* Amount to decrease the pressure of each point with each stroke */ // TODO: Fetch from toolsettings, or compute based on thickness instead? @@ -865,15 +1075,24 @@ static void gp_stroke_eraser_dostroke(tGPsdata *p, /* get points to work with */ pt1 = gps->points + i; pt2 = gps->points + i + 1; - + /* only process if it hasn't been masked out... */ if ((p->flags & GP_PAINTFLAG_SELECTMASK) && !(gps->points->flag & GP_SPOINT_SELECT)) continue; - /* get coordinates of point in screenspace */ - gp_point_to_xy(&p->gsc, gps, pt1, &pc1[0], &pc1[1]); - gp_point_to_xy(&p->gsc, gps, pt2, &pc2[0], &pc2[1]); - + if (gpl->parent == NULL) { + gp_point_to_xy(&p->gsc, gps, pt1, &pc1[0], &pc1[1]); + gp_point_to_xy(&p->gsc, gps, pt2, &pc2[0], &pc2[1]); + } + else { + bGPDspoint npt; + gp_point_to_parent_space(pt1, 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_xy(&p->gsc, gps, &npt, &pc2[0], &pc2[1]); + } + /* Check that point segment of the boundbox of the eraser stroke */ if (((!ELEM(V2D_IS_CLIPPED, pc1[0], pc1[1])) && BLI_rcti_isect_pt(rect, pc1[0], pc1[1])) || ((!ELEM(V2D_IS_CLIPPED, pc2[0], pc2[1])) && BLI_rcti_isect_pt(rect, pc2[0], pc2[1]))) @@ -955,7 +1174,10 @@ static void gp_stroke_doeraser(tGPsdata *p) /* loop over strokes, checking segments for intersections */ for (gps = gpf->strokes.first; gps; gps = gpn) { gpn = gps->next; - + /* check if the color is editable */ + if (ED_gpencil_stroke_color_use(gpl, gps) == false) { + 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) */ @@ -994,6 +1216,78 @@ static void gp_session_validatebuffer(tGPsdata *p) p->inittime = 0.0; } +/* create a new palette color */ +static bGPDpalettecolor *gp_create_new_color(bGPDpalette *palette) +{ + bGPDpalettecolor *palcolor; + + palcolor = gpencil_palettecolor_addnew(palette, DATA_("Color"), true); + + return palcolor; +} + +/* initialize a drawing brush */ +static void gp_init_drawing_brush(ToolSettings *ts, tGPsdata *p) +{ + bGPDbrush *brush; + + /* if not exist, create a new one */ + if (BLI_listbase_is_empty(&ts->gp_brushes)) { + /* create new brushes */ + gpencil_brush_init_presets(ts); + brush = gpencil_brush_getactive(ts); + } + else { + /* Use the current */ + brush = gpencil_brush_getactive(ts); + } + /* be sure curves are initializated */ + curvemapping_initialize(brush->cur_sensitivity); + curvemapping_initialize(brush->cur_strength); + curvemapping_initialize(brush->cur_jitter); + + /* asign to temp tGPsdata */ + p->brush = brush; +} + + +/* initialize a paint palette brush and a default color if not exist */ +static void gp_init_palette(tGPsdata *p) +{ + bGPdata *gpd; + bGPDpalette *palette; + bGPDpalettecolor *palcolor; + + gpd = p->gpd; + + /* if not exist, create a new palette */ + if (BLI_listbase_is_empty(&gpd->palettes)) { + /* create new palette */ + palette = gpencil_palette_addnew(gpd, DATA_("GP_Palette"), true); + /* now create a default color */ + palcolor = gp_create_new_color(palette); + } + else { + /* Use the current palette and color */ + palette = gpencil_palette_getactive(gpd); + /* the palette needs one color */ + if (BLI_listbase_is_empty(&palette->colors)) { + palcolor = gp_create_new_color(palette); + } + else { + palcolor = gpencil_palettecolor_getactive(palette); + } + /* in some situations can be null, so use first */ + if (palcolor == NULL) { + gpencil_palettecolor_setactive(palette, palette->colors.first); + palcolor = palette->colors.first; + } + } + + /* asign to temp tGPsdata */ + p->palettecolor = palcolor; +} + /* (re)init new painting data */ static bool gp_session_initdata(bContext *C, tGPsdata *p) { @@ -1158,7 +1452,16 @@ static bool gp_session_initdata(bContext *C, tGPsdata *p) /* clear out buffer (stored in gp-data), in case something contaminated it */ gp_session_validatebuffer(p); - + /* set brush and create a new one if null */ + gp_init_drawing_brush(ts, p); + /* set palette info and create a new one if null */ + gp_init_palette(p); + /* set palette colors */ + bGPDpalettecolor *palcolor = p->palettecolor; + bGPdata *pdata = p->gpd; + copy_v4_v4(pdata->scolor, palcolor->color); + pdata->sflag = palcolor->flag; + return 1; } @@ -1177,7 +1480,7 @@ static tGPsdata *gp_session_initpaint(bContext *C) * erase size won't get lost */ p->radius = U.gp_eraser; - + /* return context data for running paint operator */ return p; } @@ -1495,7 +1798,6 @@ static bool gpencil_is_tablet_eraser_active(const wmEvent *event) /* ------------------------------- */ - static void gpencil_draw_exit(bContext *C, wmOperator *op) { tGPsdata *p = op->customdata; @@ -1513,7 +1815,7 @@ static void gpencil_draw_exit(bContext *C, wmOperator *op) /* turn off radial brush cursor */ gpencil_draw_toggle_eraser_cursor(C, p, false); } - + /* always store the new eraser size to be used again next time * NOTE: Do this even when not in eraser mode, as eraser may * have been toggled at some point. @@ -1600,7 +1902,7 @@ static void gpencil_draw_status_indicators(tGPsdata *p) break; case GP_PAINTMODE_DRAW: ED_area_headerprint(p->sa, IFACE_("Grease Pencil Freehand Session: Hold and drag LMB to draw | " - "ESC/Enter to end (or click outside this area)")); + "E/ESC/Enter to end (or click outside this area)")); break; case GP_PAINTMODE_DRAW_POLY: ED_area_headerprint(p->sa, IFACE_("Grease Pencil Poly Session: LMB click to place next stroke vertex | " @@ -1691,6 +1993,31 @@ static void gpencil_draw_apply_event(wmOperator *op, const wmEvent *event) */ p->mval[0] = event->mval[0] + 1; p->mval[1] = event->mval[1] + 1; + + /* verify key status for straight lines */ + if ((event->ctrl > 0) || (event->alt > 0)) { + if (p->straight[0] == 0) { + int dx = abs(p->mval[0] - p->mvalo[0]); + int dy = abs(p->mval[1] - p->mvalo[1]); + if ((dx > 0) || (dy > 0)) { + /* check mouse direction to replace the other coordinate with previous values */ + if (dx >= dy) { + /* horizontal */ + p->straight[0] = 1; + p->straight[1] = p->mval[1]; /* save y */ + } + else { + /* vertical */ + p->straight[0] = 2; + p->straight[1] = p->mval[0]; /* save x */ + } + } + } + } + else { + p->straight[0] = 0; + } + p->curtime = PIL_check_seconds_timer(); /* handle pressure sensitivity (which is supplied by tablets) */ @@ -1725,7 +2052,9 @@ static void gpencil_draw_apply_event(wmOperator *op, const wmEvent *event) p->mvalo[1] = p->mval[1]; p->opressure = p->pressure; p->inittime = p->ocurtime = p->curtime; - + p->straight[0] = 0; + p->straight[1] = 0; + /* special exception here for too high pressure values on first touch in * windows for some tablets, then we just skip first touch... */ @@ -1733,6 +2062,18 @@ static void gpencil_draw_apply_event(wmOperator *op, const wmEvent *event) return; } + /* check if alt key is pressed and limit to straight lines */ + if (p->straight[0] != 0) { + if (p->straight[0] == 1) { + /* horizontal */ + p->mval[1] = p->straight[1]; /* replace y */ + } + else { + /* vertical */ + p->mval[0] = p->straight[1]; /* replace x */ + } + } + /* fill in stroke data (not actually used directly by gpencil_draw_apply) */ RNA_collection_add(op->ptr, "stroke", &itemptr); @@ -1855,21 +2196,21 @@ static int gpencil_draw_invoke(bContext *C, wmOperator *op, const wmEvent *event if (p->paintmode == GP_PAINTMODE_ERASER) { gpencil_draw_toggle_eraser_cursor(C, p, true); } - /* 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(op, event); + op->flag |= OP_IS_MODAL_CURSOR_REGION; } else { /* toolbar invoked - don't start drawing yet... */ @@ -1976,6 +2317,10 @@ static int gpencil_draw_modal(bContext *C, wmOperator *op, const wmEvent *event) * is essential for ensuring that they can quickly return to that view */ } + else if ((ELEM(event->type, DKEY)) && (event->val == KM_RELEASE)) { + /* enable continuous if release D key in mid drawing */ + p->scene->toolsettings->gpencil_flags |= GP_TOOL_FLAG_PAINTSESSIONS_ON; + } else { estate = OPERATOR_RUNNING_MODAL; } @@ -1986,7 +2331,7 @@ static int gpencil_draw_modal(bContext *C, wmOperator *op, const wmEvent *event) /* exit painting mode (and/or end current stroke) * NOTE: cannot do RIGHTMOUSE (as is standard for canceling) as that would break polyline [#32647] */ - if (ELEM(event->type, RETKEY, PADENTER, ESCKEY, SPACEKEY)) { + if (ELEM(event->type, RETKEY, PADENTER, ESCKEY, SPACEKEY, EKEY)) { /* exit() ends the current stroke before cleaning up */ /* printf("\t\tGP - end of paint op + end of stroke\n"); */ p->status = GP_STATUS_DONE; @@ -2045,6 +2390,9 @@ static int gpencil_draw_modal(bContext *C, wmOperator *op, const wmEvent *event) } else { /* printf("\t\tGP - end of stroke + op\n"); */ + /* disable paint session */ + p->scene->toolsettings->gpencil_flags &= ~GP_TOOL_FLAG_PAINTSESSIONS_ON; + p->status = GP_STATUS_DONE; estate = OPERATOR_FINISHED; } @@ -2074,6 +2422,9 @@ static int gpencil_draw_modal(bContext *C, wmOperator *op, const wmEvent *event) in_bounds = true; } else { + /* disable paint session */ + p->scene->toolsettings->gpencil_flags &= ~GP_TOOL_FLAG_PAINTSESSIONS_ON; + /* Out of bounds, or invalid in some other way */ p->status = GP_STATUS_ERROR; estate = OPERATOR_CANCELLED; @@ -2090,6 +2441,9 @@ static int gpencil_draw_modal(bContext *C, wmOperator *op, const wmEvent *event) in_bounds = BLI_rcti_isect_pt_v(®ion_rect, event->mval); } else { + /* disable paint session */ + p->scene->toolsettings->gpencil_flags &= ~GP_TOOL_FLAG_PAINTSESSIONS_ON; + /* No region */ p->status = GP_STATUS_ERROR; estate = OPERATOR_CANCELLED; @@ -2117,6 +2471,9 @@ static int gpencil_draw_modal(bContext *C, wmOperator *op, const wmEvent *event) p = gpencil_stroke_begin(C, op); if (p->status == GP_STATUS_ERROR) { + /* disable paint session */ + p->scene->toolsettings->gpencil_flags &= ~GP_TOOL_FLAG_PAINTSESSIONS_ON; + estate = OPERATOR_CANCELLED; } } @@ -2125,6 +2482,9 @@ static int gpencil_draw_modal(bContext *C, wmOperator *op, const wmEvent *event) * NOTE: Don't eter this case if an error occurred while finding the * region (as above) */ + /* disable paint session */ + p->scene->toolsettings->gpencil_flags &= ~GP_TOOL_FLAG_PAINTSESSIONS_ON; + p->status = GP_STATUS_DONE; estate = OPERATOR_FINISHED; } diff --git a/source/blender/editors/gpencil/gpencil_select.c b/source/blender/editors/gpencil/gpencil_select.c index b6482786b4f..612b35aa608 100644 --- a/source/blender/editors/gpencil/gpencil_select.c +++ b/source/blender/editors/gpencil/gpencil_select.c @@ -43,6 +43,7 @@ #include "DNA_gpencil_types.h" #include "DNA_scene_types.h" #include "DNA_screen_types.h" +#include "DNA_object_types.h" #include "BKE_context.h" #include "BKE_gpencil.h" @@ -616,9 +617,10 @@ void GPENCIL_OT_select_less(wmOperatorType *ot) /* NOTE: Code here is adapted (i.e. copied directly) 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(bGPDstroke *gps, GP_SpaceConversion *gsc, - const int mx, const int my, const int radius, - const bool select, rcti *rect) +static bool gp_stroke_do_circle_sel( + bGPDstroke *gps, GP_SpaceConversion *gsc, + const int mx, const int my, const int radius, + const bool select, rcti *rect, const bool parented, float diff_mat[4][4]) { bGPDspoint *pt1, *pt2; int x0 = 0, y0 = 0, x1 = 0, y1 = 0; @@ -626,7 +628,14 @@ static bool gp_stroke_do_circle_sel(bGPDstroke *gps, GP_SpaceConversion *gsc, bool changed = false; if (gps->totpoints == 1) { - gp_point_to_xy(gsc, gps, gps->points, &x0, &y0); + if (!parented) { + gp_point_to_xy(gsc, gps, gps->points, &x0, &y0); + } + else { + bGPDspoint pt_temp; + gp_point_to_parent_space(gps->points, diff_mat, &pt_temp); + gp_point_to_xy(gsc, gps, &pt_temp, &x0, &y0); + } /* do boundbox check first */ if ((!ELEM(V2D_IS_CLIPPED, x0, y0)) && BLI_rcti_isect_pt(rect, x0, y0)) { @@ -654,9 +663,18 @@ static bool gp_stroke_do_circle_sel(bGPDstroke *gps, GP_SpaceConversion *gsc, /* get points to work with */ pt1 = gps->points + i; pt2 = gps->points + i + 1; - - gp_point_to_xy(gsc, gps, pt1, &x0, &y0); - gp_point_to_xy(gsc, gps, pt2, &x1, &y1); + if (!parented) { + gp_point_to_xy(gsc, gps, pt1, &x0, &y0); + gp_point_to_xy(gsc, gps, pt2, &x1, &y1); + } + else { + bGPDspoint npt; + gp_point_to_parent_space(pt1, diff_mat, &npt); + gp_point_to_xy(gsc, gps, &npt, &x0, &y0); + + gp_point_to_parent_space(pt2, diff_mat, &npt); + gp_point_to_xy(gsc, gps, &npt, &x1, &y1); + } /* check that point segment of the boundbox of the selection stroke */ if (((!ELEM(V2D_IS_CLIPPED, x0, y0)) && BLI_rcti_isect_pt(rect, x0, y0)) || @@ -733,12 +751,14 @@ static int gpencil_circle_select_exec(bContext *C, wmOperator *op) /* find visible strokes, and select if hit */ - CTX_DATA_BEGIN(C, bGPDstroke *, gps, editable_gpencil_strokes) + GP_EDITABLE_STROKES_BEGIN(C, gpl, gps) { - changed |= gp_stroke_do_circle_sel(gps, &gsc, mx, my, radius, select, &rect); + changed |= gp_stroke_do_circle_sel( + gps, &gsc, mx, my, radius, select, &rect, + (gpl->parent != NULL), diff_mat); } - CTX_DATA_END; - + GP_EDITABLE_STROKES_END; + /* updates */ if (changed) { WM_event_add_notifier(C, NC_GPENCIL | NA_SELECTED, NULL); @@ -818,17 +838,25 @@ static int gpencil_border_select_exec(bContext *C, wmOperator *op) WM_operator_properties_border_to_rcti(op, &rect); /* select/deselect points */ - CTX_DATA_BEGIN(C, bGPDstroke *, gps, editable_gpencil_strokes) + GP_EDITABLE_STROKES_BEGIN(C, gpl, gps) { + bGPDspoint *pt; int i; - + for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) { int x0, y0; - + /* convert point coords to screenspace */ - gp_point_to_xy(&gsc, gps, pt, &x0, &y0); - + if (gpl->parent == NULL) { + gp_point_to_xy(&gsc, gps, pt, &x0, &y0); + } + else { + bGPDspoint pt2; + gp_point_to_parent_space(pt, diff_mat, &pt2); + gp_point_to_xy(&gsc, gps, &pt2, &x0, &y0); + } + /* test if in selection rect */ if ((!ELEM(V2D_IS_CLIPPED, x0, y0)) && BLI_rcti_isect_pt(&rect, x0, y0)) { if (select) { @@ -837,16 +865,16 @@ static int gpencil_border_select_exec(bContext *C, wmOperator *op) else { pt->flag &= ~GP_SPOINT_SELECT; } - + changed = true; } } - + /* Ensure that stroke selection is in sync with its points */ gpencil_stroke_sync_selection(gps); } - CTX_DATA_END; - + GP_EDITABLE_STROKES_END; + /* updates */ if (changed) { WM_event_add_notifier(C, NC_GPENCIL | NA_SELECTED, NULL); @@ -920,20 +948,26 @@ static int gpencil_lasso_select_exec(bContext *C, wmOperator *op) } /* select/deselect points */ - CTX_DATA_BEGIN(C, bGPDstroke *, gps, editable_gpencil_strokes) + GP_EDITABLE_STROKES_BEGIN(C, gpl, gps) { bGPDspoint *pt; int i; - + for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) { int x0, y0; - + /* convert point coords to screenspace */ - gp_point_to_xy(&gsc, gps, pt, &x0, &y0); - + if (gpl->parent == NULL) { + gp_point_to_xy(&gsc, gps, pt, &x0, &y0); + } + else { + bGPDspoint pt2; + gp_point_to_parent_space(pt, diff_mat, &pt2); + gp_point_to_xy(&gsc, gps, &pt2, &x0, &y0); + } /* test if in lasso boundbox + within the lasso noose */ if ((!ELEM(V2D_IS_CLIPPED, x0, y0)) && BLI_rcti_isect_pt(&rect, x0, y0) && - BLI_lasso_is_point_inside(mcords, mcords_tot, x0, y0, INT_MAX)) + BLI_lasso_is_point_inside(mcords, mcords_tot, x0, y0, INT_MAX)) { if (select) { pt->flag |= GP_SPOINT_SELECT; @@ -941,16 +975,16 @@ static int gpencil_lasso_select_exec(bContext *C, wmOperator *op) else { pt->flag &= ~GP_SPOINT_SELECT; } - + changed = true; } } - + /* Ensure that stroke selection is in sync with its points */ gpencil_stroke_sync_selection(gps); } - CTX_DATA_END; - + GP_EDITABLE_STROKES_END; + /* cleanup */ MEM_freeN((void *)mcords); @@ -1020,35 +1054,42 @@ static int gpencil_select_exec(bContext *C, wmOperator *op) /* First Pass: Find stroke point which gets hit */ /* XXX: maybe we should go from the top of the stack down instead... */ - CTX_DATA_BEGIN(C, bGPDstroke *, gps, editable_gpencil_strokes) + GP_EDITABLE_STROKES_BEGIN(C, gpl, gps) { bGPDspoint *pt; int i; - + /* firstly, check for hit-point */ for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) { int xy[2]; - - gp_point_to_xy(&gsc, gps, pt, &xy[0], &xy[1]); - + + if (gpl->parent == NULL) { + gp_point_to_xy(&gsc, gps, pt, &xy[0], &xy[1]); + } + else { + bGPDspoint pt2; + gp_point_to_parent_space(pt, diff_mat, &pt2); + gp_point_to_xy(&gsc, gps, &pt2, &xy[0], &xy[1]); + } + /* do boundbox check first */ if (!ELEM(V2D_IS_CLIPPED, xy[0], xy[1])) { const int pt_distance = len_manhattan_v2v2_int(mval, xy); - + /* check if point is inside */ if (pt_distance <= radius_squared) { /* only use this point if it is a better match than the current hit - T44685 */ if (pt_distance < hit_distance) { hit_stroke = gps; - hit_point = pt; + hit_point = pt; hit_distance = pt_distance; } } } } } - CTX_DATA_END; - + GP_EDITABLE_STROKES_END; + /* Abort if nothing hit... */ if (ELEM(NULL, hit_stroke, hit_point)) { return OPERATOR_CANCELLED; diff --git a/source/blender/editors/gpencil/gpencil_undo.c b/source/blender/editors/gpencil/gpencil_undo.c index f9b479ca03d..1d7582eb18b 100644 --- a/source/blender/editors/gpencil/gpencil_undo.c +++ b/source/blender/editors/gpencil/gpencil_undo.c @@ -142,7 +142,7 @@ void gpencil_undo_push(bGPdata *gpd) */ undo_node->gpd->adt = NULL; - BKE_gpencil_free(undo_node->gpd); + BKE_gpencil_free(undo_node->gpd, false); MEM_freeN(undo_node->gpd); BLI_freelinkN(&undo_nodes, undo_node); @@ -170,7 +170,7 @@ void gpencil_undo_finish(void) */ undo_node->gpd->adt = NULL; - BKE_gpencil_free(undo_node->gpd); + BKE_gpencil_free(undo_node->gpd, false); MEM_freeN(undo_node->gpd); undo_node = undo_node->next; diff --git a/source/blender/editors/gpencil/gpencil_utils.c b/source/blender/editors/gpencil/gpencil_utils.c index d62625baaa4..f2c542da0aa 100644 --- a/source/blender/editors/gpencil/gpencil_utils.c +++ b/source/blender/editors/gpencil/gpencil_utils.c @@ -17,7 +17,7 @@ * * The Original Code is Copyright (C) 2014, Blender Foundation * - * Contributor(s): Joshua Leung + * Contributor(s): Joshua Leung, Antonio Vazquez * * ***** END GPL LICENSE BLOCK ***** */ @@ -32,9 +32,13 @@ #include <stddef.h> #include <math.h> +#include "MEM_guardedalloc.h" + #include "BLI_math.h" #include "BLI_blenlib.h" #include "BLI_utildefines.h" +#include "BLT_translation.h" +#include "BLI_rand.h" #include "DNA_gpencil_types.h" #include "DNA_object_types.h" @@ -46,6 +50,7 @@ #include "BKE_context.h" #include "BKE_gpencil.h" #include "BKE_tracking.h" +#include "BKE_action.h" #include "WM_api.h" @@ -269,6 +274,34 @@ int gp_active_layer_poll(bContext *C) return (gpl != NULL); } +/* poll callback for checking if there is an active brush */ +int gp_active_brush_poll(bContext *C) +{ + ToolSettings *ts = CTX_data_tool_settings(C); + bGPDbrush *brush = gpencil_brush_getactive(ts); + + return (brush != NULL); +} + +/* poll callback for checking if there is an active palette */ +int gp_active_palette_poll(bContext *C) +{ + bGPdata *gpd = ED_gpencil_data_get_active(C); + bGPDpalette *palette = gpencil_palette_getactive(gpd); + + return (palette != NULL); +} + +/* poll callback for checking if there is an active palette color */ +int gp_active_palettecolor_poll(bContext *C) +{ + bGPdata *gpd = ED_gpencil_data_get_active(C); + bGPDpalette *palette = gpencil_palette_getactive(gpd); + bGPDpalettecolor *palcolor = gpencil_palettecolor_getactive(palette); + + return (palcolor != NULL); +} + /* ******************************************************** */ /* Dynamic Enums of GP Layers */ /* NOTE: These include an option to create a new layer and use that... */ @@ -412,6 +445,60 @@ bool ED_gpencil_stroke_can_use(const bContext *C, const bGPDstroke *gps) return ED_gpencil_stroke_can_use_direct(sa, gps); } +/* Check whether given stroke can be edited for the current color */ +bool ED_gpencil_stroke_color_use(const bGPDlayer *gpl, const bGPDstroke *gps) +{ + /* check if the color is editable */ + bGPDpalettecolor *palcolor = gps->palcolor; + if (palcolor != NULL) { + if (palcolor->flag & PC_COLOR_HIDE) + return false; + if (((gpl->flag & GP_LAYER_UNLOCK_COLOR) == 0) && (palcolor->flag & PC_COLOR_LOCKED)) + return false; + } + + return true; +} + +/* Get palette color or create a new one */ +bGPDpalettecolor *ED_gpencil_stroke_getcolor(bGPdata *gpd, bGPDstroke *gps) +{ + bGPDpalette *palette; + bGPDpalettecolor *palcolor; + + if ((gps->palcolor != NULL) && ((gps->flag & GP_STROKE_RECALC_COLOR) == 0)) + return gps->palcolor; + + /* get palette */ + palette = gpencil_palette_getactive(gpd); + if (palette == NULL) { + palette = gpencil_palette_addnew(gpd, DATA_("GP_Palette"), true); + } + /* get color */ + palcolor = gpencil_palettecolor_getbyname(palette, gps->colorname); + if (palcolor == NULL) { + if (gps->palcolor == NULL) { + palcolor = gpencil_palettecolor_addnew(palette, DATA_("Color"), true); + /* set to a different color */ + ARRAY_SET_ITEMS(palcolor->color, 1.0f, 0.0f, 1.0f, 0.9f); + } + else { + palcolor = gpencil_palettecolor_addnew(palette, gps->colorname, true); + /* set old color and attributes */ + bGPDpalettecolor *gpscolor = gps->palcolor; + copy_v4_v4(palcolor->color, gpscolor->color); + copy_v4_v4(palcolor->fill, gpscolor->fill); + palcolor->flag = gpscolor->flag; + } + } + + /* clear flag and set pointer */ + gps->flag &= ~GP_STROKE_RECALC_COLOR; + gps->palcolor = palcolor; + + return palcolor; +} + /* ******************************************************** */ /* Space Conversion */ @@ -451,6 +538,50 @@ void gp_point_conversion_init(bContext *C, GP_SpaceConversion *r_gsc) } } +/* convert point to parent space */ +void gp_point_to_parent_space(bGPDspoint *pt, float diff_mat[4][4], bGPDspoint *r_pt) +{ + float fpt[3]; + + mul_v3_m4v3(fpt, diff_mat, &pt->x); + copy_v3_v3(&r_pt->x, fpt); +} + +/* Change position relative to parent object */ +void gp_apply_parent(bGPDlayer *gpl, bGPDstroke *gps) +{ + bGPDspoint *pt; + int i; + + /* undo matrix */ + float diff_mat[4][4]; + float inverse_diff_mat[4][4]; + float fpt[3]; + + ED_gpencil_parent_location(gpl, diff_mat); + invert_m4_m4(inverse_diff_mat, diff_mat); + + for (i = 0; i < gps->totpoints; i++) { + pt = &gps->points[i]; + mul_v3_m4v3(fpt, inverse_diff_mat, &pt->x); + copy_v3_v3(&pt->x, fpt); + } +} + +/* Change point position relative to parent object */ +void gp_apply_parent_point(bGPDlayer *gpl, bGPDspoint *pt) +{ + /* undo matrix */ + float diff_mat[4][4]; + float inverse_diff_mat[4][4]; + float fpt[3]; + + ED_gpencil_parent_location(gpl, diff_mat); + invert_m4_m4(inverse_diff_mat, diff_mat); + + mul_v3_m4v3(fpt, inverse_diff_mat, &pt->x); + copy_v3_v3(&pt->x, fpt); +} /* Convert Grease Pencil points to screen-space values * WARNING: This assumes that the caller has already checked whether the stroke in question can be drawn @@ -591,25 +722,107 @@ bool gp_smooth_stroke(bGPDstroke *gps, int i, float inf, bool affect_pressure) madd_v3_v3fl(sco, &pt1->x, average_fac); madd_v3_v3fl(sco, &pt2->x, average_fac); +#if 0 + /* XXX: Disabled because get weird result */ /* do pressure too? */ if (affect_pressure) { pressure += pt1->pressure * average_fac; pressure += pt2->pressure * average_fac; } +#endif } } /* Based on influence factor, blend between original and optimal smoothed coordinate */ interp_v3_v3v3(&pt->x, &pt->x, sco, inf); +#if 0 + /* XXX: Disabled because get weird result */ if (affect_pressure) { pt->pressure = pressure; } +#endif return true; } /** +* Apply smooth for strength to stroke point +* \param gps Stroke to smooth +* \param i Point index +* \param inf Amount of smoothing to apply +*/ +bool gp_smooth_stroke_strength(bGPDstroke *gps, int i, float inf) +{ + bGPDspoint *ptb = &gps->points[i]; + + /* Do nothing if not enough points */ + if (gps->totpoints <= 2) { + return false; + } + + /* Compute theoretical optimal value using distances */ + bGPDspoint *pta, *ptc; + int before = i - 1; + int after = i + 1; + + CLAMP_MIN(before, 0); + CLAMP_MAX(after, gps->totpoints - 1); + + pta = &gps->points[before]; + ptc = &gps->points[after]; + + /* the optimal value is the corresponding to the interpolation of the strength + * at the distance of point b + */ + const float fac = line_point_factor_v3(&ptb->x, &pta->x, &ptc->x); + const float optimal = (1.0f - fac) * pta->strength + fac * ptc->strength; + + /* Based on influence factor, blend between original and optimal */ + ptb->strength = (1.0f - inf) * ptb->strength + inf * optimal; + + return true; +} + +/** +* Apply smooth for thickness to stroke point (use pressure) +* \param gps Stroke to smooth +* \param i Point index +* \param inf Amount of smoothing to apply +*/ +bool gp_smooth_stroke_thickness(bGPDstroke *gps, int i, float inf) +{ + bGPDspoint *ptb = &gps->points[i]; + + /* Do nothing if not enough points */ + if (gps->totpoints <= 2) { + return false; + } + + /* Compute theoretical optimal value using distances */ + bGPDspoint *pta, *ptc; + int before = i - 1; + int after = i + 1; + + CLAMP_MIN(before, 0); + CLAMP_MAX(after, gps->totpoints - 1); + + pta = &gps->points[before]; + ptc = &gps->points[after]; + + /* the optimal value is the corresponding to the interpolation of the pressure + * at the distance of point b + */ + float fac = line_point_factor_v3(&ptb->x, &pta->x, &ptc->x); + float optimal = (1.0f - fac) * pta->pressure + fac * ptc->pressure; + + /* Based on influence factor, blend between original and optimal */ + ptb->pressure = (1.0f - inf) * ptb->pressure + inf * optimal; + + return true; +} + +/** * Subdivide a stroke once, by adding a point half way between each pair of existing points * \param gps Stroke data * \param new_totpoints Total number of points (after subdividing) @@ -633,16 +846,99 @@ void gp_subdivide_stroke(bGPDstroke *gps, const int new_totpoints) interp_v3_v3v3(&pt->x, &prev->x, &next->x, 0.5f); pt->pressure = interpf(prev->pressure, next->pressure, 0.5f); - pt->time = interpf(prev->time, next->time, 0.5f); + pt->strength = interpf(prev->strength, next->strength, 0.5f); + CLAMP(pt->strength, GPENCIL_STRENGTH_MIN, 1.0f); + pt->time = interpf(prev->time, next->time, 0.5f); } /* Update to new total number of points */ gps->totpoints = new_totpoints; } -/* ******************************************************** */ +/** + * Add randomness to stroke + * \param gps Stroke data + * \param brsuh Brush data + */ +void gp_randomize_stroke(bGPDstroke *gps, bGPDbrush *brush) +{ + 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_frand() * (brush->draw_random_sub / 10.0f); + float svec[3]; + copy_v3_v3(svec, ortho); + if (BLI_frand() > 0.5f) { + mul_v3_fl(svec, -fac); + } + else { + mul_v3_fl(svec, fac); + } + + /* apply shift */ + add_v3_v3(&pt->x, svec); + } + +} +/* calculate difference matrix */ +void ED_gpencil_parent_location(bGPDlayer *gpl, float diff_mat[4][4]) +{ + Object *ob = gpl->parent; + if (ob == NULL) { + unit_m4(diff_mat); + return; + } + else { + if ((gpl->partype == PAROBJECT) || (gpl->partype == PARSKEL)) { + mul_m4_m4m4(diff_mat, ob->obmat, gpl->inverse); + return; + } + else if (gpl->partype == PARBONE) { + bPoseChannel *pchan = BKE_pose_channel_find_name(ob->pose, gpl->parsubstr); + if (pchan) { + float tmp_mat[4][4]; + mul_m4_m4m4(tmp_mat, ob->obmat, pchan->pose_mat); + mul_m4_m4m4(diff_mat, tmp_mat, gpl->inverse); + } + else { + mul_m4_m4m4(diff_mat, ob->obmat, gpl->inverse); /* if bone not found use object (armature) */ + } + return; + } + else { + unit_m4(diff_mat); /* not defined type */ + } + } +} +/* ******************************************************** */ bool ED_gpencil_stroke_minmax( const bGPDstroke *gps, const bool use_select, float r_min[3], float r_max[3]) @@ -659,3 +955,74 @@ bool ED_gpencil_stroke_minmax( } return changed; } +/* Dynamic Enums of GP Brushes */ + +EnumPropertyItem *ED_gpencil_brushes_enum_itemf( + bContext *C, PointerRNA *UNUSED(ptr), PropertyRNA *UNUSED(prop), + bool *r_free) +{ + ToolSettings *ts = CTX_data_tool_settings(C); + bGPDbrush *brush; + EnumPropertyItem *item = NULL, item_tmp = { 0 }; + int totitem = 0; + int i = 0; + + if (ELEM(NULL, C, ts)) { + return DummyRNA_DEFAULT_items; + } + + /* Existing brushes */ + for (brush = ts->gp_brushes.first; brush; brush = brush->next, i++) { + item_tmp.identifier = brush->info; + item_tmp.name = brush->info; + item_tmp.value = i; + + if (brush->flag & GP_BRUSH_ACTIVE) + item_tmp.icon = ICON_BRUSH_DATA; + else + item_tmp.icon = ICON_NONE; + + RNA_enum_item_add(&item, &totitem, &item_tmp); + } + + RNA_enum_item_end(&item, &totitem); + *r_free = true; + + return item; +} +/* Dynamic Enums of GP Palettes */ + +EnumPropertyItem *ED_gpencil_palettes_enum_itemf( + bContext *C, PointerRNA *UNUSED(ptr), PropertyRNA *UNUSED(prop), + bool *r_free) +{ + bGPdata *gpd = CTX_data_gpencil_data(C); + bGPDpalette *palette; + EnumPropertyItem *item = NULL, item_tmp = { 0 }; + int totitem = 0; + int i = 0; + + if (ELEM(NULL, C, gpd)) { + return DummyRNA_DEFAULT_items; + } + + /* Existing palettes */ + for (palette = gpd->palettes.first; palette; palette = palette->next, i++) { + item_tmp.identifier = palette->info; + item_tmp.name = palette->info; + item_tmp.value = i; + + if (palette->flag & PL_PALETTE_ACTIVE) + item_tmp.icon = ICON_COLOR; + else + item_tmp.icon = ICON_NONE; + + RNA_enum_item_add(&item, &totitem, &item_tmp); + } + + RNA_enum_item_end(&item, &totitem); + *r_free = true; + + return item; +} +/* ******************************************************** */ |