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