diff options
Diffstat (limited to 'source/blender/editors/gpencil/gpencil_paint.c')
-rw-r--r-- | source/blender/editors/gpencil/gpencil_paint.c | 451 |
1 files changed, 284 insertions, 167 deletions
diff --git a/source/blender/editors/gpencil/gpencil_paint.c b/source/blender/editors/gpencil/gpencil_paint.c index afecdd91599..df88da073ca 100644 --- a/source/blender/editors/gpencil/gpencil_paint.c +++ b/source/blender/editors/gpencil/gpencil_paint.c @@ -27,6 +27,7 @@ * \ingroup edgpencil */ + #include <stdio.h> #include <stddef.h> #include <stdlib.h> @@ -39,7 +40,7 @@ #include "BLI_math.h" #include "BLI_utildefines.h" -#include "BLF_translation.h" +#include "BLT_translation.h" #include "PIL_time.h" @@ -47,6 +48,7 @@ #include "BKE_context.h" #include "BKE_global.h" #include "BKE_report.h" +#include "BKE_screen.h" #include "BKE_tracking.h" #include "DNA_object_types.h" @@ -86,11 +88,13 @@ typedef struct tGPsdata { rctf *subrect; /* for using the camera rect within the 3d view */ rctf subrect_data; + GP_SpaceConversion gsc; /* settings to pass to gp_points_to_xy() */ + PointerRNA ownerPtr; /* pointer to owner of gp-datablock */ bGPdata *gpd; /* gp-datablock layer comes from */ bGPDlayer *gpl; /* layer we're working on */ bGPDframe *gpf; /* frame we're working on */ - + short status; /* current status of painting */ short paintmode; /* mode for painting */ @@ -110,9 +114,10 @@ typedef struct tGPsdata { double inittime; /* Used when converting to path */ double curtime; /* Used when converting to path */ double ocurtime; /* Used when converting to path */ - + float imat[4][4]; /* inverted transformation matrix applying when converting coords from screen-space * to region space */ + float mat[4][4]; float custom_color[4]; /* custom color - hack for enforcing a particular color for track/mask editing */ @@ -218,7 +223,7 @@ static void gp_get_3d_reference(tGPsdata *p, float vec[3]) if (p->ownerPtr.type == &RNA_Object) { Object *ob = (Object *)p->ownerPtr.data; - /* active Object + /* active Object * - use relative distance of 3D-cursor from object center */ sub_v3_v3v3(vec, fp, ob->loc); @@ -243,13 +248,13 @@ static bool gp_stroke_filtermval(tGPsdata *p, const int mval[2], int pmval[2]) if (p->gpd->sbuffer_size == 0) return true; - /* check if mouse moved at least certain distance on both axes (best case) + /* check if mouse moved at least certain distance on both axes (best case) * - aims to eliminate some jitter-noise from input when trying to draw straight lines freehand */ else if ((dx > MIN_MANHATTEN_PX) && (dy > MIN_MANHATTEN_PX)) return true; - /* check if the distance since the last point is significant enough + /* check if the distance since the last point is significant enough * - prevents points being added too densely * - distance here doesn't use sqrt to prevent slowness... we should still be safe from overflows though */ @@ -279,7 +284,7 @@ static void gp_stroke_convertcoords(tGPsdata *p, const int mval[2], float out[3] float rvec[3], dvec[3]; float mval_f[2] = {UNPACK2(mval)}; float zfac; - + /* Current method just converts each point in screen-coordinates to * 3D-coordinates using the 3D-cursor as reference. In general, this * works OK, but it could of course be improved. @@ -291,7 +296,7 @@ static void gp_stroke_convertcoords(tGPsdata *p, const int mval[2], float out[3] gp_get_3d_reference(p, rvec); zfac = ED_view3d_calc_zfac(p->ar->regiondata, rvec, NULL); - + if (ED_view3d_project_float_global(p->ar, rvec, mval_prj, V3D_PROJ_TEST_NOP) == V3D_PROJ_RET_OK) { sub_v2_v2v2(mval_f, mval_prj, mval_f); ED_view3d_win_to_delta(p->ar, mval_f, dvec, zfac); @@ -327,7 +332,7 @@ static short gp_stroke_addpoint(tGPsdata *p, const int mval[2], float pressure, { bGPdata *gpd = p->gpd; tGPspoint *pt; - + /* check painting mode */ if (p->paintmode == GP_PAINTMODE_DRAW_STRAIGHT) { /* straight lines only - i.e. only store start and end point in buffer */ @@ -337,29 +342,25 @@ static short gp_stroke_addpoint(tGPsdata *p, const int mval[2], float pressure, /* store settings */ copy_v2_v2_int(&pt->x, mval); - pt->pressure = pressure; + pt->pressure = 1.0f; /* T44932 - Pressure vals are unreliable, so ignore for now */ pt->time = (float)(curtime - p->inittime); /* increment buffer size */ gpd->sbuffer_size++; } else { - /* normally, we just reset the endpoint to the latest value + /* just reset the endpoint to the latest value * - assume that pointers for this are always valid... */ pt = ((tGPspoint *)(gpd->sbuffer) + 1); /* store settings */ copy_v2_v2_int(&pt->x, mval); - pt->pressure = pressure; + pt->pressure = 1.0f; /* T44932 - Pressure vals are unreliable, so ignore for now */ pt->time = (float)(curtime - p->inittime); - /* if this is just the second point we've added, increment the buffer size - * so that it will be drawn properly... - * otherwise, just leave it alone, otherwise we get problems - */ - if (gpd->sbuffer_size != 2) - gpd->sbuffer_size = 2; + /* now the buffer has 2 points (and shouldn't be allowed to get any larger) */ + gpd->sbuffer_size = 2; } /* can keep carrying on this way :) */ @@ -393,7 +394,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 = pressure; + pt->pressure = 1.0f; /* T44932 - Pressure vals are unreliable, so ignore for now */ pt->time = (float)(curtime - p->inittime); /* if there's stroke for this poly line session add (or replace last) point @@ -438,7 +439,7 @@ static short gp_stroke_addpoint(tGPsdata *p, const int mval[2], float pressure, return GP_STROKEADD_NORMAL; } - + /* return invalid state for now... */ return GP_STROKEADD_INVALID; } @@ -458,12 +459,12 @@ static void gp_stroke_smooth(tGPsdata *p) if ((cmx <= 2) || (gpd->sbuffer == NULL)) return; - /* Calculate smoothing coordinates using weighted-averages + /* Calculate smoothing coordinates using weighted-averages * WARNING: we do NOT smooth first and last points (to avoid shrinkage) */ spt = (tGPspoint *)gpd->sbuffer; - /* This (tmp_spt) small array stores the last two points' original coordinates, + /* This (tmp_spt) small array stores the last two points' original coordinates, * as we don't want to use already averaged ones! It is used as a cyclic buffer... */ tmp_spt[0] = *spt; @@ -482,7 +483,7 @@ static void gp_stroke_smooth(tGPsdata *p) } } -/* simplify a stroke (in buffer) before storing it +/* simplify a stroke (in buffer) before storing it * - applies a reverse Chaikin filter * - code adapted from etch-a-ton branch (editarmature_sketch.c) */ @@ -502,7 +503,7 @@ static void gp_stroke_simplify(tGPsdata *p) if ((num_points <= 4) || (old_points == NULL)) return; - /* clear buffer (but don't free mem yet) so that we can write to it + /* clear buffer (but don't free mem yet) so that we can write to it * - firstly set sbuffer to NULL, so a new one is allocated * - secondly, reset flag after, as it gets cleared auto */ @@ -569,7 +570,7 @@ static void gp_stroke_newfrombuffer(tGPsdata *p) /* since strokes are so fine, when using their depth we need a margin otherwise they might get missed */ int depth_margin = (p->gpd->flag & GP_DATA_DEPTH_STROKE) ? 4 : 0; - /* get total number of points to allocate space for + /* get total number of points to allocate space for * - drawing straight-lines only requires the endpoints */ if (p->paintmode == GP_PAINTMODE_DRAW_STRAIGHT) @@ -625,7 +626,7 @@ static void gp_stroke_newfrombuffer(tGPsdata *p) pt++; } - + if (totelem == 2) { /* last point if applicable */ ptc = ((tGPspoint *)gpd->sbuffer) + (gpd->sbuffer_size - 1); @@ -746,7 +747,7 @@ static short gp_stroke_eraser_splitdel(bGPDframe *gpf, bGPDstroke *gps, int i) /* free stroke points, then stroke */ MEM_freeN(pt_tmp); BLI_freelinkN(&gpf->strokes, gps); - + /* nothing left in stroke, so stop */ return 1; } @@ -755,7 +756,7 @@ static short gp_stroke_eraser_splitdel(bGPDframe *gpf, bGPDstroke *gps, int i) else if (i == gps->totpoints - 2) { /* allocate new points array, and assign most of the old stroke there */ gps->totpoints--; - gps->points = MEM_callocN(sizeof(bGPDspoint) * gps->totpoints, "gp_stroke_points"); + gps->points = MEM_mallocN(sizeof(bGPDspoint) * gps->totpoints, "gp_stroke_points"); memcpy(gps->points, pt_tmp, sizeof(bGPDspoint) * gps->totpoints); /* free temp buffer */ @@ -769,7 +770,7 @@ static short gp_stroke_eraser_splitdel(bGPDframe *gpf, bGPDstroke *gps, int i) else if (i == 0) { /* allocate new points array, and assign most of the old stroke there */ gps->totpoints--; - gps->points = MEM_callocN(sizeof(bGPDspoint) * gps->totpoints, "gp_stroke_points"); + gps->points = MEM_mallocN(sizeof(bGPDspoint) * gps->totpoints, "gp_stroke_points"); memcpy(gps->points, pt_tmp + 1, sizeof(bGPDspoint) * gps->totpoints); /* We must adjust timings! @@ -806,7 +807,7 @@ static short gp_stroke_eraser_splitdel(bGPDframe *gpf, bGPDstroke *gps, int i) BLI_insertlinkafter(&gpf->strokes, gps, gsn); gsn->totpoints = gps->totpoints - i; - gsn->points = MEM_callocN(sizeof(bGPDspoint) * gsn->totpoints, "gp_stroke_points"); + gsn->points = MEM_mallocN(sizeof(bGPDspoint) * gsn->totpoints, "gp_stroke_points"); memcpy(gsn->points, pt_tmp + i, sizeof(bGPDspoint) * gsn->totpoints); /* We must adjust timings of this new stroke! @@ -830,8 +831,8 @@ static short gp_stroke_eraser_splitdel(bGPDframe *gpf, bGPDstroke *gps, int i) /* adjust existing stroke */ gps->totpoints = i; - gps->points = MEM_callocN(sizeof(bGPDspoint) * gps->totpoints, "gp_stroke_points"); - memcpy(gps->points, pt_tmp, sizeof(bGPDspoint) * i); + gps->points = MEM_mallocN(sizeof(bGPDspoint) * gps->totpoints, "gp_stroke_points"); + memcpy(gps->points, pt_tmp, sizeof(bGPDspoint) * gps->totpoints); /* free temp buffer */ MEM_freeN(pt_tmp); @@ -852,8 +853,7 @@ static float view3d_point_depth(const RegionView3D *rv3d, const float co[3]) } } -static bool gp_stroke_eraser_is_occluded(tGPsdata *p, - const bGPDspoint *pt, const int x, const int y) +static bool gp_stroke_eraser_is_occluded(tGPsdata *p, const bGPDspoint *pt, const int x, const int y) { if ((p->sa->spacetype == SPACE_VIEW3D) && (p->flags & GP_PAINTFLAG_V3D_ERASER_DEPTH)) @@ -861,11 +861,11 @@ static bool gp_stroke_eraser_is_occluded(tGPsdata *p, RegionView3D *rv3d = p->ar->regiondata; const int mval[2] = {x, y}; float mval_3d[3]; - + 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); - + if (depth_pt > depth_mval) { return true; } @@ -874,53 +874,6 @@ static bool gp_stroke_eraser_is_occluded(tGPsdata *p, return false; } -/* eraser tool - check if part of stroke occurs within last segment drawn by eraser */ -static short gp_stroke_eraser_strokeinside(const int mval[2], const int UNUSED(mvalo[2]), - int rad, int x0, int y0, int x1, int y1) -{ - /* simple within-radius check for now */ - const float mval_fl[2] = {mval[0], mval[1]}; - const float screen_co_a[2] = {x0, y0}; - const float screen_co_b[2] = {x1, y1}; - - if (edge_inside_circle(mval_fl, rad, screen_co_a, screen_co_b)) { - return true; - } - - /* not inside */ - return false; -} - -static void gp_point_to_xy(ARegion *ar, View2D *v2d, rctf *subrect, bGPDstroke *gps, bGPDspoint *pt, - int *r_x, int *r_y) -{ - int xyval[2]; - - if (gps->flag & GP_STROKE_3DSPACE) { - if (ED_view3d_project_int_global(ar, &pt->x, xyval, V3D_PROJ_TEST_NOP) == V3D_PROJ_RET_OK) { - *r_x = xyval[0]; - *r_y = xyval[1]; - } - else { - *r_x = V2D_IS_CLIPPED; - *r_y = V2D_IS_CLIPPED; - } - } - else if (gps->flag & GP_STROKE_2DSPACE) { - UI_view2d_view_to_region_clip(v2d, pt->x, pt->y, r_x, r_y); - } - else { - if (subrect == NULL) { /* normal 3D view */ - *r_x = (int)(pt->x / 100 * ar->winx); - *r_y = (int)(pt->y / 100 * ar->winy); - } - else { /* camera view, use subrect */ - *r_x = (int)((pt->x / 100) * BLI_rctf_size_x(subrect)) + subrect->xmin; - *r_y = (int)((pt->y / 100) * BLI_rctf_size_y(subrect)) + subrect->ymin; - } - } -} - /* eraser tool - evaluation per stroke */ /* TODO: this could really do with some optimization (KD-Tree/BVH?) */ @@ -934,12 +887,12 @@ static void gp_stroke_eraser_dostroke(tGPsdata *p, if (gps->totpoints == 0) { /* just free stroke */ - if (gps->points) + if (gps->points) MEM_freeN(gps->points); BLI_freelinkN(&gpf->strokes, gps); } else if (gps->totpoints == 1) { - gp_point_to_xy(p->ar, p->v2d, p->subrect, gps, gps->points, &x0, &y0); + gp_point_to_xy(&p->gsc, gps, gps->points, &x0, &y0); /* do boundbox check first */ if ((!ELEM(V2D_IS_CLIPPED, x0, y0)) && BLI_rcti_isect_pt(rect, x0, y0)) { @@ -952,7 +905,7 @@ static void gp_stroke_eraser_dostroke(tGPsdata *p, } } else { - /* loop over the points in the stroke, checking for intersections + /* loop over the points in the stroke, checking for intersections * - an intersection will require the stroke to be split */ for (i = 0; (i + 1) < gps->totpoints; i++) { @@ -960,8 +913,8 @@ static void gp_stroke_eraser_dostroke(tGPsdata *p, pt1 = gps->points + i; pt2 = gps->points + i + 1; - gp_point_to_xy(p->ar, p->v2d, p->subrect, gps, pt1, &x0, &y0); - gp_point_to_xy(p->ar, p->v2d, p->subrect, gps, pt2, &x1, &y1); + gp_point_to_xy(&p->gsc, gps, pt1, &x0, &y0); + gp_point_to_xy(&p->gsc, gps, pt2, &x1, &y1); /* check that point segment of the boundbox of the eraser stroke */ if (((!ELEM(V2D_IS_CLIPPED, x0, y0)) && BLI_rcti_isect_pt(rect, x0, y0)) || @@ -971,7 +924,7 @@ static void gp_stroke_eraser_dostroke(tGPsdata *p, * eraser region (either within stroke painted, or on its lines) * - this assumes that linewidth is irrelevant */ - if (gp_stroke_eraser_strokeinside(mval, mvalo, rad, x0, y0, x1, y1)) { + if (gp_stroke_inside_circle(mval, mvalo, rad, x0, y0, x1, y1)) { if ((gp_stroke_eraser_is_occluded(p, pt1, x0, y0) == false) || (gp_stroke_eraser_is_occluded(p, pt2, x1, y1) == false)) { @@ -997,16 +950,16 @@ static void gp_stroke_doeraser(tGPsdata *p) rect.ymin = p->mval[1] - p->radius; rect.xmax = p->mval[0] + p->radius; rect.ymax = p->mval[1] + p->radius; - + if (p->sa->spacetype == SPACE_VIEW3D) { if (p->flags & GP_PAINTFLAG_V3D_ERASER_DEPTH) { View3D *v3d = p->sa->spacedata.first; - + view3d_region_operator_needs_opengl(p->win, p->ar); ED_view3d_autodist_init(p->scene, p->ar, v3d, 0); } } - + /* loop over strokes, checking segments for intersections */ for (gps = gpf->strokes.first; gps; gps = gpn) { gpn = gps->next; @@ -1037,13 +990,13 @@ static void gp_session_validatebuffer(tGPsdata *p) /* reset flags */ gpd->sbuffer_sflag = 0; - + /* reset inittime */ p->inittime = 0.0; } /* (re)init new painting data */ -static int gp_session_initdata(bContext *C, tGPsdata *p) +static bool gp_session_initdata(bContext *C, tGPsdata *p) { bGPdata **gpd_ptr = NULL; ScrArea *curarea = CTX_wm_area(C); @@ -1060,8 +1013,9 @@ static int gp_session_initdata(bContext *C, tGPsdata *p) /* pass on current scene and window */ p->scene = CTX_data_scene(C); p->win = CTX_wm_window(C); - + unit_m4(p->imat); + unit_m4(p->mat); switch (curarea->spacetype) { /* supported views first */ @@ -1070,9 +1024,10 @@ static int gp_session_initdata(bContext *C, tGPsdata *p) /* View3D *v3d = curarea->spacedata.first; */ /* RegionView3D *rv3d = ar->regiondata; */ - /* set current area + /* set current area * - must verify that region data is 3D-view (and not something else) */ + /* CAUTION: If this is the "toolbar", then this will change on the first stroke */ p->sa = curarea; p->ar = ar; @@ -1125,7 +1080,13 @@ static int gp_session_initdata(bContext *C, tGPsdata *p) case SPACE_CLIP: { SpaceClip *sc = curarea->spacedata.first; + MovieClip *clip = ED_space_clip_get_clip(sc); + if (clip == NULL) { + p->status = GP_STATUS_ERROR; + return false; + } + /* set the current area */ p->sa = curarea; p->ar = ar; @@ -1140,14 +1101,22 @@ static int gp_session_initdata(bContext *C, tGPsdata *p) p->custom_color[3] = 0.9f; if (sc->gpencil_src == SC_GPENCIL_SRC_TRACK) { - MovieClip *clip = ED_space_clip_get_clip(sc); int framenr = ED_space_clip_get_clip_frame_number(sc); MovieTrackingTrack *track = BKE_tracking_track_get_active(&clip->tracking); - MovieTrackingMarker *marker = BKE_tracking_marker_get(track, framenr); - - p->imat[3][0] -= marker->pos[0]; - p->imat[3][1] -= marker->pos[1]; + MovieTrackingMarker *marker = track ? BKE_tracking_marker_get(track, framenr) : NULL; + + if (marker) { + p->imat[3][0] -= marker->pos[0]; + p->imat[3][1] -= marker->pos[1]; + } + else { + p->status = GP_STATUS_ERROR; + return false; + } } + + invert_m4_m4(p->mat, p->imat); + copy_m4_m4(p->gsc.mat, p->mat); break; } /* unsupported views */ @@ -1192,10 +1161,10 @@ static int gp_session_initdata(bContext *C, tGPsdata *p) static tGPsdata *gp_session_initpaint(bContext *C) { tGPsdata *p = NULL; - + /* create new context data */ p = MEM_callocN(sizeof(tGPsdata), "GPencil Drawing Data"); - + gp_session_initdata(C, p); /* return context data for running paint operator */ @@ -1226,7 +1195,7 @@ static void gp_session_cleanup(tGPsdata *p) /* init new stroke */ static void gp_paint_initstroke(tGPsdata *p, short paintmode) -{ +{ /* get active layer (or add a new one if non-existent) */ p->gpl = gpencil_layer_getactive(p->gpd); if (p->gpl == NULL) { @@ -1241,7 +1210,7 @@ static void gp_paint_initstroke(tGPsdata *p, short paintmode) printf("Error: Cannot paint on locked layer\n"); return; } - + /* get active frame (add a new one if not matching frame) */ p->gpf = gpencil_layer_getframe(p->gpl, p->scene->r.cfra, 1); if (p->gpf == NULL) { @@ -1257,7 +1226,7 @@ static void gp_paint_initstroke(tGPsdata *p, short paintmode) p->paintmode = paintmode; if (p->paintmode == GP_PAINTMODE_ERASER) { p->gpd->sbuffer_sflag |= GP_STROKE_ERASER; - + /* check if we should respect depth while erasing */ if (p->sa->spacetype == SPACE_VIEW3D) { if (p->gpl->flag & GP_LAYER_NO_XRAY) { @@ -1265,12 +1234,23 @@ static void gp_paint_initstroke(tGPsdata *p, short paintmode) } } } - + else { + /* disable eraser flags - so that we can switch modes during a session */ + p->gpd->sbuffer_sflag &= ~GP_STROKE_ERASER; + + if (p->sa->spacetype == SPACE_VIEW3D) { + if (p->gpl->flag & GP_LAYER_NO_XRAY) { + p->flags &= ~GP_PAINTFLAG_V3D_ERASER_DEPTH; + } + } + } + /* set 'initial run' flag, which is only used to denote when a new stroke is starting */ p->flags |= GP_PAINTFLAG_FIRSTRUN; - + /* when drawing in the camera view, in 2D space, set the subrect */ + p->subrect = NULL; if (!(p->gpd->flag & GP_DATA_VIEWALIGN)) { if (p->sa->spacetype == SPACE_VIEW3D) { View3D *v3d = p->sa->spacedata.first; @@ -1283,7 +1263,21 @@ static void gp_paint_initstroke(tGPsdata *p, short paintmode) } } } - + + /* init stroke point space-conversion settings... */ + p->gsc.gpd = p->gpd; + p->gsc.gpl = p->gpl; + + p->gsc.sa = p->sa; + p->gsc.ar = p->ar; + p->gsc.v2d = p->v2d; + + p->gsc.subrect_data = p->subrect_data; + p->gsc.subrect = p->subrect; + + copy_m4_m4(p->gsc.mat, p->mat); + + /* check if points will need to be made in view-aligned space */ if (p->gpd->flag & GP_DATA_VIEWALIGN) { switch (p->sa->spacetype) { @@ -1333,7 +1327,7 @@ static void gp_paint_initstroke(tGPsdata *p, short paintmode) /* finish off a stroke (clears buffer, but doesn't finish the paint operation) */ static void gp_paint_strokeend(tGPsdata *p) { - /* for surface sketching, need to set the right OpenGL context stuff so that + /* for surface sketching, need to set the right OpenGL context stuff so that * the conversions will project the values correctly... */ if (gpencil_project_check(p)) { @@ -1388,13 +1382,18 @@ static void gpencil_draw_eraser(bContext *UNUSED(C), int x, int y, void *p_ptr) glTranslatef((float)x, (float)y, 0.0f); - glColor4ub(255, 255, 255, 128); - glEnable(GL_LINE_SMOOTH); glEnable(GL_BLEND); + glColor4ub(255, 100, 100, 20); + glutil_draw_filled_arc(0.0, M_PI * 2.0, p->radius, 40); + + setlinestyle(6); + + glColor4ub(255, 100, 100, 200); glutil_draw_lined_arc(0.0, M_PI * 2.0, p->radius, 40); + setlinestyle(0); glDisable(GL_BLEND); glDisable(GL_LINE_SMOOTH); @@ -1410,9 +1409,9 @@ static void gpencil_draw_toggle_eraser_cursor(bContext *C, tGPsdata *p, short en WM_paint_cursor_end(CTX_wm_manager(C), p->erasercursor); p->erasercursor = NULL; } - else if (enable) { + else if (enable && !p->erasercursor) { /* enable cursor */ - p->erasercursor = WM_paint_cursor_activate(CTX_wm_manager(C), + p->erasercursor = WM_paint_cursor_activate(CTX_wm_manager(C), NULL, /* XXX */ gpencil_draw_eraser, p); } @@ -1437,12 +1436,14 @@ static void gpencil_draw_exit(bContext *C, wmOperator *op) if (p->paintmode == GP_PAINTMODE_ERASER) { /* turn off radial brush cursor */ gpencil_draw_toggle_eraser_cursor(C, p, false); - - /* if successful, store the new eraser size to be used again next time */ - if (p->status == GP_STATUS_DONE) - U.gp_eraser = p->radius; } + /* 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. + */ + U.gp_eraser = p->radius; + /* cleanup */ gp_paint_cleanup(p); gp_session_cleanup(p); @@ -1493,6 +1494,15 @@ static int gpencil_draw_init(bContext *C, wmOperator *op) /* ------------------------------- */ +/* ensure that the correct cursor icon is set */ +static void gpencil_draw_cursor_set(tGPsdata *p) +{ + if (p->paintmode == GP_PAINTMODE_ERASER) + WM_cursor_modal_set(p->win, BC_CROSSCURSOR); /* XXX need a better cursor */ + else + WM_cursor_modal_set(p->win, BC_PAINTBRUSHCURSOR); +} + /* update UI indicators of status, including cursor and header prints */ static void gpencil_draw_status_indicators(tGPsdata *p) { @@ -1519,13 +1529,13 @@ static void gpencil_draw_status_indicators(tGPsdata *p) ED_area_headerprint(p->sa, IFACE_("Grease Pencil Freehand Session: Hold and drag LMB to draw | " "ESC/Enter to end")); break; - + default: /* unhandled future cases */ ED_area_headerprint(p->sa, IFACE_("Grease Pencil Session: ESC/Enter to end")); break; } break; - + case GP_STATUS_ERROR: case GP_STATUS_DONE: /* clear status string */ @@ -1608,7 +1618,7 @@ static void gpencil_draw_apply_event(wmOperator *op, const wmEvent *event) /* handle pressure sensitivity (which is supplied by tablets) */ if (event->tablet_data) { - wmTabletData *wmtab = event->tablet_data; + const wmTabletData *wmtab = event->tablet_data; tablet = (wmtab->Active != EVT_TABLET_NONE); p->pressure = wmtab->Pressure; @@ -1619,15 +1629,6 @@ static void gpencil_draw_apply_event(wmOperator *op, const wmEvent *event) else p->pressure = 1.0f; - /* fill in stroke data (not actually used directly by gpencil_draw_apply) */ - RNA_collection_add(op->ptr, "stroke", &itemptr); - - mousef[0] = p->mval[0]; - mousef[1] = p->mval[1]; - RNA_float_set_array(&itemptr, "mouse", mousef); - RNA_float_set(&itemptr, "pressure", p->pressure); - RNA_boolean_set(&itemptr, "is_start", (p->flags & GP_PAINTFLAG_FIRSTRUN)); - /* special exception for start of strokes (i.e. maybe for just a dot) */ if (p->flags & GP_PAINTFLAG_FIRSTRUN) { p->flags &= ~GP_PAINTFLAG_FIRSTRUN; @@ -1644,6 +1645,15 @@ static void gpencil_draw_apply_event(wmOperator *op, const wmEvent *event) return; } + /* fill in stroke data (not actually used directly by gpencil_draw_apply) */ + RNA_collection_add(op->ptr, "stroke", &itemptr); + + mousef[0] = p->mval[0]; + mousef[1] = p->mval[1]; + RNA_float_set_array(&itemptr, "mouse", mousef); + RNA_float_set(&itemptr, "pressure", p->pressure); + RNA_boolean_set(&itemptr, "is_start", (p->flags & GP_PAINTFLAG_FIRSTRUN) != 0); + RNA_float_set(&itemptr, "time", p->curtime - p->inittime); /* apply the current latest drawing point */ @@ -1714,7 +1724,7 @@ static int gpencil_draw_exec(bContext *C, wmOperator *op) gpencil_draw_apply(op, p); } RNA_END; - + /* printf("\tGP - done\n"); */ /* cleanup */ @@ -1733,14 +1743,13 @@ static int gpencil_draw_exec(bContext *C, wmOperator *op) static int gpencil_draw_invoke(bContext *C, wmOperator *op, const wmEvent *event) { tGPsdata *p = NULL; - wmWindow *win = CTX_wm_window(C); if (G.debug & G_DEBUG) printf("GPencil - Starting Drawing\n"); /* try to initialize context data needed while drawing */ if (!gpencil_draw_init(C, op)) { - if (op->customdata) + if (op->customdata) MEM_freeN(op->customdata); if (G.debug & G_DEBUG) printf("\tGP - no valid data\n"); @@ -1748,28 +1757,25 @@ static int gpencil_draw_invoke(bContext *C, wmOperator *op, const wmEvent *event } else p = op->customdata; - + /* TODO: set any additional settings that we can take from the events? * TODO? if tablet is erasing, force eraser to be on? */ - + /* TODO: move cursor setting stuff to stroke-start so that paintmode can be changed midway... */ - + /* if eraser is on, draw radial aid */ if (p->paintmode == GP_PAINTMODE_ERASER) { gpencil_draw_toggle_eraser_cursor(C, p, true); } - /* set cursor */ - if (p->paintmode == GP_PAINTMODE_ERASER) - WM_cursor_modal_set(win, BC_CROSSCURSOR); /* XXX need a better cursor */ - else - WM_cursor_modal_set(win, BC_PAINTBRUSHCURSOR); - - /* special hack: if there was an initial event, then we were invoked via a hotkey, and - * painting should start immediately. Otherwise, this was called from a toolbar, in which - * case we should wait for the mouse to be clicked. + /* set cursor + * NOTE: This may change later (i.e. intentionally via brush toggle, + * or unintentionally if the user scrolls outside the area)... */ - if (event->val == KM_PRESS) { + 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; @@ -1780,6 +1786,7 @@ static int gpencil_draw_invoke(bContext *C, wmOperator *op, const wmEvent *event else { /* toolbar invoked - don't start drawing yet... */ /* printf("\tGP - hotkey invoked... waiting for click-drag\n"); */ + op->flag |= OP_IS_MODAL_CURSOR_REGION; } WM_event_add_notifier(C, NC_GPENCIL | NA_EDITED, NULL); @@ -1817,8 +1824,10 @@ static tGPsdata *gpencil_stroke_begin(bContext *C, wmOperator *op) if (gp_session_initdata(C, p)) gp_paint_initstroke(p, p->paintmode); - if (p->status != GP_STATUS_ERROR) + if (p->status != GP_STATUS_ERROR) { p->status = GP_STATUS_PAINTING; + op->flag &= ~OP_IS_MODAL_CURSOR_REGION; + } return op->customdata; } @@ -1826,15 +1835,16 @@ static tGPsdata *gpencil_stroke_begin(bContext *C, wmOperator *op) static void gpencil_stroke_end(wmOperator *op) { tGPsdata *p = op->customdata; - + gp_paint_cleanup(p); - + gpencil_undo_push(p->gpd); - + gp_session_cleanup(p); - + p->status = GP_STATUS_IDLING; - + op->flag |= OP_IS_MODAL_CURSOR_REGION; + p->gpd = NULL; p->gpl = NULL; p->gpf = NULL; @@ -1858,11 +1868,19 @@ static int gpencil_draw_modal(bContext *C, wmOperator *op, const wmEvent *event) * better in tools that immediately apply * in 3D space. */ + + if (p->status == GP_STATUS_IDLING) { + ARegion *ar = CTX_wm_region(C); + p->ar = ar; + } /* we don't pass on key events, GP is used with key-modifiers - prevents Dkey to insert drivers */ if (ISKEYBOARD(event->type)) { - if (ELEM(event->type, LEFTARROWKEY, DOWNARROWKEY, RIGHTARROWKEY, UPARROWKEY)) { - /* allow some keys - for frame changing: [#33412] */ + if (ELEM(event->type, LEFTARROWKEY, DOWNARROWKEY, RIGHTARROWKEY, UPARROWKEY, ZKEY)) { + /* allow some keys: + * - for frame changing [#33412] + * - for undo (during sketching sessions) + */ } else { estate = OPERATOR_RUNNING_MODAL; @@ -1871,7 +1889,7 @@ static int gpencil_draw_modal(bContext *C, wmOperator *op, const wmEvent *event) //printf("\tGP - handle modal event...\n"); - /* exit painting mode (and/or end current stroke) + /* 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)) { @@ -1881,17 +1899,18 @@ static int gpencil_draw_modal(bContext *C, wmOperator *op, const wmEvent *event) estate = OPERATOR_FINISHED; } - /* toggle painting mode upon mouse-button movement + /* toggle painting mode upon mouse-button movement * - LEFTMOUSE = standard drawing (all) / straight line drawing (all) / polyline (toolbox only) * - RIGHTMOUSE = polyline (hotkey) / eraser (all) * (Disabling RIGHTMOUSE case here results in bugs like [#32647]) + * also making sure we have a valid event value, to not exit too early */ - if (ELEM(event->type, LEFTMOUSE, RIGHTMOUSE)) { + if (ELEM(event->type, LEFTMOUSE, RIGHTMOUSE) && (event->val != KM_NOTHING)) { /* if painting, end stroke */ if (p->status == GP_STATUS_PAINTING) { int sketch = 0; - /* basically, this should be mouse-button up = end stroke + /* basically, this should be mouse-button up = end stroke * BUT what happens next depends on whether we 'painting sessions' is enabled */ sketch |= GPENCIL_SKETCH_SESSIONS_ON(p->scene); @@ -1903,6 +1922,27 @@ static int gpencil_draw_modal(bContext *C, wmOperator *op, const wmEvent *event) /* printf("\t\tGP - end stroke only\n"); */ gpencil_stroke_end(op); + /* If eraser mode is on, turn it off after the stroke finishes + * NOTE: This just makes it nicer to work with drawing sessions + */ + if (p->paintmode == GP_PAINTMODE_ERASER) { + p->paintmode = RNA_enum_get(op->ptr, "mode"); + + /* if the original mode was *still* eraser, + * we'll let it say for now, since this gives + * users an opportunity to have visual feedback + * when adjusting eraser size + */ + if (p->paintmode != GP_PAINTMODE_ERASER) { + /* turn off cursor... + * NOTE: this should be enough for now + * Just hiding this makes it seem like + * you can paint again... + */ + gpencil_draw_toggle_eraser_cursor(C, p, false); + } + } + /* we've just entered idling state, so this event was processed (but no others yet) */ estate = OPERATOR_RUNNING_MODAL; @@ -1916,15 +1956,88 @@ static int gpencil_draw_modal(bContext *C, wmOperator *op, const wmEvent *event) } } else if (event->val == KM_PRESS) { - /* not painting, so start stroke (this should be mouse-button down) */ - p = gpencil_stroke_begin(C, op); + bool in_bounds = false; - if (p->status == GP_STATUS_ERROR) { + /* Check if we're outside the bounds of the active region + * NOTE: An exception here is that if launched from the toolbar, + * whatever region we're now in should become the new region + */ + if ((p->ar) && (p->ar->regiontype == RGN_TYPE_TOOLS)) { + /* Change to whatever region is now under the mouse */ + ARegion *current_region = BKE_area_find_region_xy(p->sa, RGN_TYPE_ANY, event->x, event->y); + + if (G.debug & G_DEBUG) { + printf("found alternative region %p (old was %p) - at %d %d (sa: %d %d -> %d %d)\n", + current_region, p->ar, event->x, event->y, + p->sa->totrct.xmin, p->sa->totrct.ymin, p->sa->totrct.xmax, p->sa->totrct.ymax); + } + + if (current_region) { + /* Assume that since we found the cursor in here, it is in bounds + * and that this should be the region that we begin drawing in + */ + p->ar = current_region; + in_bounds = true; + } + else { + /* Out of bounds, or invalid in some other way */ + p->status = GP_STATUS_ERROR; + estate = OPERATOR_CANCELLED; + + if (G.debug & G_DEBUG) + printf("%s: Region under cursor is out of bounds, so cannot be drawn on\n", __func__); + } + } + else if (p->ar) { + rcti region_rect; + + /* Perform bounds check using */ + ED_region_visible_rect(p->ar, ®ion_rect); + in_bounds = BLI_rcti_isect_pt_v(®ion_rect, event->mval); + } + else { + /* No region */ + p->status = GP_STATUS_ERROR; estate = OPERATOR_CANCELLED; + + if (G.debug & G_DEBUG) + printf("%s: No active region found in GP Paint session data\n", __func__); + } + + if (in_bounds) { + /* Switch paintmode (temporarily if need be) based on which button was used + * NOTE: This is to make it more convenient to erase strokes when using drawing sessions + */ + if (event->type == LEFTMOUSE) { + /* restore drawmode to default */ + p->paintmode = RNA_enum_get(op->ptr, "mode"); + } + else if (event->type == RIGHTMOUSE) { + /* turn on eraser */ + p->paintmode = GP_PAINTMODE_ERASER; + } + + gpencil_draw_toggle_eraser_cursor(C, p, p->paintmode == GP_PAINTMODE_ERASER); + + /* not painting, so start stroke (this should be mouse-button down) */ + p = gpencil_stroke_begin(C, op); + + if (p->status == GP_STATUS_ERROR) { + estate = OPERATOR_CANCELLED; + } + } + else if (p->status != GP_STATUS_ERROR) { + /* User clicked outside bounds of window while idling, so exit paintmode + * NOTE: Don't eter this case if an error occurred while finding the + * region (as above) + */ + p->status = GP_STATUS_DONE; + estate = OPERATOR_FINISHED; } } else { p->status = GP_STATUS_IDLING; + op->flag |= OP_IS_MODAL_CURSOR_REGION; } } @@ -1960,12 +2073,12 @@ static int gpencil_draw_modal(bContext *C, wmOperator *op, const wmEvent *event) case PADPLUSKEY: p->radius += 5; break; - + case WHEELUPMOUSE: /* smaller */ case PADMINUS: p->radius -= 5; - if (p->radius < 0) + if (p->radius < 0) p->radius = 0; break; } @@ -1976,7 +2089,7 @@ static int gpencil_draw_modal(bContext *C, wmOperator *op, const wmEvent *event) /* event handled, so just tag as running modal */ estate = OPERATOR_RUNNING_MODAL; } - /* there shouldn't be any other events, but just in case there are, let's swallow them + /* there shouldn't be any other events, but just in case there are, let's swallow them * (i.e. to prevent problems with undo) */ else { @@ -1988,9 +2101,11 @@ static int gpencil_draw_modal(bContext *C, wmOperator *op, const wmEvent *event) /* gpencil modal operator stores area, which can be removed while using it (like fullscreen) */ if (0 == gpencil_area_exists(C, p->sa)) estate = OPERATOR_CANCELLED; - else + else { /* update status indicators - cursor, header, etc. */ gpencil_draw_status_indicators(p); + gpencil_draw_cursor_set(p); /* cursor may have changed outside our control - T44084 */ + } /* process last operations before exiting */ switch (estate) { @@ -1999,7 +2114,7 @@ static int gpencil_draw_modal(bContext *C, wmOperator *op, const wmEvent *event) gpencil_draw_exit(C, op); WM_event_add_notifier(C, NC_GPENCIL | NA_EDITED, NULL); break; - + case OPERATOR_CANCELLED: gpencil_draw_exit(C, op); break; @@ -2046,6 +2161,8 @@ void GPENCIL_OT_draw(wmOperatorType *ot) /* settings for drawing */ ot->prop = RNA_def_enum(ot->srna, "mode", prop_gpencil_drawmodes, 0, "Mode", "Way to interpret mouse movements"); - RNA_def_collection_runtime(ot->srna, "stroke", &RNA_OperatorStrokeElement, "Stroke", ""); + + /* NOTE: wait for input is enabled by default, so that all UI code can work properly without needing users to know about this */ + RNA_def_boolean(ot->srna, "wait_for_input", true, "Wait for Input", "Wait for first click instead of painting immediately"); } |