1 /*
2  * This program is free software; you can redistribute it and/or
3  * modify it under the terms of the GNU General Public License
4  * as published by the Free Software Foundation; either version 2
5  * of the License, or (at your option) any later version.
6  *
7  * This program is distributed in the hope that it will be useful,
8  * but WITHOUT ANY WARRANTY; without even the implied warranty of
9  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
10  * GNU General Public License for more details.
11  *
12  * You should have received a copy of the GNU General Public License
13  * along with this program; if not, write to the Free Software Foundation,
14  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
15  *
16  * The Original Code is Copyright (C) 2015, Blender Foundation
17  * This is a new part of Blender
18  * Brush based operators for editing Grease Pencil strokes
19  */
20 
21 /** \file
22  * \ingroup edgpencil
23  */
24 
25 #include "MEM_guardedalloc.h"
26 
27 #include "BLI_blenlib.h"
28 #include "BLI_math.h"
29 
30 #include "BLT_translation.h"
31 
32 #include "DNA_brush_types.h"
33 #include "DNA_gpencil_types.h"
34 
35 #include "BKE_brush.h"
36 #include "BKE_colortools.h"
37 #include "BKE_context.h"
38 #include "BKE_deform.h"
39 #include "BKE_gpencil.h"
40 #include "BKE_main.h"
41 #include "BKE_object_deform.h"
42 #include "BKE_report.h"
43 #include "DNA_meshdata_types.h"
44 
45 #include "WM_api.h"
46 #include "WM_types.h"
47 
48 #include "RNA_access.h"
49 #include "RNA_define.h"
50 
51 #include "UI_view2d.h"
52 
53 #include "ED_gpencil.h"
54 #include "ED_screen.h"
55 #include "ED_view3d.h"
56 
57 #include "DEG_depsgraph.h"
58 #include "DEG_depsgraph_query.h"
59 
60 #include "gpencil_intern.h"
61 
62 /* ************************************************ */
63 /* General Brush Editing Context */
64 #define GP_SELECT_BUFFER_CHUNK 256
65 
66 /* Grid of Colors for Smear. */
67 typedef struct tGP_Grid {
68   /** Lower right corner of rectangle of grid cell. */
69   float bottom[2];
70   /** Upper left corner of rectangle of grid cell. */
71   float top[2];
72   /** Average Color */
73   float color[4];
74   /** Total points included. */
75   int totcol;
76 
77 } tGP_Grid;
78 
79 /* List of points affected by brush. */
80 typedef struct tGP_Selected {
81   /** Referenced stroke. */
82   bGPDstroke *gps;
83   /** Point index in points array. */
84   int pt_index;
85   /** Position */
86   int pc[2];
87   /** Color */
88   float color[4];
89 } tGP_Selected;
90 
91 /* Context for brush operators */
92 typedef struct tGP_BrushWeightpaintData {
93   struct Main *bmain;
94   Scene *scene;
95   Object *object;
96 
97   ARegion *region;
98 
99   /* Current GPencil datablock */
100   bGPdata *gpd;
101 
102   Brush *brush;
103 
104   /* Space Conversion Data */
105   GP_SpaceConversion gsc;
106 
107   /* Is the brush currently painting? */
108   bool is_painting;
109 
110   /* Start of new paint */
111   bool first;
112 
113   /* Is multi-frame editing enabled, and are we using falloff for that? */
114   bool is_multiframe;
115   bool use_multiframe_falloff;
116 
117   /* active vertex group */
118   int vrgroup;
119 
120   /* Brush Runtime Data: */
121   /* - position and pressure
122    * - the *_prev variants are the previous values
123    */
124   float mval[2], mval_prev[2];
125   float pressure, pressure_prev;
126 
127   /* - Effect 2D vector */
128   float dvec[2];
129 
130   /* - multi-frame falloff factor. */
131   float mf_falloff;
132 
133   /* brush geometry (bounding box). */
134   rcti brush_rect;
135 
136   /* Temp data to save selected points */
137   /** Stroke buffer. */
138   tGP_Selected *pbuffer;
139   /** Number of elements currently used in cache. */
140   int pbuffer_used;
141   /** Number of total elements available in cache. */
142   int pbuffer_size;
143 } tGP_BrushWeightpaintData;
144 
145 /* Ensure the buffer to hold temp selected point size is enough to save all points selected. */
gpencil_select_buffer_ensure(tGP_Selected * buffer_array,int * buffer_size,int * buffer_used,const bool clear)146 static tGP_Selected *gpencil_select_buffer_ensure(tGP_Selected *buffer_array,
147                                                   int *buffer_size,
148                                                   int *buffer_used,
149                                                   const bool clear)
150 {
151   tGP_Selected *p = NULL;
152 
153   /* By default a buffer is created with one block with a predefined number of free slots,
154    * if the size is not enough, the cache is reallocated adding a new block of free slots.
155    * This is done in order to keep cache small and improve speed. */
156   if (*buffer_used + 1 > *buffer_size) {
157     if ((*buffer_size == 0) || (buffer_array == NULL)) {
158       p = MEM_callocN(sizeof(struct tGP_Selected) * GP_SELECT_BUFFER_CHUNK, __func__);
159       *buffer_size = GP_SELECT_BUFFER_CHUNK;
160     }
161     else {
162       *buffer_size += GP_SELECT_BUFFER_CHUNK;
163       p = MEM_recallocN(buffer_array, sizeof(struct tGP_Selected) * *buffer_size);
164     }
165 
166     if (p == NULL) {
167       *buffer_size = *buffer_used = 0;
168     }
169 
170     buffer_array = p;
171   }
172 
173   /* clear old data */
174   if (clear) {
175     *buffer_used = 0;
176     if (buffer_array != NULL) {
177       memset(buffer_array, 0, sizeof(tGP_Selected) * *buffer_size);
178     }
179   }
180 
181   return buffer_array;
182 }
183 
184 /* Brush Operations ------------------------------- */
185 
186 /* Compute strength of effect. */
brush_influence_calc(tGP_BrushWeightpaintData * gso,const int radius,const int co[2])187 static float brush_influence_calc(tGP_BrushWeightpaintData *gso, const int radius, const int co[2])
188 {
189   Brush *brush = gso->brush;
190 
191   /* basic strength factor from brush settings */
192   float influence = brush->alpha;
193 
194   /* use pressure? */
195   if (brush->gpencil_settings->flag & GP_BRUSH_USE_PRESSURE) {
196     influence *= gso->pressure;
197   }
198 
199   /* distance fading */
200   int mval_i[2];
201   round_v2i_v2fl(mval_i, gso->mval);
202   float distance = (float)len_v2v2_int(mval_i, co);
203   influence *= 1.0f - (distance / max_ff(radius, 1e-8));
204 
205   /* Apply Brush curve. */
206   float brush_falloff = BKE_brush_curve_strength(brush, distance, (float)radius);
207   influence *= brush_falloff;
208 
209   /* apply multi-frame falloff */
210   influence *= gso->mf_falloff;
211 
212   /* return influence */
213   return influence;
214 }
215 
216 /* Compute effect vector for directional brushes. */
brush_calc_dvec_2d(tGP_BrushWeightpaintData * gso)217 static void brush_calc_dvec_2d(tGP_BrushWeightpaintData *gso)
218 {
219   gso->dvec[0] = (float)(gso->mval[0] - gso->mval_prev[0]);
220   gso->dvec[1] = (float)(gso->mval[1] - gso->mval_prev[1]);
221 
222   normalize_v2(gso->dvec);
223 }
224 
225 /* ************************************************ */
226 /* Brush Callbacks
227  * This section defines the callbacks used by each brush to perform their magic.
228  * These are called on each point within the brush's radius. */
229 
230 /* Draw Brush */
brush_draw_apply(tGP_BrushWeightpaintData * gso,bGPDstroke * gps,int pt_index,const int radius,const int co[2])231 static bool brush_draw_apply(tGP_BrushWeightpaintData *gso,
232                              bGPDstroke *gps,
233                              int pt_index,
234                              const int radius,
235                              const int co[2])
236 {
237   /* create dvert */
238   BKE_gpencil_dvert_ensure(gps);
239 
240   MDeformVert *dvert = gps->dvert + pt_index;
241   float inf;
242 
243   /* Compute strength of effect */
244   inf = brush_influence_calc(gso, radius, co);
245 
246   /* need a vertex group */
247   if (gso->vrgroup == -1) {
248     if (gso->object) {
249       BKE_object_defgroup_add(gso->object);
250       DEG_relations_tag_update(gso->bmain);
251       gso->vrgroup = 0;
252     }
253   }
254   else {
255     bDeformGroup *defgroup = BLI_findlink(&gso->object->defbase, gso->vrgroup);
256     if (defgroup->flag & DG_LOCK_WEIGHT) {
257       return false;
258     }
259   }
260   /* Get current weight and blend. */
261   MDeformWeight *dw = BKE_defvert_ensure_index(dvert, gso->vrgroup);
262   if (dw) {
263     dw->weight = interpf(gso->brush->weight, dw->weight, inf);
264     CLAMP(dw->weight, 0.0f, 1.0f);
265   }
266   return true;
267 }
268 
269 /* ************************************************ */
270 /* Header Info */
gpencil_weightpaint_brush_header_set(bContext * C)271 static void gpencil_weightpaint_brush_header_set(bContext *C)
272 {
273   ED_workspace_status_text(C, TIP_("GPencil Weight Paint: LMB to paint | RMB/Escape to Exit"));
274 }
275 
276 /* ************************************************ */
277 /* Grease Pencil Weight Paint Operator */
278 
279 /* Init/Exit ----------------------------------------------- */
280 
gpencil_weightpaint_brush_init(bContext * C,wmOperator * op)281 static bool gpencil_weightpaint_brush_init(bContext *C, wmOperator *op)
282 {
283   Scene *scene = CTX_data_scene(C);
284   ToolSettings *ts = CTX_data_tool_settings(C);
285   Object *ob = CTX_data_active_object(C);
286   Paint *paint = &ts->gp_weightpaint->paint;
287 
288   /* set the brush using the tool */
289   tGP_BrushWeightpaintData *gso;
290 
291   /* setup operator data */
292   gso = MEM_callocN(sizeof(tGP_BrushWeightpaintData), "tGP_BrushWeightpaintData");
293   op->customdata = gso;
294 
295   gso->bmain = CTX_data_main(C);
296 
297   gso->brush = paint->brush;
298   BKE_curvemapping_init(gso->brush->curve);
299 
300   gso->is_painting = false;
301   gso->first = true;
302 
303   gso->pbuffer = NULL;
304   gso->pbuffer_size = 0;
305   gso->pbuffer_used = 0;
306 
307   gso->gpd = ED_gpencil_data_get_active(C);
308   gso->scene = scene;
309   gso->object = ob;
310   if (ob) {
311     gso->vrgroup = ob->actdef - 1;
312     if (!BLI_findlink(&ob->defbase, gso->vrgroup)) {
313       gso->vrgroup = -1;
314     }
315   }
316   else {
317     gso->vrgroup = -1;
318   }
319 
320   gso->region = CTX_wm_region(C);
321 
322   /* Multiframe settings. */
323   gso->is_multiframe = (bool)GPENCIL_MULTIEDIT_SESSIONS_ON(gso->gpd);
324   gso->use_multiframe_falloff = (ts->gp_sculpt.flag & GP_SCULPT_SETT_FLAG_FRAME_FALLOFF) != 0;
325 
326   /* Init multi-edit falloff curve data before doing anything,
327    * so we won't have to do it again later. */
328   if (gso->is_multiframe) {
329     BKE_curvemapping_init(ts->gp_sculpt.cur_falloff);
330   }
331 
332   /* Setup space conversions. */
333   gpencil_point_conversion_init(C, &gso->gsc);
334 
335   /* Update header. */
336   gpencil_weightpaint_brush_header_set(C);
337 
338   return true;
339 }
340 
gpencil_weightpaint_brush_exit(bContext * C,wmOperator * op)341 static void gpencil_weightpaint_brush_exit(bContext *C, wmOperator *op)
342 {
343   tGP_BrushWeightpaintData *gso = op->customdata;
344 
345   /* Disable headerprints. */
346   ED_workspace_status_text(C, NULL);
347 
348   /* Free operator data */
349   MEM_SAFE_FREE(gso->pbuffer);
350   MEM_SAFE_FREE(gso);
351   op->customdata = NULL;
352 }
353 
354 /* Poll callback for stroke weight paint operator. */
gpencil_weightpaint_brush_poll(bContext * C)355 static bool gpencil_weightpaint_brush_poll(bContext *C)
356 {
357   /* NOTE: this is a bit slower, but is the most accurate... */
358   return CTX_DATA_COUNT(C, editable_gpencil_strokes) != 0;
359 }
360 
361 /* Helper to save the points selected by the brush. */
gpencil_save_selected_point(tGP_BrushWeightpaintData * gso,bGPDstroke * gps,int index,int pc[2])362 static void gpencil_save_selected_point(tGP_BrushWeightpaintData *gso,
363                                         bGPDstroke *gps,
364                                         int index,
365                                         int pc[2])
366 {
367   tGP_Selected *selected;
368   bGPDspoint *pt = &gps->points[index];
369 
370   /* Ensure the array to save the list of selected points is big enough. */
371   gso->pbuffer = gpencil_select_buffer_ensure(
372       gso->pbuffer, &gso->pbuffer_size, &gso->pbuffer_used, false);
373 
374   selected = &gso->pbuffer[gso->pbuffer_used];
375   selected->gps = gps;
376   selected->pt_index = index;
377   copy_v2_v2_int(selected->pc, pc);
378   copy_v4_v4(selected->color, pt->vert_color);
379 
380   gso->pbuffer_used++;
381 }
382 
383 /* Select points in this stroke and add to an array to be used later. */
gpencil_weightpaint_select_stroke(tGP_BrushWeightpaintData * gso,bGPDstroke * gps,const float diff_mat[4][4])384 static void gpencil_weightpaint_select_stroke(tGP_BrushWeightpaintData *gso,
385                                               bGPDstroke *gps,
386                                               const float diff_mat[4][4])
387 {
388   GP_SpaceConversion *gsc = &gso->gsc;
389   rcti *rect = &gso->brush_rect;
390   Brush *brush = gso->brush;
391   const int radius = (brush->flag & GP_BRUSH_USE_PRESSURE) ? gso->brush->size * gso->pressure :
392                                                              gso->brush->size;
393   bGPDstroke *gps_active = (gps->runtime.gps_orig) ? gps->runtime.gps_orig : gps;
394   bGPDspoint *pt_active = NULL;
395 
396   bGPDspoint *pt1, *pt2;
397   bGPDspoint *pt = NULL;
398   int pc1[2] = {0};
399   int pc2[2] = {0};
400   int i;
401   int index;
402   bool include_last = false;
403 
404   /* Check if the stroke collide with brush. */
405   if (!ED_gpencil_stroke_check_collision(gsc, gps, gso->mval, radius, diff_mat)) {
406     return;
407   }
408 
409   if (gps->totpoints == 1) {
410     bGPDspoint pt_temp;
411     pt = &gps->points[0];
412     gpencil_point_to_parent_space(gps->points, diff_mat, &pt_temp);
413     gpencil_point_to_xy(gsc, gps, &pt_temp, &pc1[0], &pc1[1]);
414 
415     pt_active = (pt->runtime.pt_orig) ? pt->runtime.pt_orig : pt;
416     /* do boundbox check first */
417     if ((!ELEM(V2D_IS_CLIPPED, pc1[0], pc1[1])) && BLI_rcti_isect_pt(rect, pc1[0], pc1[1])) {
418       /* only check if point is inside */
419       int mval_i[2];
420       round_v2i_v2fl(mval_i, gso->mval);
421       if (len_v2v2_int(mval_i, pc1) <= radius) {
422         /* apply operation to this point */
423         if (pt_active != NULL) {
424           gpencil_save_selected_point(gso, gps_active, 0, pc1);
425         }
426       }
427     }
428   }
429   else {
430     /* Loop over the points in the stroke, checking for intersections
431      * - an intersection means that we touched the stroke
432      */
433     for (i = 0; (i + 1) < gps->totpoints; i++) {
434       /* Get points to work with */
435       pt1 = gps->points + i;
436       pt2 = gps->points + i + 1;
437 
438       bGPDspoint npt;
439       gpencil_point_to_parent_space(pt1, diff_mat, &npt);
440       gpencil_point_to_xy(gsc, gps, &npt, &pc1[0], &pc1[1]);
441 
442       gpencil_point_to_parent_space(pt2, diff_mat, &npt);
443       gpencil_point_to_xy(gsc, gps, &npt, &pc2[0], &pc2[1]);
444 
445       /* Check that point segment of the boundbox of the selection stroke */
446       if (((!ELEM(V2D_IS_CLIPPED, pc1[0], pc1[1])) && BLI_rcti_isect_pt(rect, pc1[0], pc1[1])) ||
447           ((!ELEM(V2D_IS_CLIPPED, pc2[0], pc2[1])) && BLI_rcti_isect_pt(rect, pc2[0], pc2[1]))) {
448         /* Check if point segment of stroke had anything to do with
449          * brush region  (either within stroke painted, or on its lines)
450          * - this assumes that linewidth is irrelevant
451          */
452         if (gpencil_stroke_inside_circle(gso->mval, radius, pc1[0], pc1[1], pc2[0], pc2[1])) {
453 
454           /* To each point individually... */
455           pt = &gps->points[i];
456           pt_active = pt->runtime.pt_orig;
457           if (pt_active != NULL) {
458             index = (pt->runtime.pt_orig) ? pt->runtime.idx_orig : i;
459             gpencil_save_selected_point(gso, gps_active, index, pc1);
460           }
461 
462           /* Only do the second point if this is the last segment,
463            * and it is unlikely that the point will get handled
464            * otherwise.
465            *
466            * NOTE: There is a small risk here that the second point wasn't really
467            *       actually in-range. In that case, it only got in because
468            *       the line linking the points was!
469            */
470           if (i + 1 == gps->totpoints - 1) {
471             pt = &gps->points[i + 1];
472             pt_active = pt->runtime.pt_orig;
473             if (pt_active != NULL) {
474               index = (pt->runtime.pt_orig) ? pt->runtime.idx_orig : i + 1;
475               gpencil_save_selected_point(gso, gps_active, index, pc2);
476               include_last = false;
477             }
478           }
479           else {
480             include_last = true;
481           }
482         }
483         else if (include_last) {
484           /* This case is for cases where for whatever reason the second vert (1st here)
485            * doesn't get included because the whole edge isn't in bounds,
486            * but it would've qualified since it did with the previous step
487            * (but wasn't added then, to avoid double-ups).
488            */
489           pt = &gps->points[i];
490           pt_active = pt->runtime.pt_orig;
491           if (pt_active != NULL) {
492             index = (pt->runtime.pt_orig) ? pt->runtime.idx_orig : i;
493             gpencil_save_selected_point(gso, gps_active, index, pc1);
494 
495             include_last = false;
496           }
497         }
498       }
499     }
500   }
501 }
502 
503 /* Apply weight paint brushes to strokes in the given frame. */
gpencil_weightpaint_brush_do_frame(bContext * C,tGP_BrushWeightpaintData * gso,bGPDlayer * gpl,bGPDframe * gpf,const float diff_mat[4][4])504 static bool gpencil_weightpaint_brush_do_frame(bContext *C,
505                                                tGP_BrushWeightpaintData *gso,
506                                                bGPDlayer *gpl,
507                                                bGPDframe *gpf,
508                                                const float diff_mat[4][4])
509 {
510   Object *ob = CTX_data_active_object(C);
511   char tool = gso->brush->gpencil_weight_tool;
512   const int radius = (gso->brush->flag & GP_BRUSH_USE_PRESSURE) ?
513                          gso->brush->size * gso->pressure :
514                          gso->brush->size;
515   tGP_Selected *selected = NULL;
516   int i;
517 
518   /*---------------------------------------------------------------------
519    * First step: select the points affected. This step is required to have
520    * all selected points before apply the effect, because it could be
521    * required to do some step. Now is not used, but the operator is ready.
522    *--------------------------------------------------------------------- */
523   LISTBASE_FOREACH (bGPDstroke *, gps, &gpf->strokes) {
524     /* Skip strokes that are invalid for current view. */
525     if (ED_gpencil_stroke_can_use(C, gps) == false) {
526       continue;
527     }
528     /* Check if the color is editable. */
529     if (ED_gpencil_stroke_color_use(ob, gpl, gps) == false) {
530       continue;
531     }
532 
533     /* Check points below the brush. */
534     gpencil_weightpaint_select_stroke(gso, gps, diff_mat);
535   }
536 
537   /*---------------------------------------------------------------------
538    * Second step: Apply effect.
539    *--------------------------------------------------------------------- */
540   bool changed = false;
541   for (i = 0; i < gso->pbuffer_used; i++) {
542     changed = true;
543     selected = &gso->pbuffer[i];
544 
545     switch (tool) {
546       case GPWEIGHT_TOOL_DRAW: {
547         brush_draw_apply(gso, selected->gps, selected->pt_index, radius, selected->pc);
548         changed |= true;
549         break;
550       }
551       default:
552         printf("ERROR: Unknown type of GPencil Weight Paint brush\n");
553         break;
554     }
555   }
556   /* Clear the selected array, but keep the memory allocation.*/
557   gso->pbuffer = gpencil_select_buffer_ensure(
558       gso->pbuffer, &gso->pbuffer_size, &gso->pbuffer_used, true);
559 
560   return changed;
561 }
562 
563 /* Apply brush effect to all layers. */
gpencil_weightpaint_brush_apply_to_layers(bContext * C,tGP_BrushWeightpaintData * gso)564 static bool gpencil_weightpaint_brush_apply_to_layers(bContext *C, tGP_BrushWeightpaintData *gso)
565 {
566   ToolSettings *ts = CTX_data_tool_settings(C);
567   Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C);
568   Object *obact = gso->object;
569   bool changed = false;
570 
571   Object *ob_eval = (Object *)DEG_get_evaluated_id(depsgraph, &obact->id);
572   bGPdata *gpd = (bGPdata *)ob_eval->data;
573 
574   /* Find visible strokes, and perform operations on those if hit */
575   LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd->layers) {
576     /* If locked or no active frame, don't do anything. */
577     if ((!BKE_gpencil_layer_is_editable(gpl)) || (gpl->actframe == NULL)) {
578       continue;
579     }
580 
581     /* calculate difference matrix */
582     float diff_mat[4][4];
583     BKE_gpencil_parent_matrix_get(depsgraph, obact, gpl, diff_mat);
584 
585     /* Active Frame or MultiFrame? */
586     if (gso->is_multiframe) {
587       /* init multi-frame falloff options */
588       int f_init = 0;
589       int f_end = 0;
590 
591       if (gso->use_multiframe_falloff) {
592         BKE_gpencil_frame_range_selected(gpl, &f_init, &f_end);
593       }
594 
595       LISTBASE_FOREACH (bGPDframe *, gpf, &gpl->frames) {
596         /* Always do active frame; Otherwise, only include selected frames */
597         if ((gpf == gpl->actframe) || (gpf->flag & GP_FRAME_SELECT)) {
598           /* Compute multi-frame falloff factor. */
599           if (gso->use_multiframe_falloff) {
600             /* Falloff depends on distance to active frame
601              * (relative to the overall frame range). */
602             gso->mf_falloff = BKE_gpencil_multiframe_falloff_calc(
603                 gpf, gpl->actframe->framenum, f_init, f_end, ts->gp_sculpt.cur_falloff);
604           }
605           else {
606             /* No falloff */
607             gso->mf_falloff = 1.0f;
608           }
609 
610           /* affect strokes in this frame */
611           changed |= gpencil_weightpaint_brush_do_frame(C, gso, gpl, gpf, diff_mat);
612         }
613       }
614     }
615     else {
616       if (gpl->actframe != NULL) {
617         /* Apply to active frame's strokes */
618         gso->mf_falloff = 1.0f;
619         changed |= gpencil_weightpaint_brush_do_frame(C, gso, gpl, gpl->actframe, diff_mat);
620       }
621     }
622   }
623 
624   return changed;
625 }
626 
627 /* Calculate settings for applying brush */
gpencil_weightpaint_brush_apply(bContext * C,wmOperator * op,PointerRNA * itemptr)628 static void gpencil_weightpaint_brush_apply(bContext *C, wmOperator *op, PointerRNA *itemptr)
629 {
630   tGP_BrushWeightpaintData *gso = op->customdata;
631   Brush *brush = gso->brush;
632   const int radius = ((brush->flag & GP_BRUSH_USE_PRESSURE) ? gso->brush->size * gso->pressure :
633                                                               gso->brush->size);
634   float mousef[2];
635   int mouse[2];
636   bool changed = false;
637 
638   /* Get latest mouse coordinates */
639   RNA_float_get_array(itemptr, "mouse", mousef);
640   gso->mval[0] = mouse[0] = (int)(mousef[0]);
641   gso->mval[1] = mouse[1] = (int)(mousef[1]);
642 
643   gso->pressure = RNA_float_get(itemptr, "pressure");
644 
645   /* Store coordinates as reference, if operator just started running */
646   if (gso->first) {
647     gso->mval_prev[0] = gso->mval[0];
648     gso->mval_prev[1] = gso->mval[1];
649     gso->pressure_prev = gso->pressure;
650   }
651 
652   /* Update brush_rect, so that it represents the bounding rectangle of brush. */
653   gso->brush_rect.xmin = mouse[0] - radius;
654   gso->brush_rect.ymin = mouse[1] - radius;
655   gso->brush_rect.xmax = mouse[0] + radius;
656   gso->brush_rect.ymax = mouse[1] + radius;
657 
658   /* Calculate 2D direction vector and relative angle. */
659   brush_calc_dvec_2d(gso);
660 
661   changed = gpencil_weightpaint_brush_apply_to_layers(C, gso);
662 
663   /* Updates */
664   if (changed) {
665     DEG_id_tag_update(&gso->gpd->id, ID_RECALC_GEOMETRY);
666     WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL);
667   }
668 
669   /* Store values for next step */
670   gso->mval_prev[0] = gso->mval[0];
671   gso->mval_prev[1] = gso->mval[1];
672   gso->pressure_prev = gso->pressure;
673   gso->first = false;
674 }
675 
676 /* Running --------------------------------------------- */
677 
678 /* helper - a record stroke, and apply paint event */
gpencil_weightpaint_brush_apply_event(bContext * C,wmOperator * op,const wmEvent * event)679 static void gpencil_weightpaint_brush_apply_event(bContext *C,
680                                                   wmOperator *op,
681                                                   const wmEvent *event)
682 {
683   tGP_BrushWeightpaintData *gso = op->customdata;
684   PointerRNA itemptr;
685   float mouse[2];
686 
687   mouse[0] = event->mval[0] + 1;
688   mouse[1] = event->mval[1] + 1;
689 
690   /* fill in stroke */
691   RNA_collection_add(op->ptr, "stroke", &itemptr);
692 
693   RNA_float_set_array(&itemptr, "mouse", mouse);
694   RNA_boolean_set(&itemptr, "pen_flip", event->ctrl != false);
695   RNA_boolean_set(&itemptr, "is_start", gso->first);
696 
697   /* Handle pressure sensitivity (which is supplied by tablets). */
698   float pressure = event->tablet.pressure;
699   CLAMP(pressure, 0.0f, 1.0f);
700   RNA_float_set(&itemptr, "pressure", pressure);
701 
702   /* apply */
703   gpencil_weightpaint_brush_apply(C, op, &itemptr);
704 }
705 
706 /* reapply */
gpencil_weightpaint_brush_exec(bContext * C,wmOperator * op)707 static int gpencil_weightpaint_brush_exec(bContext *C, wmOperator *op)
708 {
709   if (!gpencil_weightpaint_brush_init(C, op)) {
710     return OPERATOR_CANCELLED;
711   }
712 
713   RNA_BEGIN (op->ptr, itemptr, "stroke") {
714     gpencil_weightpaint_brush_apply(C, op, &itemptr);
715   }
716   RNA_END;
717 
718   gpencil_weightpaint_brush_exit(C, op);
719 
720   return OPERATOR_FINISHED;
721 }
722 
723 /* start modal painting */
gpencil_weightpaint_brush_invoke(bContext * C,wmOperator * op,const wmEvent * event)724 static int gpencil_weightpaint_brush_invoke(bContext *C, wmOperator *op, const wmEvent *event)
725 {
726   tGP_BrushWeightpaintData *gso = NULL;
727   const bool is_modal = RNA_boolean_get(op->ptr, "wait_for_input");
728   const bool is_playing = ED_screen_animation_playing(CTX_wm_manager(C)) != NULL;
729 
730   /* the operator cannot work while play animation */
731   if (is_playing) {
732     BKE_report(op->reports, RPT_ERROR, "Cannot Paint while play animation");
733 
734     return OPERATOR_CANCELLED;
735   }
736 
737   /* init painting data */
738   if (!gpencil_weightpaint_brush_init(C, op)) {
739     return OPERATOR_CANCELLED;
740   }
741 
742   gso = op->customdata;
743 
744   /* register modal handler */
745   WM_event_add_modal_handler(C, op);
746 
747   /* start drawing immediately? */
748   if (is_modal == false) {
749     ARegion *region = CTX_wm_region(C);
750 
751     /* apply first dab... */
752     gso->is_painting = true;
753     gpencil_weightpaint_brush_apply_event(C, op, event);
754 
755     /* redraw view with feedback */
756     ED_region_tag_redraw(region);
757   }
758 
759   return OPERATOR_RUNNING_MODAL;
760 }
761 
762 /* painting - handle events */
gpencil_weightpaint_brush_modal(bContext * C,wmOperator * op,const wmEvent * event)763 static int gpencil_weightpaint_brush_modal(bContext *C, wmOperator *op, const wmEvent *event)
764 {
765   tGP_BrushWeightpaintData *gso = op->customdata;
766   const bool is_modal = RNA_boolean_get(op->ptr, "wait_for_input");
767   bool redraw_region = false;
768   bool redraw_toolsettings = false;
769 
770   /* The operator can be in 2 states: Painting and Idling */
771   if (gso->is_painting) {
772     /* Painting  */
773     switch (event->type) {
774       /* Mouse Move = Apply somewhere else */
775       case MOUSEMOVE:
776       case INBETWEEN_MOUSEMOVE:
777         /* apply brush effect at new position */
778         gpencil_weightpaint_brush_apply_event(C, op, event);
779 
780         /* force redraw, so that the cursor will at least be valid */
781         redraw_region = true;
782         break;
783 
784       /* Painting mbut release = Stop painting (back to idle) */
785       case LEFTMOUSE:
786         if (is_modal) {
787           /* go back to idling... */
788           gso->is_painting = false;
789         }
790         else {
791           /* end painting, since we're not modal */
792           gso->is_painting = false;
793 
794           gpencil_weightpaint_brush_exit(C, op);
795           return OPERATOR_FINISHED;
796         }
797         break;
798 
799       /* Abort painting if any of the usual things are tried */
800       case MIDDLEMOUSE:
801       case RIGHTMOUSE:
802       case EVT_ESCKEY:
803         gpencil_weightpaint_brush_exit(C, op);
804         return OPERATOR_FINISHED;
805     }
806   }
807   else {
808     /* Idling */
809     BLI_assert(is_modal == true);
810 
811     switch (event->type) {
812       /* Painting mbut press = Start painting (switch to painting state) */
813       case LEFTMOUSE:
814         /* do initial "click" apply */
815         gso->is_painting = true;
816         gso->first = true;
817 
818         gpencil_weightpaint_brush_apply_event(C, op, event);
819         break;
820 
821       /* Exit modal operator, based on the "standard" ops */
822       case RIGHTMOUSE:
823       case EVT_ESCKEY:
824         gpencil_weightpaint_brush_exit(C, op);
825         return OPERATOR_FINISHED;
826 
827       /* MMB is often used for view manipulations */
828       case MIDDLEMOUSE:
829         return OPERATOR_PASS_THROUGH;
830 
831       /* Mouse movements should update the brush cursor - Just redraw the active region */
832       case MOUSEMOVE:
833       case INBETWEEN_MOUSEMOVE:
834         redraw_region = true;
835         break;
836 
837       /* Change Frame - Allowed */
838       case EVT_LEFTARROWKEY:
839       case EVT_RIGHTARROWKEY:
840       case EVT_UPARROWKEY:
841       case EVT_DOWNARROWKEY:
842         return OPERATOR_PASS_THROUGH;
843 
844       /* Camera/View Gizmo's - Allowed */
845       /* (See rationale in gpencil_paint.c -> gpencil_draw_modal()) */
846       case EVT_PAD0:
847       case EVT_PAD1:
848       case EVT_PAD2:
849       case EVT_PAD3:
850       case EVT_PAD4:
851       case EVT_PAD5:
852       case EVT_PAD6:
853       case EVT_PAD7:
854       case EVT_PAD8:
855       case EVT_PAD9:
856         return OPERATOR_PASS_THROUGH;
857 
858       /* Unhandled event */
859       default:
860         break;
861     }
862   }
863 
864   /* Redraw region? */
865   if (redraw_region) {
866     ED_region_tag_redraw(CTX_wm_region(C));
867   }
868 
869   /* Redraw toolsettings (brush settings)? */
870   if (redraw_toolsettings) {
871     DEG_id_tag_update(&gso->gpd->id, ID_RECALC_GEOMETRY);
872     WM_event_add_notifier(C, NC_SCENE | ND_TOOLSETTINGS, NULL);
873   }
874 
875   return OPERATOR_RUNNING_MODAL;
876 }
877 
GPENCIL_OT_weight_paint(wmOperatorType * ot)878 void GPENCIL_OT_weight_paint(wmOperatorType *ot)
879 {
880   /* identifiers */
881   ot->name = "Stroke Weight Paint";
882   ot->idname = "GPENCIL_OT_weight_paint";
883   ot->description = "Paint stroke points with a color";
884 
885   /* api callbacks */
886   ot->exec = gpencil_weightpaint_brush_exec;
887   ot->invoke = gpencil_weightpaint_brush_invoke;
888   ot->modal = gpencil_weightpaint_brush_modal;
889   ot->cancel = gpencil_weightpaint_brush_exit;
890   ot->poll = gpencil_weightpaint_brush_poll;
891 
892   /* flags */
893   ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO | OPTYPE_BLOCKING;
894 
895   /* properties */
896   PropertyRNA *prop;
897   prop = RNA_def_collection_runtime(ot->srna, "stroke", &RNA_OperatorStrokeElement, "Stroke", "");
898   RNA_def_property_flag(prop, PROP_HIDDEN | PROP_SKIP_SAVE);
899 
900   prop = RNA_def_boolean(ot->srna, "wait_for_input", true, "Wait for Input", "");
901   RNA_def_property_flag(prop, PROP_HIDDEN | PROP_SKIP_SAVE);
902 }
903