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:
authorAntonio Vazquez <blendergit@gmail.com>2020-03-09 18:27:24 +0300
committerAntonio Vazquez <blendergit@gmail.com>2020-03-09 18:27:24 +0300
commit29f3af95272590d26f610ae828b2eeee89c82a00 (patch)
treea696a58a2561c48f7ec6166e369e22081e0a64d8 /source/blender/editors/gpencil/gpencil_vertex_paint.c
parentdcb93126876879d969a30a7865700abd072066f8 (diff)
GPencil: Refactor of Draw Engine, Vertex Paint and all internal functions
This commit is a full refactor of the grease pencil modules including Draw Engine, Modifiers, VFX, depsgraph update, improvements in operators and conversion of Sculpt and Weight paint tools to real brushes. Also, a huge code cleanup has been done at all levels. Thanks to @fclem for his work and yo @pepeland and @mendio for the testing and help in the development. Differential Revision: https://developer.blender.org/D6293
Diffstat (limited to 'source/blender/editors/gpencil/gpencil_vertex_paint.c')
-rw-r--r--source/blender/editors/gpencil/gpencil_vertex_paint.c1414
1 files changed, 1414 insertions, 0 deletions
diff --git a/source/blender/editors/gpencil/gpencil_vertex_paint.c b/source/blender/editors/gpencil/gpencil_vertex_paint.c
new file mode 100644
index 00000000000..5b5a306aa25
--- /dev/null
+++ b/source/blender/editors/gpencil/gpencil_vertex_paint.c
@@ -0,0 +1,1414 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * The Original Code is Copyright (C) 2015, Blender Foundation
+ * This is a new part of Blender
+ * Brush based operators for editing Grease Pencil strokes
+ */
+
+/** \file
+ * \ingroup edgpencil
+ */
+
+#include "MEM_guardedalloc.h"
+
+#include "BLI_blenlib.h"
+#include "BLI_math.h"
+
+#include "BLT_translation.h"
+
+#include "DNA_brush_types.h"
+#include "DNA_gpencil_types.h"
+
+#include "BKE_brush.h"
+#include "BKE_colortools.h"
+#include "BKE_context.h"
+#include "BKE_gpencil.h"
+#include "BKE_report.h"
+
+#include "WM_api.h"
+#include "WM_types.h"
+
+#include "RNA_access.h"
+#include "RNA_define.h"
+
+#include "UI_view2d.h"
+
+#include "ED_gpencil.h"
+#include "ED_screen.h"
+#include "ED_view3d.h"
+
+#include "DEG_depsgraph.h"
+#include "DEG_depsgraph_query.h"
+
+#include "gpencil_intern.h"
+
+/* ************************************************ */
+/* General Brush Editing Context */
+#define GP_SELECT_BUFFER_CHUNK 256
+#define GP_GRID_PIXEL_SIZE 10.0f
+
+/* Temp Flags while Painting. */
+typedef enum eGPDvertex_brush_Flag {
+ /* invert the effect of the brush */
+ GP_VERTEX_FLAG_INVERT = (1 << 0),
+ /* temporary invert action */
+ GP_VERTEX_FLAG_TMP_INVERT = (1 << 1),
+} eGPDvertex_brush_Flag;
+
+/* Grid of Colors for Smear. */
+typedef struct tGP_Grid {
+ /** Lower right corner of rectangle of grid cell. */
+ float bottom[2];
+ /** Upper left corner of rectangle of grid cell. */
+ float top[2];
+ /** Average Color */
+ float color[4];
+ /** Total points included. */
+ int totcol;
+
+} tGP_Grid;
+
+/* List of points affected by brush. */
+typedef struct tGP_Selected {
+ /** Referenced stroke. */
+ bGPDstroke *gps;
+ /** Point index in points array. */
+ int pt_index;
+ /** Position */
+ int pc[2];
+ /** Color */
+ float color[4];
+} tGP_Selected;
+
+/* Context for brush operators */
+typedef struct tGP_BrushVertexpaintData {
+ Scene *scene;
+ Object *object;
+
+ ARegion *region;
+
+ /* Current GPencil datablock */
+ bGPdata *gpd;
+
+ Brush *brush;
+ eGPDvertex_brush_Flag flag;
+ eGP_Vertex_SelectMaskFlag mask;
+
+ /* Space Conversion Data */
+ GP_SpaceConversion gsc;
+
+ /* Is the brush currently painting? */
+ bool is_painting;
+
+ /* Start of new paint */
+ bool first;
+
+ /* Is multiframe editing enabled, and are we using falloff for that? */
+ bool is_multiframe;
+ bool use_multiframe_falloff;
+
+ /* Brush Runtime Data: */
+ /* - position and pressure
+ * - the *_prev variants are the previous values
+ */
+ float mval[2], mval_prev[2];
+ float pressure, pressure_prev;
+
+ /* - Effect 2D vector */
+ float dvec[2];
+
+ /* - multiframe falloff factor */
+ float mf_falloff;
+
+ /* brush geometry (bounding box) */
+ rcti brush_rect;
+
+ /* Temp data to save selected points */
+ /** Stroke buffer. */
+ tGP_Selected *pbuffer;
+ /** Number of elements currently used in cache. */
+ int pbuffer_used;
+ /** Number of total elements available in cache. */
+ int pbuffer_size;
+
+ /** Grid of average colors */
+ tGP_Grid *grid;
+ /** Total number of rows/cols. */
+ int grid_size;
+ /** Total number of cells elments in the grid array. */
+ int grid_len;
+ /** Grid sample position (used to determine distance of falloff) */
+ int grid_sample[2];
+ /** Grid is ready to use */
+ bool grid_ready;
+
+} tGP_BrushVertexpaintData;
+
+/* Ensure the buffer to hold temp selected point size is enough to save all points selected. */
+static tGP_Selected *gpencil_select_buffer_ensure(tGP_Selected *buffer_array,
+ int *buffer_size,
+ int *buffer_used,
+ const bool clear)
+{
+ tGP_Selected *p = NULL;
+
+ /* By default a buffer is created with one block with a predefined number of free slots,
+ * if the size is not enough, the cache is reallocated adding a new block of free slots.
+ * This is done in order to keep cache small and improve speed. */
+ if (*buffer_used + 1 > *buffer_size) {
+ if ((*buffer_size == 0) || (buffer_array == NULL)) {
+ p = MEM_callocN(sizeof(struct tGP_Selected) * GP_SELECT_BUFFER_CHUNK, __func__);
+ *buffer_size = GP_SELECT_BUFFER_CHUNK;
+ }
+ else {
+ *buffer_size += GP_SELECT_BUFFER_CHUNK;
+ p = MEM_recallocN(buffer_array, sizeof(struct tGP_Selected) * *buffer_size);
+ }
+
+ if (p == NULL) {
+ *buffer_size = *buffer_used = 0;
+ }
+
+ buffer_array = p;
+ }
+
+ /* clear old data */
+ if (clear) {
+ *buffer_used = 0;
+ if (buffer_array != NULL) {
+ memset(buffer_array, 0, sizeof(tGP_Selected) * *buffer_size);
+ }
+ }
+
+ return buffer_array;
+}
+
+/* Brush Operations ------------------------------- */
+
+/* Invert behavior of brush? */
+static bool brush_invert_check(tGP_BrushVertexpaintData *gso)
+{
+ /* The basic setting is no inverted */
+ bool invert = false;
+
+ /* During runtime, the user can hold down the Ctrl key to invert the basic behavior */
+ if (gso->flag & GP_VERTEX_FLAG_INVERT) {
+ invert ^= true;
+ }
+
+ return invert;
+}
+
+/* Compute strength of effect. */
+static float brush_influence_calc(tGP_BrushVertexpaintData *gso, const int radius, const int co[2])
+{
+ Brush *brush = gso->brush;
+ float influence = brush->size;
+
+ /* use pressure? */
+ if (brush->gpencil_settings->flag & GP_BRUSH_USE_PRESSURE) {
+ influence *= gso->pressure;
+ }
+
+ /* distance fading */
+ int mval_i[2];
+ round_v2i_v2fl(mval_i, gso->mval);
+ float distance = (float)len_v2v2_int(mval_i, co);
+
+ /* Apply Brush curve. */
+ float brush_fallof = BKE_brush_curve_strength(brush, distance, (float)radius);
+ influence *= brush_fallof;
+
+ /* apply multiframe falloff */
+ influence *= gso->mf_falloff;
+
+ /* return influence */
+ return influence;
+}
+
+/* Compute effect vector for directional brushes. */
+static void brush_calc_dvec_2d(tGP_BrushVertexpaintData *gso)
+{
+ gso->dvec[0] = (float)(gso->mval[0] - gso->mval_prev[0]);
+ gso->dvec[1] = (float)(gso->mval[1] - gso->mval_prev[1]);
+
+ normalize_v2(gso->dvec);
+}
+
+/* Init a grid of cells around mouse position.
+ *
+ * For each Cell.
+ *
+ * *--------* Top
+ * | |
+ * | |
+ * Bottom *--------*
+ *
+ * The number of cells is calculated using the brush size and a predefined
+ * number of pixels (see: GP_GRID_PIXEL_SIZE)
+ */
+
+static void gp_grid_cells_init(tGP_BrushVertexpaintData *gso)
+{
+ tGP_Grid *grid;
+ float bottom[2];
+ float top[2];
+ int grid_index = 0;
+
+ /* The grid center is (0,0). */
+ bottom[0] = gso->brush_rect.xmin - gso->mval[0];
+ bottom[1] = gso->brush_rect.ymax - GP_GRID_PIXEL_SIZE - gso->mval[1];
+
+ /* Calc all cell of the grid from top/left. */
+ for (int y = gso->grid_size - 1; y >= 0; y--) {
+ top[1] = bottom[1] + GP_GRID_PIXEL_SIZE;
+
+ for (int x = 0; x < gso->grid_size; x++) {
+ top[0] = bottom[0] + GP_GRID_PIXEL_SIZE;
+
+ grid = &gso->grid[grid_index];
+
+ copy_v2_v2(grid->bottom, bottom);
+ copy_v2_v2(grid->top, top);
+
+ bottom[0] += GP_GRID_PIXEL_SIZE;
+
+ grid_index++;
+ }
+
+ /* Reset for new row. */
+ bottom[0] = gso->brush_rect.xmin - gso->mval[0];
+ bottom[1] -= GP_GRID_PIXEL_SIZE;
+ }
+}
+
+/* Get the index used in the grid base on dvec. */
+static void gp_grid_cell_average_color_idx_get(tGP_BrushVertexpaintData *gso, int r_idx[2])
+{
+ /* Lower direction. */
+ if (gso->dvec[1] < 0.0f) {
+ if ((gso->dvec[0] >= -1.0f) && (gso->dvec[0] < -0.8f)) {
+ r_idx[0] = 0;
+ r_idx[1] = -1;
+ }
+ else if ((gso->dvec[0] >= -0.8f) && (gso->dvec[0] < -0.6f)) {
+ r_idx[0] = -1;
+ r_idx[1] = -1;
+ }
+ else if ((gso->dvec[0] >= -0.6f) && (gso->dvec[0] < 0.6f)) {
+ r_idx[0] = -1;
+ r_idx[1] = 0;
+ }
+ else if ((gso->dvec[0] >= 0.6f) && (gso->dvec[0] < 0.8f)) {
+ r_idx[0] = -1;
+ r_idx[1] = 1;
+ }
+ else if (gso->dvec[0] >= 0.8f) {
+ r_idx[0] = 0;
+ r_idx[1] = 1;
+ }
+ }
+ /* Upper direction. */
+ else {
+ if ((gso->dvec[0] >= -1.0f) && (gso->dvec[0] < -0.8f)) {
+ r_idx[0] = 0;
+ r_idx[1] = -1;
+ }
+ else if ((gso->dvec[0] >= -0.8f) && (gso->dvec[0] < -0.6f)) {
+ r_idx[0] = 1;
+ r_idx[1] = -1;
+ }
+ else if ((gso->dvec[0] >= -0.6f) && (gso->dvec[0] < 0.6f)) {
+ r_idx[0] = 1;
+ r_idx[1] = 0;
+ }
+ else if ((gso->dvec[0] >= 0.6f) && (gso->dvec[0] < 0.8f)) {
+ r_idx[0] = 1;
+ r_idx[1] = 1;
+ }
+ else if (gso->dvec[0] >= 0.8f) {
+ r_idx[0] = 0;
+ r_idx[1] = 1;
+ }
+ }
+}
+
+static int gp_grid_cell_index_get(tGP_BrushVertexpaintData *gso, int pc[2])
+{
+ float bottom[2], top[2];
+
+ for (int i = 0; i < gso->grid_len; i++) {
+ tGP_Grid *grid = &gso->grid[i];
+ add_v2_v2v2(bottom, grid->bottom, gso->mval);
+ add_v2_v2v2(top, grid->top, gso->mval);
+
+ if (pc[0] >= bottom[0] && pc[0] <= top[0] && pc[1] >= bottom[1] && pc[1] <= top[1]) {
+ return i;
+ }
+ }
+
+ return -1;
+}
+
+/* Fill the grid with the color in each cell and assign point cell index. */
+static void gp_grid_colors_calc(tGP_BrushVertexpaintData *gso)
+{
+ tGP_Selected *selected = NULL;
+ bGPDstroke *gps_selected = NULL;
+ bGPDspoint *pt = NULL;
+ tGP_Grid *grid = NULL;
+
+ /* Don't calculate again. */
+ if (gso->grid_ready) {
+ return;
+ }
+
+ /* Extract colors by cell. */
+ for (int i = 0; i < gso->pbuffer_used; i++) {
+ selected = &gso->pbuffer[i];
+ gps_selected = selected->gps;
+ pt = &gps_selected->points[selected->pt_index];
+ int grid_index = gp_grid_cell_index_get(gso, selected->pc);
+
+ if (grid_index > -1) {
+ grid = &gso->grid[grid_index];
+ /* Add stroke mix color (only if used). */
+ if (pt->vert_color[3] > 0.0f) {
+ add_v3_v3(grid->color, selected->color);
+ grid->color[3] = 1.0f;
+ grid->totcol++;
+ }
+ }
+ }
+
+ /* Average colors. */
+ for (int i = 0; i < gso->grid_len; i++) {
+ grid = &gso->grid[i];
+ if (grid->totcol > 0) {
+ mul_v3_fl(grid->color, (1.0f / (float)grid->totcol));
+ }
+ }
+
+ /* Save sample position. */
+ round_v2i_v2fl(gso->grid_sample, gso->mval);
+
+ gso->grid_ready = true;
+
+ return;
+}
+
+/* ************************************************ */
+/* Brush Callbacks
+ * This section defines the callbacks used by each brush to perform their magic.
+ * These are called on each point within the brush's radius. */
+
+/* Tint Brush */
+static bool brush_tint_apply(tGP_BrushVertexpaintData *gso,
+ bGPDstroke *gps,
+ int pt_index,
+ const int radius,
+ const int co[2])
+{
+ Brush *brush = gso->brush;
+
+ /* Attenuate factor to get a smoother tinting. */
+ float inf = (brush_influence_calc(gso, radius, co) * brush->gpencil_settings->draw_strength) /
+ 100.0f;
+ float inf_fill = (gso->pressure * brush->gpencil_settings->draw_strength) / 1000.0f;
+
+ CLAMP(inf, 0.0f, 1.0f);
+ CLAMP(inf_fill, 0.0f, 1.0f);
+
+ bGPDspoint *pt = &gps->points[pt_index];
+
+ /* Apply color to Stroke point. */
+ if (GPENCIL_TINT_VERTEX_COLOR_STROKE(brush)) {
+ if (brush_invert_check(gso)) {
+ pt->vert_color[3] -= inf;
+ CLAMP_MIN(pt->vert_color[3], 0.0f);
+ }
+ else {
+ /* Premult. */
+ mul_v3_fl(pt->vert_color, pt->vert_color[3]);
+ /* "Alpha over" blending. */
+ interp_v3_v3v3(pt->vert_color, pt->vert_color, brush->rgb, inf);
+ pt->vert_color[3] = pt->vert_color[3] * (1.0 - inf) + inf;
+ /* Un-premult. */
+ if (pt->vert_color[3] > 0.0f) {
+ mul_v3_fl(pt->vert_color, 1.0f / pt->vert_color[3]);
+ }
+ }
+ }
+
+ /* Apply color to Fill area (all with same color and factor). */
+ if (GPENCIL_TINT_VERTEX_COLOR_FILL(brush)) {
+ if (brush_invert_check(gso)) {
+ gps->vert_color_fill[3] -= inf_fill;
+ CLAMP_MIN(gps->vert_color_fill[3], 0.0f);
+ }
+ else {
+ /* Premult. */
+ mul_v3_fl(gps->vert_color_fill, gps->vert_color_fill[3]);
+ /* "Alpha over" blending. */
+ interp_v3_v3v3(gps->vert_color_fill, gps->vert_color_fill, brush->rgb, inf_fill);
+ gps->vert_color_fill[3] = gps->vert_color_fill[3] * (1.0 - inf_fill) + inf_fill;
+ /* Un-premult. */
+ if (gps->vert_color_fill[3] > 0.0f) {
+ mul_v3_fl(gps->vert_color_fill, 1.0f / gps->vert_color_fill[3]);
+ }
+ }
+ }
+
+ return true;
+}
+
+/* Replace Brush (Don't use pressure or invert). */
+static bool brush_replace_apply(tGP_BrushVertexpaintData *gso, bGPDstroke *gps, int pt_index)
+{
+ Brush *brush = gso->brush;
+ bGPDspoint *pt = &gps->points[pt_index];
+
+ /* Apply color to Stroke point. */
+ if (GPENCIL_TINT_VERTEX_COLOR_STROKE(brush)) {
+ copy_v3_v3(pt->vert_color, brush->rgb);
+ /* If not mix color, full replace. */
+ if (pt->vert_color[3] == 0.0f) {
+ pt->vert_color[3] = 1.0f;
+ }
+ }
+
+ /* Apply color to Fill area (all with same color and factor). */
+ if (GPENCIL_TINT_VERTEX_COLOR_FILL(brush)) {
+ copy_v3_v3(gps->vert_color_fill, brush->rgb);
+ /* If not mix color, full replace. */
+ if (gps->vert_color_fill[3] == 0.0f) {
+ gps->vert_color_fill[3] = 1.0f;
+ }
+ }
+
+ return true;
+}
+
+/* Get surrounding color. */
+static bool get_surrounding_color(tGP_BrushVertexpaintData *gso,
+ bGPDstroke *gps,
+ int pt_index,
+ float r_color[3])
+{
+ tGP_Selected *selected = NULL;
+ bGPDstroke *gps_selected = NULL;
+ bGPDspoint *pt = NULL;
+
+ int totcol = 0;
+ zero_v3(r_color);
+
+ /* Average the surrounding points except current one. */
+ for (int i = 0; i < gso->pbuffer_used; i++) {
+ selected = &gso->pbuffer[i];
+ gps_selected = selected->gps;
+ /* current point is not evaluated. */
+ if ((gps_selected == gps) && (selected->pt_index == pt_index)) {
+ continue;
+ }
+
+ pt = &gps_selected->points[selected->pt_index];
+
+ /* Add stroke mix color (only if used). */
+ if (pt->vert_color[3] > 0.0f) {
+ add_v3_v3(r_color, selected->color);
+ totcol++;
+ }
+ }
+ if (totcol > 0) {
+ mul_v3_fl(r_color, (1.0f / (float)totcol));
+ return true;
+ }
+
+ return false;
+}
+
+/* Blur Brush */
+static bool brush_blur_apply(tGP_BrushVertexpaintData *gso,
+ bGPDstroke *gps,
+ int pt_index,
+ const int radius,
+ const int co[2])
+{
+ Brush *brush = gso->brush;
+
+ /* Attenuate factor to get a smoother tinting. */
+ float inf = (brush_influence_calc(gso, radius, co) * brush->gpencil_settings->draw_strength) /
+ 100.0f;
+ float inf_fill = (gso->pressure * brush->gpencil_settings->draw_strength) / 1000.0f;
+
+ bGPDspoint *pt = &gps->points[pt_index];
+
+ /* Get surrounding color. */
+ float blur_color[3];
+ if (get_surrounding_color(gso, gps, pt_index, blur_color)) {
+ /* Apply color to Stroke point. */
+ if (GPENCIL_TINT_VERTEX_COLOR_STROKE(brush)) {
+ interp_v3_v3v3(pt->vert_color, pt->vert_color, blur_color, inf);
+ }
+
+ /* Apply color to Fill area (all with same color and factor). */
+ if (GPENCIL_TINT_VERTEX_COLOR_FILL(brush)) {
+ interp_v3_v3v3(gps->vert_color_fill, gps->vert_color_fill, blur_color, inf_fill);
+ }
+ return true;
+ }
+
+ return false;
+}
+
+/* Average Brush */
+static bool brush_average_apply(tGP_BrushVertexpaintData *gso,
+ bGPDstroke *gps,
+ int pt_index,
+ const int radius,
+ const int co[2],
+ float average_color[3])
+{
+ Brush *brush = gso->brush;
+
+ /* Attenuate factor to get a smoother tinting. */
+ float inf = (brush_influence_calc(gso, radius, co) * brush->gpencil_settings->draw_strength) /
+ 100.0f;
+ float inf_fill = (gso->pressure * brush->gpencil_settings->draw_strength) / 1000.0f;
+
+ bGPDspoint *pt = &gps->points[pt_index];
+
+ float alpha = pt->vert_color[3];
+ float alpha_fill = gps->vert_color_fill[3];
+
+ if (brush_invert_check(gso)) {
+ alpha -= inf;
+ alpha_fill -= inf_fill;
+ }
+ else {
+ alpha += inf;
+ alpha_fill += inf_fill;
+ }
+
+ /* Apply color to Stroke point. */
+ if (GPENCIL_TINT_VERTEX_COLOR_STROKE(brush)) {
+ CLAMP(alpha, 0.0f, 1.0f);
+ interp_v3_v3v3(pt->vert_color, pt->vert_color, average_color, inf);
+ pt->vert_color[3] = alpha;
+ }
+
+ /* Apply color to Fill area (all with same color and factor). */
+ if (GPENCIL_TINT_VERTEX_COLOR_FILL(brush)) {
+ CLAMP(alpha_fill, 0.0f, 1.0f);
+ copy_v3_v3(gps->vert_color_fill, average_color);
+ gps->vert_color_fill[3] = alpha_fill;
+ }
+
+ return true;
+}
+
+/* Smear Brush */
+static bool brush_smear_apply(tGP_BrushVertexpaintData *gso,
+ bGPDstroke *gps,
+ int pt_index,
+ tGP_Selected *selected)
+{
+ Brush *brush = gso->brush;
+ tGP_Grid *grid = NULL;
+ int average_idx[2];
+ ARRAY_SET_ITEMS(average_idx, 0, 0);
+
+ bool changed = false;
+
+ /* Need some movement, so first input is not done. */
+ if (gso->first) {
+ return false;
+ }
+
+ bGPDspoint *pt = &gps->points[pt_index];
+
+ /* Need get average colors in the grid. */
+ if ((!gso->grid_ready) && (gso->pbuffer_used > 0)) {
+ gp_grid_colors_calc(gso);
+ }
+
+ /* The influence is equal to strength and no decay around brush radius. */
+ float inf = brush->gpencil_settings->draw_strength;
+ if (brush->flag & GP_BRUSH_USE_PRESSURE) {
+ inf *= gso->pressure;
+ }
+
+ /* Calc distance from initial sample location and add a fallof effect. */
+ int mval_i[2];
+ round_v2i_v2fl(mval_i, gso->mval);
+ float distance = (float)len_v2v2_int(mval_i, gso->grid_sample);
+ float fac = 1.0f - (distance / (float)(brush->size * 2));
+ CLAMP(fac, 0.0f, 1.0f);
+ inf *= fac;
+
+ /* Retry row and col for average color. */
+ gp_grid_cell_average_color_idx_get(gso, average_idx);
+
+ /* Retry average color cell. */
+ int grid_index = gp_grid_cell_index_get(gso, selected->pc);
+ if (grid_index > -1) {
+ int row = grid_index / gso->grid_size;
+ int col = grid_index - (gso->grid_size * row);
+ row += average_idx[0];
+ col += average_idx[1];
+ CLAMP(row, 0, gso->grid_size);
+ CLAMP(col, 0, gso->grid_size);
+
+ int new_index = (row * gso->grid_size) + col;
+ CLAMP(new_index, 0, gso->grid_len - 1);
+ grid = &gso->grid[new_index];
+ }
+
+ /* Apply color to Stroke point. */
+ if (GPENCIL_TINT_VERTEX_COLOR_STROKE(brush)) {
+ if (grid_index > -1) {
+ if (grid->color[3] > 0.0f) {
+ // copy_v3_v3(pt->vert_color, grid->color);
+ interp_v3_v3v3(pt->vert_color, pt->vert_color, grid->color, inf);
+ changed = true;
+ }
+ }
+ }
+
+ /* Apply color to Fill area (all with same color and factor). */
+ if (GPENCIL_TINT_VERTEX_COLOR_FILL(brush)) {
+ if (grid_index > -1) {
+ if (grid->color[3] > 0.0f) {
+ interp_v3_v3v3(gps->vert_color_fill, gps->vert_color_fill, grid->color, inf);
+ changed = true;
+ }
+ }
+ }
+
+ return changed;
+}
+
+/* ************************************************ */
+/* Header Info */
+static void gp_vertexpaint_brush_header_set(bContext *C)
+{
+ ED_workspace_status_text(C,
+ TIP_("GPencil Vertex Paint: LMB to paint | RMB/Escape to Exit"
+ " | Ctrl to Invert Action"));
+}
+
+/* ************************************************ */
+/* Grease Pencil Vertex Paint Operator */
+
+/* Init/Exit ----------------------------------------------- */
+
+static bool gp_vertexpaint_brush_init(bContext *C, wmOperator *op)
+{
+ Scene *scene = CTX_data_scene(C);
+ ToolSettings *ts = CTX_data_tool_settings(C);
+ Object *ob = CTX_data_active_object(C);
+ Paint *paint = ob->mode == OB_MODE_VERTEX_GPENCIL ? &ts->gp_vertexpaint->paint :
+ &ts->gp_paint->paint;
+
+ /* set the brush using the tool */
+ tGP_BrushVertexpaintData *gso;
+
+ /* setup operator data */
+ gso = MEM_callocN(sizeof(tGP_BrushVertexpaintData), "tGP_BrushVertexpaintData");
+ op->customdata = gso;
+
+ gso->brush = paint->brush;
+ BKE_curvemapping_initialize(gso->brush->curve);
+
+ gso->is_painting = false;
+ gso->first = true;
+
+ gso->pbuffer = NULL;
+ gso->pbuffer_size = 0;
+ gso->pbuffer_used = 0;
+
+ /* Alloc grid array */
+ gso->grid_size = (int)(((gso->brush->size * 2.0f) / GP_GRID_PIXEL_SIZE) + 1.0);
+ /* Square value. */
+ gso->grid_len = gso->grid_size * gso->grid_size;
+ gso->grid = MEM_callocN(sizeof(tGP_Grid) * gso->grid_len, "tGP_Grid");
+ gso->grid_ready = false;
+
+ gso->gpd = ED_gpencil_data_get_active(C);
+ gso->scene = scene;
+ gso->object = ob;
+
+ gso->region = CTX_wm_region(C);
+
+ /* Save mask. */
+ gso->mask = ts->gpencil_selectmode_vertex;
+
+ /* Multiframe settings. */
+ gso->is_multiframe = (bool)GPENCIL_MULTIEDIT_SESSIONS_ON(gso->gpd);
+ gso->use_multiframe_falloff = (ts->gp_sculpt.flag & GP_SCULPT_SETT_FLAG_FRAME_FALLOFF) != 0;
+
+ /* Init multi-edit falloff curve data before doing anything,
+ * so we won't have to do it again later. */
+ if (gso->is_multiframe) {
+ BKE_curvemapping_initialize(ts->gp_sculpt.cur_falloff);
+ }
+
+ /* Setup space conversions. */
+ gp_point_conversion_init(C, &gso->gsc);
+
+ /* Update header. */
+ gp_vertexpaint_brush_header_set(C);
+
+ return true;
+}
+
+static void gp_vertexpaint_brush_exit(bContext *C, wmOperator *op)
+{
+ tGP_BrushVertexpaintData *gso = op->customdata;
+
+ /* Disable headerprints. */
+ ED_workspace_status_text(C, NULL);
+
+ /* Disable temp invert flag. */
+ gso->brush->flag &= ~GP_VERTEX_FLAG_TMP_INVERT;
+
+ /* Free operator data */
+ MEM_SAFE_FREE(gso->pbuffer);
+ MEM_SAFE_FREE(gso->grid);
+ MEM_SAFE_FREE(gso);
+ op->customdata = NULL;
+}
+
+/* Poll callback for stroke vertex paint operator. */
+static bool gp_vertexpaint_brush_poll(bContext *C)
+{
+ /* NOTE: this is a bit slower, but is the most accurate... */
+ return CTX_DATA_COUNT(C, editable_gpencil_strokes) != 0;
+}
+
+/* Helper to save the points selected by the brush. */
+static void gp_save_selected_point(tGP_BrushVertexpaintData *gso,
+ bGPDstroke *gps,
+ int index,
+ int pc[2])
+{
+ tGP_Selected *selected;
+ bGPDspoint *pt = &gps->points[index];
+
+ /* Ensure the array to save the list of selected points is big enough. */
+ gso->pbuffer = gpencil_select_buffer_ensure(
+ gso->pbuffer, &gso->pbuffer_size, &gso->pbuffer_used, false);
+
+ selected = &gso->pbuffer[gso->pbuffer_used];
+ selected->gps = gps;
+ selected->pt_index = index;
+ copy_v2_v2_int(selected->pc, pc);
+ copy_v4_v4(selected->color, pt->vert_color);
+
+ gso->pbuffer_used++;
+}
+
+/* Select points in this stroke and add to an array to be used later. */
+static void gp_vertexpaint_select_stroke(tGP_BrushVertexpaintData *gso,
+ bGPDstroke *gps,
+ const float diff_mat[4][4])
+{
+ GP_SpaceConversion *gsc = &gso->gsc;
+ rcti *rect = &gso->brush_rect;
+ Brush *brush = gso->brush;
+ const int radius = (brush->flag & GP_BRUSH_USE_PRESSURE) ? gso->brush->size * gso->pressure :
+ gso->brush->size;
+ bGPDstroke *gps_active = (gps->runtime.gps_orig) ? gps->runtime.gps_orig : gps;
+ bGPDspoint *pt_active = NULL;
+
+ bGPDspoint *pt1, *pt2;
+ bGPDspoint *pt = NULL;
+ int pc1[2] = {0};
+ int pc2[2] = {0};
+ int i;
+ int index;
+ bool include_last = false;
+
+ /* Check if the stroke collide with brush. */
+ if (!ED_gpencil_stroke_check_collision(gsc, gps, gso->mval, radius, diff_mat)) {
+ return;
+ }
+
+ if (gps->totpoints == 1) {
+ bGPDspoint pt_temp;
+ pt = &gps->points[0];
+ gp_point_to_parent_space(gps->points, diff_mat, &pt_temp);
+ gp_point_to_xy(gsc, gps, &pt_temp, &pc1[0], &pc1[1]);
+
+ pt_active = (pt->runtime.pt_orig) ? pt->runtime.pt_orig : pt;
+ /* do boundbox check first */
+ if ((!ELEM(V2D_IS_CLIPPED, pc1[0], pc1[1])) && BLI_rcti_isect_pt(rect, pc1[0], pc1[1])) {
+ /* only check if point is inside */
+ int mval_i[2];
+ round_v2i_v2fl(mval_i, gso->mval);
+ if (len_v2v2_int(mval_i, pc1) <= radius) {
+ /* apply operation to this point */
+ if (pt_active != NULL) {
+ gp_save_selected_point(gso, gps_active, 0, pc1);
+ }
+ }
+ }
+ }
+ else {
+ /* Loop over the points in the stroke, checking for intersections
+ * - an intersection means that we touched the stroke
+ */
+ for (i = 0; (i + 1) < gps->totpoints; i++) {
+ /* Get points to work with */
+ pt1 = gps->points + i;
+ pt2 = gps->points + i + 1;
+
+ /* Skip if neither one is selected
+ * (and we are only allowed to edit/consider selected points) */
+ if ((GPENCIL_ANY_VERTEX_MASK(gso->mask)) && (GPENCIL_VERTEX_MODE(gso->gpd))) {
+ if (!(pt1->flag & GP_SPOINT_SELECT) && !(pt2->flag & GP_SPOINT_SELECT)) {
+ include_last = false;
+ continue;
+ }
+ }
+
+ bGPDspoint npt;
+ gp_point_to_parent_space(pt1, diff_mat, &npt);
+ gp_point_to_xy(gsc, gps, &npt, &pc1[0], &pc1[1]);
+
+ gp_point_to_parent_space(pt2, diff_mat, &npt);
+ gp_point_to_xy(gsc, gps, &npt, &pc2[0], &pc2[1]);
+
+ /* Check that point segment of the boundbox of the selection stroke */
+ if (((!ELEM(V2D_IS_CLIPPED, pc1[0], pc1[1])) && BLI_rcti_isect_pt(rect, pc1[0], pc1[1])) ||
+ ((!ELEM(V2D_IS_CLIPPED, pc2[0], pc2[1])) && BLI_rcti_isect_pt(rect, pc2[0], pc2[1]))) {
+ /* Check if point segment of stroke had anything to do with
+ * brush region (either within stroke painted, or on its lines)
+ * - this assumes that linewidth is irrelevant
+ */
+ if (gp_stroke_inside_circle(
+ gso->mval, gso->mval_prev, radius, pc1[0], pc1[1], pc2[0], pc2[1])) {
+
+ /* To each point individually... */
+ pt = &gps->points[i];
+ pt_active = (pt->runtime.pt_orig) ? pt->runtime.pt_orig : pt;
+ index = (pt->runtime.pt_orig) ? pt->runtime.idx_orig : i;
+ if (pt_active != NULL) {
+ gp_save_selected_point(gso, gps_active, index, pc1);
+ }
+
+ /* Only do the second point if this is the last segment,
+ * and it is unlikely that the point will get handled
+ * otherwise.
+ *
+ * NOTE: There is a small risk here that the second point wasn't really
+ * actually in-range. In that case, it only got in because
+ * the line linking the points was!
+ */
+ if (i + 1 == gps->totpoints - 1) {
+ pt = &gps->points[i + 1];
+ pt_active = (pt->runtime.pt_orig) ? pt->runtime.pt_orig : pt;
+ index = (pt->runtime.pt_orig) ? pt->runtime.idx_orig : i + 1;
+ if (pt_active != NULL) {
+ gp_save_selected_point(gso, gps_active, index, pc2);
+ include_last = false;
+ }
+ }
+ else {
+ include_last = true;
+ }
+ }
+ else if (include_last) {
+ /* This case is for cases where for whatever reason the second vert (1st here)
+ * doesn't get included because the whole edge isn't in bounds,
+ * but it would've qualified since it did with the previous step
+ * (but wasn't added then, to avoid double-ups).
+ */
+ pt = &gps->points[i];
+ pt_active = (pt->runtime.pt_orig) ? pt->runtime.pt_orig : pt;
+ index = (pt->runtime.pt_orig) ? pt->runtime.idx_orig : i;
+ if (pt_active != NULL) {
+ gp_save_selected_point(gso, gps_active, index, pc1);
+
+ include_last = false;
+ }
+ }
+ }
+ }
+ }
+}
+
+/* Apply vertex paint brushes to strokes in the given frame. */
+static bool gp_vertexpaint_brush_do_frame(bContext *C,
+ tGP_BrushVertexpaintData *gso,
+ bGPDlayer *gpl,
+ bGPDframe *gpf,
+ const float diff_mat[4][4])
+{
+ Object *ob = CTX_data_active_object(C);
+ char tool = ob->mode == OB_MODE_VERTEX_GPENCIL ? gso->brush->gpencil_vertex_tool :
+ gso->brush->gpencil_tool;
+ const int radius = (gso->brush->flag & GP_BRUSH_USE_PRESSURE) ?
+ gso->brush->size * gso->pressure :
+ gso->brush->size;
+ tGP_Selected *selected = NULL;
+ int i;
+
+ /*---------------------------------------------------------------------
+ * First step: select the points affected. This step is required to have
+ * all selected points before apply the effect, because it could be
+ * required to average data.
+ *--------------------------------------------------------------------- */
+ LISTBASE_FOREACH (bGPDstroke *, gps, &gpf->strokes) {
+ /* Skip strokes that are invalid for current view. */
+ if (ED_gpencil_stroke_can_use(C, gps) == false) {
+ continue;
+ }
+ /* Check if the color is editable. */
+ if (ED_gpencil_stroke_color_use(ob, gpl, gps) == false) {
+ continue;
+ }
+
+ /* Check points below the brush. */
+ gp_vertexpaint_select_stroke(gso, gps, diff_mat);
+ }
+
+ /* For Average tool, need calculate the average resulting color from all colors
+ * under the brush. */
+ float average_color[3] = {0};
+ int totcol = 0;
+ if ((tool == GPVERTEX_TOOL_AVERAGE) && (gso->pbuffer_used > 0)) {
+ for (i = 0; i < gso->pbuffer_used; i++) {
+ selected = &gso->pbuffer[i];
+ bGPDstroke *gps = selected->gps;
+ bGPDspoint *pt = &gps->points[selected->pt_index];
+
+ /* Add stroke mix color (only if used). */
+ if (pt->vert_color[3] > 0.0f) {
+ add_v3_v3(average_color, pt->vert_color);
+ totcol++;
+ }
+
+ /* If Fill color mix, add to average. */
+ if (gps->vert_color_fill[3] > 0.0f) {
+ add_v3_v3(average_color, gps->vert_color_fill);
+ totcol++;
+ }
+ }
+
+ /* Get average. */
+ if (totcol > 0) {
+ mul_v3_fl(average_color, (1.0f / (float)totcol));
+ }
+ }
+
+ /*---------------------------------------------------------------------
+ * Second step: Apply effect.
+ *--------------------------------------------------------------------- */
+ bool changed = false;
+ for (i = 0; i < gso->pbuffer_used; i++) {
+ changed = true;
+ selected = &gso->pbuffer[i];
+
+ switch (tool) {
+ case GPAINT_TOOL_TINT:
+ case GPVERTEX_TOOL_DRAW: {
+ brush_tint_apply(gso, selected->gps, selected->pt_index, radius, selected->pc);
+ changed |= true;
+ break;
+ }
+ case GPVERTEX_TOOL_BLUR: {
+ brush_blur_apply(gso, selected->gps, selected->pt_index, radius, selected->pc);
+ changed |= true;
+ break;
+ }
+ case GPVERTEX_TOOL_AVERAGE: {
+ brush_average_apply(
+ gso, selected->gps, selected->pt_index, radius, selected->pc, average_color);
+ changed |= true;
+ break;
+ }
+ case GPVERTEX_TOOL_SMEAR: {
+ brush_smear_apply(gso, selected->gps, selected->pt_index, selected);
+ changed |= true;
+ break;
+ }
+ case GPVERTEX_TOOL_REPLACE: {
+ brush_replace_apply(gso, selected->gps, selected->pt_index);
+ changed |= true;
+ break;
+ }
+
+ default:
+ printf("ERROR: Unknown type of GPencil Vertex Paint brush\n");
+ break;
+ }
+ }
+ /* Clear the selected array, but keep the memory allocation.*/
+ gso->pbuffer = gpencil_select_buffer_ensure(
+ gso->pbuffer, &gso->pbuffer_size, &gso->pbuffer_used, true);
+
+ return changed;
+}
+
+/* Apply brush effect to all layers. */
+static bool gp_vertexpaint_brush_apply_to_layers(bContext *C, tGP_BrushVertexpaintData *gso)
+{
+ ToolSettings *ts = CTX_data_tool_settings(C);
+ Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C);
+ Object *obact = gso->object;
+ bool changed = false;
+
+ Object *ob_eval = (Object *)DEG_get_evaluated_id(depsgraph, &obact->id);
+ bGPdata *gpd = (bGPdata *)ob_eval->data;
+
+ /* Find visible strokes, and perform operations on those if hit */
+ LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd->layers) {
+ /* If locked or no active frame, don't do anything. */
+ if ((!BKE_gpencil_layer_is_editable(gpl)) || (gpl->actframe == NULL)) {
+ continue;
+ }
+
+ /* calculate difference matrix */
+ float diff_mat[4][4];
+ BKE_gpencil_parent_matrix_get(depsgraph, obact, gpl, diff_mat);
+
+ /* Active Frame or MultiFrame? */
+ if (gso->is_multiframe) {
+ /* init multiframe falloff options */
+ int f_init = 0;
+ int f_end = 0;
+
+ if (gso->use_multiframe_falloff) {
+ BKE_gpencil_frame_range_selected(gpl, &f_init, &f_end);
+ }
+
+ LISTBASE_FOREACH (bGPDframe *, gpf, &gpl->frames) {
+ /* Always do active frame; Otherwise, only include selected frames */
+ if ((gpf == gpl->actframe) || (gpf->flag & GP_FRAME_SELECT)) {
+ /* compute multiframe falloff factor */
+ if (gso->use_multiframe_falloff) {
+ /* Faloff depends on distance to active frame (relative to the overall frame range)
+ */
+ gso->mf_falloff = BKE_gpencil_multiframe_falloff_calc(
+ gpf, gpl->actframe->framenum, f_init, f_end, ts->gp_sculpt.cur_falloff);
+ }
+ else {
+ /* No falloff */
+ gso->mf_falloff = 1.0f;
+ }
+
+ /* affect strokes in this frame */
+ changed |= gp_vertexpaint_brush_do_frame(C, gso, gpl, gpf, diff_mat);
+ }
+ }
+ }
+ else {
+ /* Apply to active frame's strokes */
+ if (gpl->actframe != NULL) {
+ gso->mf_falloff = 1.0f;
+ changed |= gp_vertexpaint_brush_do_frame(C, gso, gpl, gpl->actframe, diff_mat);
+ }
+ }
+ }
+
+ return changed;
+}
+
+/* Calculate settings for applying brush */
+static void gp_vertexpaint_brush_apply(bContext *C, wmOperator *op, PointerRNA *itemptr)
+{
+ tGP_BrushVertexpaintData *gso = op->customdata;
+ Brush *brush = gso->brush;
+ const int radius = ((brush->flag & GP_BRUSH_USE_PRESSURE) ? gso->brush->size * gso->pressure :
+ gso->brush->size);
+ float mousef[2];
+ int mouse[2];
+ bool changed = false;
+
+ /* Get latest mouse coordinates */
+ RNA_float_get_array(itemptr, "mouse", mousef);
+ gso->mval[0] = mouse[0] = (int)(mousef[0]);
+ gso->mval[1] = mouse[1] = (int)(mousef[1]);
+
+ gso->pressure = RNA_float_get(itemptr, "pressure");
+
+ if (RNA_boolean_get(itemptr, "pen_flip")) {
+ gso->flag |= GP_VERTEX_FLAG_INVERT;
+ }
+ else {
+ gso->flag &= ~GP_VERTEX_FLAG_INVERT;
+ }
+
+ /* Store coordinates as reference, if operator just started running */
+ if (gso->first) {
+ gso->mval_prev[0] = gso->mval[0];
+ gso->mval_prev[1] = gso->mval[1];
+ gso->pressure_prev = gso->pressure;
+ }
+
+ /* Update brush_rect, so that it represents the bounding rectangle of brush. */
+ gso->brush_rect.xmin = mouse[0] - radius;
+ gso->brush_rect.ymin = mouse[1] - radius;
+ gso->brush_rect.xmax = mouse[0] + radius;
+ gso->brush_rect.ymax = mouse[1] + radius;
+
+ /* Calc 2D direction vector and relative angle. */
+ brush_calc_dvec_2d(gso);
+
+ /* Calc grid for smear tool. */
+ gp_grid_cells_init(gso);
+
+ changed = gp_vertexpaint_brush_apply_to_layers(C, gso);
+
+ /* Updates */
+ if (changed) {
+ DEG_id_tag_update(&gso->gpd->id, ID_RECALC_GEOMETRY);
+ WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL);
+ }
+
+ /* Store values for next step */
+ gso->mval_prev[0] = gso->mval[0];
+ gso->mval_prev[1] = gso->mval[1];
+ gso->pressure_prev = gso->pressure;
+ gso->first = false;
+}
+
+/* Running --------------------------------------------- */
+
+/* helper - a record stroke, and apply paint event */
+static void gp_vertexpaint_brush_apply_event(bContext *C, wmOperator *op, const wmEvent *event)
+{
+ tGP_BrushVertexpaintData *gso = op->customdata;
+ PointerRNA itemptr;
+ float mouse[2];
+
+ mouse[0] = event->mval[0] + 1;
+ mouse[1] = event->mval[1] + 1;
+
+ /* fill in stroke */
+ RNA_collection_add(op->ptr, "stroke", &itemptr);
+
+ RNA_float_set_array(&itemptr, "mouse", mouse);
+ RNA_boolean_set(&itemptr, "pen_flip", event->ctrl != false);
+ RNA_boolean_set(&itemptr, "is_start", gso->first);
+
+ /* Handle pressure sensitivity (which is supplied by tablets). */
+ float pressure = event->tablet.pressure;
+ CLAMP(pressure, 0.0f, 1.0f);
+ RNA_float_set(&itemptr, "pressure", pressure);
+
+ /* apply */
+ gp_vertexpaint_brush_apply(C, op, &itemptr);
+}
+
+/* reapply */
+static int gp_vertexpaint_brush_exec(bContext *C, wmOperator *op)
+{
+ if (!gp_vertexpaint_brush_init(C, op)) {
+ return OPERATOR_CANCELLED;
+ }
+
+ RNA_BEGIN (op->ptr, itemptr, "stroke") {
+ gp_vertexpaint_brush_apply(C, op, &itemptr);
+ }
+ RNA_END;
+
+ gp_vertexpaint_brush_exit(C, op);
+
+ return OPERATOR_FINISHED;
+}
+
+/* start modal painting */
+static int gp_vertexpaint_brush_invoke(bContext *C, wmOperator *op, const wmEvent *event)
+{
+ tGP_BrushVertexpaintData *gso = NULL;
+ const bool is_modal = RNA_boolean_get(op->ptr, "wait_for_input");
+ const bool is_playing = ED_screen_animation_playing(CTX_wm_manager(C)) != NULL;
+
+ /* the operator cannot work while play animation */
+ if (is_playing) {
+ BKE_report(op->reports, RPT_ERROR, "Cannot Paint while play animation");
+
+ return OPERATOR_CANCELLED;
+ }
+
+ /* init painting data */
+ if (!gp_vertexpaint_brush_init(C, op)) {
+ return OPERATOR_CANCELLED;
+ }
+
+ gso = op->customdata;
+
+ /* register modal handler */
+ WM_event_add_modal_handler(C, op);
+
+ /* start drawing immediately? */
+ if (is_modal == false) {
+ ARegion *region = CTX_wm_region(C);
+
+ /* apply first dab... */
+ gso->is_painting = true;
+ gp_vertexpaint_brush_apply_event(C, op, event);
+
+ /* redraw view with feedback */
+ ED_region_tag_redraw(region);
+ }
+
+ return OPERATOR_RUNNING_MODAL;
+}
+
+/* painting - handle events */
+static int gp_vertexpaint_brush_modal(bContext *C, wmOperator *op, const wmEvent *event)
+{
+ tGP_BrushVertexpaintData *gso = op->customdata;
+ const bool is_modal = RNA_boolean_get(op->ptr, "wait_for_input");
+ bool redraw_region = false;
+ bool redraw_toolsettings = false;
+
+ /* The operator can be in 2 states: Painting and Idling */
+ if (gso->is_painting) {
+ /* Painting */
+ switch (event->type) {
+ /* Mouse Move = Apply somewhere else */
+ case MOUSEMOVE:
+ case INBETWEEN_MOUSEMOVE:
+ /* apply brush effect at new position */
+ gp_vertexpaint_brush_apply_event(C, op, event);
+
+ /* force redraw, so that the cursor will at least be valid */
+ redraw_region = true;
+ break;
+
+ /* Painting mbut release = Stop painting (back to idle) */
+ case LEFTMOUSE:
+ if (is_modal) {
+ /* go back to idling... */
+ gso->is_painting = false;
+ }
+ else {
+ /* end painting, since we're not modal */
+ gso->is_painting = false;
+
+ gp_vertexpaint_brush_exit(C, op);
+ return OPERATOR_FINISHED;
+ }
+ break;
+
+ /* Abort painting if any of the usual things are tried */
+ case MIDDLEMOUSE:
+ case RIGHTMOUSE:
+ case ESCKEY:
+ gp_vertexpaint_brush_exit(C, op);
+ return OPERATOR_FINISHED;
+ }
+ }
+ else {
+ /* Idling */
+ BLI_assert(is_modal == true);
+
+ switch (event->type) {
+ /* Painting mbut press = Start painting (switch to painting state) */
+ case LEFTMOUSE:
+ /* do initial "click" apply */
+ gso->is_painting = true;
+ gso->first = true;
+
+ gp_vertexpaint_brush_apply_event(C, op, event);
+ break;
+
+ /* Exit modal operator, based on the "standard" ops */
+ case RIGHTMOUSE:
+ case ESCKEY:
+ gp_vertexpaint_brush_exit(C, op);
+ return OPERATOR_FINISHED;
+
+ /* MMB is often used for view manipulations */
+ case MIDDLEMOUSE:
+ return OPERATOR_PASS_THROUGH;
+
+ /* Mouse movements should update the brush cursor - Just redraw the active region */
+ case MOUSEMOVE:
+ case INBETWEEN_MOUSEMOVE:
+ redraw_region = true;
+ break;
+
+ /* Change Frame - Allowed */
+ case LEFTARROWKEY:
+ case RIGHTARROWKEY:
+ case UPARROWKEY:
+ case DOWNARROWKEY:
+ return OPERATOR_PASS_THROUGH;
+
+ /* Camera/View Gizmo's - Allowed */
+ /* (See rationale in gpencil_paint.c -> gpencil_draw_modal()) */
+ case PAD0:
+ case PAD1:
+ case PAD2:
+ case PAD3:
+ case PAD4:
+ case PAD5:
+ case PAD6:
+ case PAD7:
+ case PAD8:
+ case PAD9:
+ return OPERATOR_PASS_THROUGH;
+
+ /* Unhandled event */
+ default:
+ break;
+ }
+ }
+
+ /* Redraw region? */
+ if (redraw_region) {
+ ED_region_tag_redraw(CTX_wm_region(C));
+ }
+
+ /* Redraw toolsettings (brush settings)? */
+ if (redraw_toolsettings) {
+ DEG_id_tag_update(&gso->gpd->id, ID_RECALC_GEOMETRY);
+ WM_event_add_notifier(C, NC_SCENE | ND_TOOLSETTINGS, NULL);
+ }
+
+ return OPERATOR_RUNNING_MODAL;
+}
+
+void GPENCIL_OT_vertex_paint(wmOperatorType *ot)
+{
+ /* identifiers */
+ ot->name = "Stroke Vertex Paint";
+ ot->idname = "GPENCIL_OT_vertex_paint";
+ ot->description = "Paint stroke points with a color";
+
+ /* api callbacks */
+ ot->exec = gp_vertexpaint_brush_exec;
+ ot->invoke = gp_vertexpaint_brush_invoke;
+ ot->modal = gp_vertexpaint_brush_modal;
+ ot->cancel = gp_vertexpaint_brush_exit;
+ ot->poll = gp_vertexpaint_brush_poll;
+
+ /* flags */
+ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO | OPTYPE_BLOCKING;
+
+ /* properties */
+ PropertyRNA *prop;
+ prop = RNA_def_collection_runtime(ot->srna, "stroke", &RNA_OperatorStrokeElement, "Stroke", "");
+ RNA_def_property_flag(prop, PROP_HIDDEN | PROP_SKIP_SAVE);
+
+ prop = RNA_def_boolean(ot->srna, "wait_for_input", true, "Wait for Input", "");
+ RNA_def_property_flag(prop, PROP_HIDDEN | PROP_SKIP_SAVE);
+}