Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'source/blender/editors/gpencil/gpencil_paint.c')
-rw-r--r--source/blender/editors/gpencil/gpencil_paint.c451
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, &region_rect);
+ in_bounds = BLI_rcti_isect_pt_v(&region_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");
}