GPv3: Fill tool to generate strokes in empty areas

Implementation of the GPv2 Fill tool in Grease Pencil 3.

This tool creates new strokes where the user clicks, by rendering
strokes into an image and then performing flood-fill and boundary
search operations.

This is a minimal first part, several features of the GPv2 tool are
still missing (gap filling methods, smoothing, dilate/erode).

Co-authored-by: Falk David <falk@blender.org>
Pull Request: https://projects.blender.org/blender/blender/pulls/120693
This commit is contained in:
Lukas Tönne 2024-05-15 13:36:06 +02:00
parent ed2be4c89f
commit 7a3cd753ba
14 changed files with 2440 additions and 48 deletions

@ -191,6 +191,7 @@ def generate(context, space_type, *, use_fallback_keys=True, use_reset=True):
'SCULPT_GPENCIL': "gpencil_sculpt_tool",
'WEIGHT_GPENCIL': "gpencil_weight_tool",
'SCULPT_CURVES': "curves_sculpt_tool",
'PAINT_GREASE_PENCIL': "gpencil_tool",
}.get(mode, None)
else:
attr = None

@ -4567,16 +4567,6 @@ def km_grease_pencil_paint_mode(_params):
)
items.extend([
("brush.scale_size", {"type": 'LEFT_BRACKET', "value": 'PRESS', "repeat": True},
{"properties": [("scalar", 0.9)]}),
("brush.scale_size", {"type": 'RIGHT_BRACKET', "value": 'PRESS', "repeat": True},
{"properties": [("scalar", 1.0 / 0.9)]}),
("grease_pencil.brush_stroke", {"type": 'LEFTMOUSE', "value": 'PRESS'}, None),
("grease_pencil.brush_stroke", {"type": 'LEFTMOUSE', "value": 'PRESS', "ctrl": True},
{"properties": [("mode", 'INVERT')]}),
("grease_pencil.brush_stroke", {"type": 'LEFTMOUSE', "value": 'PRESS', "shift": True},
{"properties": [("mode", 'SMOOTH')]}),
*_template_paint_radial_control("gpencil_paint"),
# Active material
op_menu("VIEW3D_MT_greasepencil_material_active", {"type": 'U', "value": 'PRESS'}),
# Active layer
@ -4594,6 +4584,30 @@ def km_grease_pencil_paint_mode(_params):
return keymap
def km_grease_pencil_brush_stroke(_params):
items = []
keymap = (
"Grease Pencil Brush Stroke",
{"space_type": 'EMPTY', "region_type": 'WINDOW'},
{"items": items},
)
items.extend([
("brush.scale_size", {"type": 'LEFT_BRACKET', "value": 'PRESS', "repeat": True},
{"properties": [("scalar", 0.9)]}),
("brush.scale_size", {"type": 'RIGHT_BRACKET', "value": 'PRESS', "repeat": True},
{"properties": [("scalar", 1.0 / 0.9)]}),
("grease_pencil.brush_stroke", {"type": 'LEFTMOUSE', "value": 'PRESS'}, None),
("grease_pencil.brush_stroke", {"type": 'LEFTMOUSE', "value": 'PRESS', "ctrl": True},
{"properties": [("mode", 'INVERT')]}),
("grease_pencil.brush_stroke", {"type": 'LEFTMOUSE', "value": 'PRESS', "shift": True},
{"properties": [("mode", 'SMOOTH')]}),
*_template_paint_radial_control("gpencil_paint"),
])
return keymap
def km_grease_pencil_edit_mode(params):
items = []
keymap = (
@ -4764,6 +4778,57 @@ def km_grease_pencil_weight_paint(params):
return keymap
# Grease Pencil v3 Fill Tool.
def km_grease_pencil_fill_tool(_params):
items = []
keymap = (
"Grease Pencil Fill Tool",
{"space_type": 'EMPTY', "region_type": 'WINDOW'},
{"items": items},
)
items.extend([
# Fill operator.
("grease_pencil.fill", {"type": 'LEFTMOUSE', "value": 'PRESS'},
{"properties": [("on_back", False)]}),
("grease_pencil.fill", {"type": 'LEFTMOUSE', "value": 'PRESS', "ctrl": True},
{"properties": [("on_back", False)]}),
# Use regular stroke operator when holding shift to draw lines.
("grease_pencil.brush_stroke", {"type": 'LEFTMOUSE', "value": 'PRESS', "shift": True},
None),
])
return keymap
def km_grease_pencil_fill_tool_modal_map(params):
items = []
keymap = (
"Fill Tool Modal Map",
{"space_type": 'EMPTY', "region_type": 'WINDOW', "modal": True},
{"items": items},
)
items.extend([
("CANCEL", {"type": 'ESC', "value": 'PRESS', "any": True}, None),
("CANCEL", {"type": 'RIGHTMOUSE', "value": 'PRESS'}, None),
("CONFIRM", {"type": 'LEFTMOUSE', "value": 'PRESS', "any": True}, None),
("GAP_CLOSURE_MODE", {"type": 'S', "value": 'PRESS'}, None),
("EXTENSIONS_LENGTHEN", {"type": 'PAGE_UP', "value": 'PRESS', "repeat": True}, None),
("EXTENSIONS_LENGTHEN", {"type": 'WHEELUPMOUSE', "value": 'PRESS'}, None),
("EXTENSIONS_SHORTEN", {"type": 'PAGE_DOWN', "value": 'PRESS', "repeat": True}, None),
("EXTENSIONS_SHORTEN", {"type": 'WHEELDOWNMOUSE', "value": 'PRESS'}, None),
("EXTENSIONS_DRAG", {"type": 'MIDDLEMOUSE', "value": 'PRESS'}, None),
("EXTENSIONS_COLLIDE", {"type": 'D', "value": 'PRESS'}, None),
("INVERT", {"type": 'LEFT_CTRL', "value": 'PRESS', "any": True}, None),
("INVERT", {"type": 'RIGHT_CTRL', "value": 'PRESS', "any": True}, None),
("PRECISION", {"type": 'LEFT_SHIFT', "value": 'PRESS', "any": True}, None),
("PRECISION", {"type": 'RIGHT_SHIFT', "value": 'PRESS', "any": True}, None),
])
return keymap
# ------------------------------------------------------------------------------
# Object/Pose Modes
@ -8926,6 +8991,8 @@ def generate_keymaps(params=None):
km_grease_pencil_edit_mode(params),
km_grease_pencil_sculpt_mode(params),
km_grease_pencil_weight_paint(params),
km_grease_pencil_brush_stroke(params),
km_grease_pencil_fill_tool(params),
# Object mode.
km_object_mode(params),
km_object_non_modal(params),
@ -8977,6 +9044,7 @@ def generate_keymaps(params=None):
km_curve_pen_modal_map(params),
km_node_link_modal_map(params),
km_grease_pencil_primitive_tool_modal_map(params),
km_grease_pencil_fill_tool_modal_map(params),
# Gizmos.
km_generic_gizmo(params),

@ -1452,41 +1452,49 @@ def brush_basic_grease_pencil_paint_settings(layout, context, brush, *, compact=
grease_pencil_tool = brush.gpencil_tool
size = "size"
size_owner = ups if ups.use_unified_size else brush
if size_owner.use_locked_size == 'SCENE':
size = "unprojected_radius"
if grease_pencil_tool in {'DRAW', 'ERASE', 'TINT'} or tool.idname in {
"builtin.arc",
"builtin.curve",
"builtin.line",
"builtin.box",
"builtin.circle",
"builtin.polyline",
}:
size = "size"
size_owner = ups if ups.use_unified_size else brush
if size_owner.use_locked_size == 'SCENE':
size = "unprojected_radius"
UnifiedPaintPanel.prop_unified(
layout,
context,
brush,
size,
unified_name="use_unified_size",
pressure_name="use_pressure_size",
text="Radius",
slider=True,
header=compact,
)
UnifiedPaintPanel.prop_unified(
layout,
context,
brush,
size,
unified_name="use_unified_size",
pressure_name="use_pressure_size",
text="Radius",
slider=True,
header=compact,
)
if brush.use_pressure_size and not compact:
col = layout.column()
col.template_curve_mapping(gp_settings, "curve_sensitivity", brush=True, use_negative_slope=True)
if brush.use_pressure_size and not compact:
col = layout.column()
col.template_curve_mapping(gp_settings, "curve_sensitivity", brush=True, use_negative_slope=True)
UnifiedPaintPanel.prop_unified(
layout,
context,
brush,
"strength",
unified_name="use_unified_strength",
pressure_name="use_pressure_strength",
slider=True,
header=compact,
)
UnifiedPaintPanel.prop_unified(
layout,
context,
brush,
"strength",
unified_name="use_unified_strength",
pressure_name="use_pressure_strength",
slider=True,
header=compact,
)
if brush.use_pressure_strength and not compact:
col = layout.column()
col.template_curve_mapping(gp_settings, "curve_strength", brush=True, use_negative_slope=True)
if brush.use_pressure_strength and not compact:
col = layout.column()
col.template_curve_mapping(gp_settings, "curve_strength", brush=True, use_negative_slope=True)
# Brush details
if tool.idname in {
@ -1520,6 +1528,30 @@ def brush_basic_grease_pencil_paint_settings(layout, context, brush, *, compact=
if settings.use_thickness_curve:
# Pressure curve.
layout.template_curve_mapping(settings, "thickness_primitive_curve", brush=True)
elif grease_pencil_tool == 'DRAW':
layout.prop(gp_settings, "active_smooth_factor")
row = layout.row(align=True)
if compact:
row.prop(gp_settings, "caps_type", text="", expand=True)
else:
row.prop(gp_settings, "caps_type", text="Caps Type")
elif brush.gpencil_tool == 'FILL':
use_property_split_prev = layout.use_property_split
if compact:
row = layout.row(align=True)
row.prop(gp_settings, "fill_direction", text="", expand=True)
else:
layout.use_property_split = False
row = layout.row(align=True)
row.prop(gp_settings, "fill_direction", expand=True)
row = layout.row(align=True)
row.prop(gp_settings, "fill_factor")
row = layout.row(align=True)
row.prop(gp_settings, "dilate")
row = layout.row(align=True)
row.prop(brush, "size", text="Thickness")
layout.use_property_split = use_property_split_prev
elif grease_pencil_tool == 'ERASE':
layout.prop(gp_settings, "eraser_mode", expand=True)
if gp_settings.eraser_mode == 'HARD':

@ -1875,6 +1875,15 @@ class _defs_grease_pencil_paint:
data_block='DRAW',
)
@ToolDef.from_fn
def fill():
return dict(
idname="builtin_brush.Fill",
label="Fill",
icon="brush.gpencil_draw.fill",
data_block='FILL',
)
@ToolDef.from_fn
def erase():
return dict(
@ -3501,6 +3510,7 @@ class VIEW3D_PT_tools_active(ToolSelectPanelHelper, Panel):
_defs_view3d_generic.cursor,
None,
_defs_grease_pencil_paint.draw,
_defs_grease_pencil_paint.fill,
_defs_grease_pencil_paint.erase,
_defs_grease_pencil_paint.cutter,
_defs_grease_pencil_paint.tint,

@ -702,7 +702,7 @@ class _draw_tool_settings_context_mode:
grease_pencil_tool = brush.gpencil_tool
if grease_pencil_tool == 'DRAW':
if grease_pencil_tool in {'DRAW', 'FILL'}:
from bl_ui.properties_paint_common import (
brush_basic__draw_color_selector,
)

@ -2586,7 +2586,7 @@ class VIEW3D_PT_tools_grease_pencil_v3_brush_settings(Panel, View3DPanel, Grease
class VIEW3D_PT_tools_grease_pencil_v3_brush_advanced(View3DPanel, Panel):
bl_context = ".greasepencil_paint"
bl_label = "Advanced"
bl_parent_id = "VIEW3D_PT_tools_grease_pencil_brush_settings"
bl_parent_id = "VIEW3D_PT_tools_grease_pencil_v3_brush_settings"
bl_category = "Tool"
bl_options = {'DEFAULT_CLOSED'}
bl_ui_units_x = 13
@ -2642,7 +2642,6 @@ class VIEW3D_PT_tools_grease_pencil_v3_brush_advanced(View3DPanel, Panel):
if ma and ma.grease_pencil.mode == 'LINE':
subcol.enabled = False
subcol.prop(gp_settings, "aspect")
elif brush.gpencil_tool == 'FILL':
row = col.row(align=True)
row.prop(gp_settings, "fill_draw_mode", text="Boundary", text_ctxt=i18n_contexts.id_gpencil)

@ -27,6 +27,7 @@ set(SRC
intern/grease_pencil_edit.cc
intern/grease_pencil_frames.cc
intern/grease_pencil_geom.cc
intern/grease_pencil_image_render.cc
intern/grease_pencil_layers.cc
intern/grease_pencil_material.cc
intern/grease_pencil_ops.cc

@ -0,0 +1,419 @@
/* SPDX-FileCopyrightText: 2024 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BKE_grease_pencil.hh"
#include "BLI_color.hh"
#include "BLI_math_matrix.hh"
#include "BKE_attribute.hh"
#include "BKE_camera.h"
#include "BKE_curves.hh"
#include "BKE_image.h"
#include "DNA_gpencil_legacy_types.h"
#include "DNA_material_types.h"
#include "DNA_object_types.h"
#include "DNA_scene_types.h"
#include "DNA_view3d_types.h"
#include "ED_grease_pencil.hh"
#include "ED_view3d.hh"
#include "IMB_imbuf.hh"
#include "IMB_imbuf_types.hh"
#include "GPU_debug.hh"
#include "GPU_framebuffer.hh"
#include "GPU_immediate.hh"
#include "GPU_matrix.hh"
#include "GPU_shader_shared.hh"
#include "GPU_state.hh"
#include "GPU_texture.hh"
#include "GPU_vertex_format.hh"
namespace blender::ed::greasepencil::image_render {
/* Enable GPU debug capture (needs WITH_RENDERDOC option). */
constexpr const bool enable_debug_gpu_capture = true;
RegionViewData region_init(ARegion &region, const int2 &win_size)
{
const RegionViewData data = {int2{region.winx, region.winy}, region.winrct};
/* Resize region. */
region.winrct.xmin = 0;
region.winrct.ymin = 0;
region.winrct.xmax = win_size.x;
region.winrct.ymax = win_size.y;
region.winx = short(win_size.x);
region.winy = short(win_size.y);
return data;
}
void region_reset(ARegion &region, const RegionViewData &data)
{
region.winx = data.region_winsize.x;
region.winy = data.region_winsize.y;
region.winrct = data.region_winrct;
}
GPUOffScreen *image_render_begin(const int2 &win_size)
{
if (enable_debug_gpu_capture) {
GPU_debug_capture_begin("Grease Pencil Image Render");
}
char err_out[256] = "unknown";
GPUOffScreen *offscreen = GPU_offscreen_create(
win_size.x, win_size.y, true, GPU_RGBA8, GPU_TEXTURE_USAGE_HOST_READ, err_out);
if (offscreen == nullptr) {
return nullptr;
}
GPU_offscreen_bind(offscreen, true);
GPU_matrix_push_projection();
GPU_matrix_identity_projection_set();
GPU_matrix_push();
GPU_matrix_identity_set();
GPU_clear_color(0.0f, 0.0f, 0.0f, 0.0f);
GPU_clear_depth(1.0f);
return offscreen;
}
Image *image_render_end(Main &bmain, GPUOffScreen *buffer)
{
const int2 win_size = {GPU_offscreen_width(buffer), GPU_offscreen_height(buffer)};
const uint imb_flag = IB_rect;
ImBuf *ibuf = IMB_allocImBuf(win_size.x, win_size.y, 32, imb_flag);
if (ibuf->float_buffer.data) {
GPU_offscreen_read_color(buffer, GPU_DATA_FLOAT, ibuf->float_buffer.data);
}
else if (ibuf->byte_buffer.data) {
GPU_offscreen_read_color(buffer, GPU_DATA_UBYTE, ibuf->byte_buffer.data);
}
if (ibuf->float_buffer.data && ibuf->byte_buffer.data) {
IMB_rect_from_float(ibuf);
}
Image *ima = BKE_image_add_from_imbuf(&bmain, ibuf, "Grease Pencil Fill");
ima->id.tag |= LIB_TAG_DOIT;
BKE_image_release_ibuf(ima, ibuf, nullptr);
/* Switch back to regular frame-buffer. */
GPU_offscreen_unbind(buffer, true);
GPU_offscreen_free(buffer);
if (enable_debug_gpu_capture) {
GPU_debug_capture_end();
}
return ima;
}
void set_viewmat(const ViewContext &view_context,
const Scene &scene,
const int2 &win_size,
const float2 &zoom,
const float2 &offset)
{
rctf viewplane;
float clip_start, clip_end;
const bool is_ortho = ED_view3d_viewplane_get(view_context.depsgraph,
view_context.v3d,
view_context.rv3d,
win_size.x,
win_size.y,
&viewplane,
&clip_start,
&clip_end,
nullptr);
/* Rescale `viewplane` to fit all strokes. */
const float2 view_min = float2(viewplane.xmin, viewplane.ymin);
const float2 view_max = float2(viewplane.xmax, viewplane.ymax);
const float2 view_extent = view_max - view_min;
const float2 view_center = 0.5f * (view_max + view_min);
const float2 offset_abs = offset * view_extent;
const float2 view_min_new = (view_min - view_center) * zoom + view_center + offset_abs;
const float2 view_max_new = (view_max - view_center) * zoom + view_center + offset_abs;
viewplane.xmin = view_min_new.x;
viewplane.ymin = view_min_new.y;
viewplane.xmax = view_max_new.x;
viewplane.ymax = view_max_new.y;
float4x4 winmat;
if (is_ortho) {
orthographic_m4(winmat.ptr(),
viewplane.xmin,
viewplane.xmax,
viewplane.ymin,
viewplane.ymax,
-clip_end,
clip_end);
}
else {
perspective_m4(winmat.ptr(),
viewplane.xmin,
viewplane.xmax,
viewplane.ymin,
viewplane.ymax,
clip_start,
clip_end);
}
ED_view3d_update_viewmat(view_context.depsgraph,
&scene,
view_context.v3d,
view_context.region,
nullptr,
winmat.ptr(),
nullptr,
true);
GPU_matrix_set(view_context.rv3d->viewmat);
GPU_matrix_projection_set(view_context.rv3d->winmat);
}
void clear_viewmat()
{
GPU_matrix_pop_projection();
GPU_matrix_pop();
}
void draw_dot(const float3 &position, const float point_size, const ColorGeometry4f &color)
{
GPUVertFormat *format = immVertexFormat();
uint attr_pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 3, GPU_FETCH_FLOAT);
uint attr_size = GPU_vertformat_attr_add(format, "size", GPU_COMP_F32, 1, GPU_FETCH_FLOAT);
uint attr_color = GPU_vertformat_attr_add(format, "color", GPU_COMP_F32, 4, GPU_FETCH_FLOAT);
GPU_program_point_size(true);
immBindBuiltinProgram(GPU_SHADER_3D_POINT_VARYING_SIZE_VARYING_COLOR);
immBegin(GPU_PRIM_POINTS, 1);
immAttr1f(attr_size, point_size * M_SQRT2);
immAttr4fv(attr_color, color);
immVertex3fv(attr_pos, position);
immEnd();
immUnbindProgram();
GPU_program_point_size(false);
}
void draw_polyline(const IndexRange indices,
Span<float3> positions,
const VArray<ColorGeometry4f> &colors,
const float4x4 &layer_to_world,
const bool cyclic,
const float line_width)
{
GPUVertFormat *format = immVertexFormat();
const uint attr_pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 3, GPU_FETCH_FLOAT);
const uint attr_color = GPU_vertformat_attr_add(
format, "color", GPU_COMP_F32, 4, GPU_FETCH_FLOAT);
immBindBuiltinProgram(GPU_SHADER_3D_FLAT_COLOR);
GPU_line_width(line_width);
/* If cyclic the curve needs one more vertex. */
const int cyclic_add = (cyclic && indices.size() > 2) ? 1 : 0;
immBeginAtMost(GPU_PRIM_LINE_STRIP, indices.size() + cyclic_add);
for (const int point_i : indices) {
immAttr4fv(attr_color, colors[point_i]);
immVertex3fv(attr_pos, math::transform_point(layer_to_world, positions[point_i]));
}
if (cyclic && indices.size() > 2) {
const int point_i = indices[0];
immAttr4fv(attr_color, colors[point_i]);
immVertex3fv(attr_pos, math::transform_point(layer_to_world, positions[point_i]));
}
immEnd();
immUnbindProgram();
}
static GPUUniformBuf *create_shader_ubo(const RegionView3D &rv3d,
const int2 &win_size,
const Object &object,
const eGPDstroke_Caps cap_start,
const eGPDstroke_Caps cap_end,
const bool is_fill_stroke)
{
GPencilStrokeData data;
copy_v2_v2(data.viewport, float2(win_size));
data.pixsize = rv3d.pixsize;
data.objscale = math::average(float3(object.scale));
/* TODO Was based on the GP_DATA_STROKE_KEEPTHICKNESS flag which is currently not converted. */
data.keep_size = false;
data.pixfactor = 1.0f;
/* X-ray mode always to 3D space to avoid wrong Z-depth calculation (#60051). */
data.xraymode = GP_XRAY_3DSPACE;
data.caps_start = cap_start;
data.caps_end = cap_end;
data.fill_stroke = is_fill_stroke;
return GPU_uniformbuf_create_ex(sizeof(GPencilStrokeData), &data, __func__);
}
void draw_grease_pencil_stroke(const RegionView3D &rv3d,
const int2 &win_size,
const Object &object,
const IndexRange indices,
Span<float3> positions,
const VArray<float> &radii,
const VArray<ColorGeometry4f> &colors,
const float4x4 &layer_to_world,
const bool cyclic,
const eGPDstroke_Caps cap_start,
const eGPDstroke_Caps cap_end,
const bool fill_stroke)
{
if (indices.is_empty()) {
return;
}
GPUVertFormat *format = immVertexFormat();
const uint attr_pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 3, GPU_FETCH_FLOAT);
const uint attr_color = GPU_vertformat_attr_add(
format, "color", GPU_COMP_F32, 4, GPU_FETCH_FLOAT);
const uint attr_thickness = GPU_vertformat_attr_add(
format, "thickness", GPU_COMP_F32, 1, GPU_FETCH_FLOAT);
immBindBuiltinProgram(GPU_SHADER_GPENCIL_STROKE);
GPUUniformBuf *ubo = create_shader_ubo(rv3d, win_size, object, cap_start, cap_end, fill_stroke);
immBindUniformBuf("gpencil_stroke_data", ubo);
/* If cyclic the curve needs one more vertex. */
const int cyclic_add = (cyclic && indices.size() > 2) ? 1 : 0;
immBeginAtMost(GPU_PRIM_LINE_STRIP_ADJ, indices.size() + cyclic_add + 2);
auto draw_point = [&](const int point_i) {
constexpr const float radius_to_pixel_factor =
1.0f / bke::greasepencil::LEGACY_RADIUS_CONVERSION_FACTOR;
const float thickness = radii[point_i] * radius_to_pixel_factor;
constexpr const float min_thickness = 0.05f;
immAttr4fv(attr_color, colors[point_i]);
immAttr1f(attr_thickness, std::max(thickness, min_thickness));
immVertex3fv(attr_pos, math::transform_point(layer_to_world, positions[point_i]));
};
/* First point for adjacency (not drawn). */
if (cyclic && indices.size() > 2) {
draw_point(indices.last() - 1);
}
else {
draw_point(indices.first() + 1);
}
for (const int point_i : indices) {
draw_point(point_i);
}
if (cyclic && indices.size() > 2) {
draw_point(indices.first());
draw_point(indices.first() + 1);
}
/* Last adjacency point (not drawn). */
else {
draw_point(indices.last() - 1);
}
immEnd();
immUnbindProgram();
GPU_uniformbuf_free(ubo);
}
void draw_dots(const IndexRange indices,
Span<float3> positions,
const VArray<float> &radii,
const VArray<ColorGeometry4f> &colors,
const float4x4 &layer_to_world)
{
/* TODO */
UNUSED_VARS(indices, positions, radii, colors, layer_to_world);
}
void draw_grease_pencil_strokes(const RegionView3D &rv3d,
const int2 &win_size,
const Object &object,
const bke::greasepencil::Drawing &drawing,
const IndexMask &strokes_mask,
const VArray<ColorGeometry4f> &colors,
const float4x4 &layer_to_world,
const int mode,
const bool use_xray,
const bool fill_strokes)
{
GPU_program_point_size(true);
/* Do not write to depth (avoid self-occlusion). */
const bool prev_depth_mask = GPU_depth_mask_get();
GPU_depth_mask(false);
const bke::CurvesGeometry &curves = drawing.strokes();
const OffsetIndices points_by_curve = curves.points_by_curve();
const Span<float3> positions = curves.positions();
const bke::AttributeAccessor attributes = curves.attributes();
const VArray<bool> cyclic = curves.cyclic();
const VArray<float> &radii = drawing.radii();
const VArray<int8_t> stroke_start_caps = *attributes.lookup_or_default<int8_t>(
"start_cap", bke::AttrDomain::Curve, GP_STROKE_CAP_ROUND);
const VArray<int8_t> stroke_end_caps = *attributes.lookup_or_default<int8_t>(
"end_cap", bke::AttrDomain::Curve, GP_STROKE_CAP_ROUND);
/* Note: Serial loop without GrainSize, since immediate mode drawing can't happen in worker
* threads, has to be from the main thread. */
strokes_mask.foreach_index([&](const int stroke_i) {
const float stroke_radius = radii[stroke_i];
if (stroke_radius <= 0) {
return;
}
if (!use_xray) {
GPU_depth_test(GPU_DEPTH_LESS_EQUAL);
/* First arg is normally rv3d->dist, but this isn't
* available here and seems to work quite well without. */
GPU_polygon_offset(1.0f, 1.0f);
}
switch (eMaterialGPencilStyle_Mode(mode)) {
case GP_MATERIAL_MODE_LINE:
draw_grease_pencil_stroke(rv3d,
win_size,
object,
points_by_curve[stroke_i],
positions,
radii,
colors,
layer_to_world,
cyclic[stroke_i],
eGPDstroke_Caps(stroke_start_caps[stroke_i]),
eGPDstroke_Caps(stroke_end_caps[stroke_i]),
fill_strokes);
break;
case GP_MATERIAL_MODE_DOT:
case GP_MATERIAL_MODE_SQUARE:
draw_dots(points_by_curve[stroke_i], positions, radii, colors, layer_to_world);
break;
}
if (!use_xray) {
GPU_depth_test(GPU_DEPTH_NONE);
GPU_polygon_offset(0.0f, 0.0f);
}
});
GPU_depth_mask(prev_depth_mask);
GPU_program_point_size(false);
}
} // namespace blender::ed::greasepencil::image_render

@ -7,7 +7,9 @@
*/
#include "BKE_context.hh"
#include "BKE_paint.hh"
#include "DNA_brush_enums.h"
#include "DNA_object_enums.h"
#include "DNA_scene_types.h"
@ -15,6 +17,7 @@
#include "ED_screen.hh"
#include "WM_api.hh"
#include "WM_toolsystem.hh"
#include "WM_types.hh"
#include "RNA_access.hh"
@ -142,6 +145,48 @@ static void keymap_grease_pencil_weight_paint_mode(wmKeyConfig *keyconf)
keymap->poll = grease_pencil_weight_painting_poll;
}
/* Enabled for all tools except the fill tool. */
static bool keymap_grease_pencil_brush_stroke_poll(bContext *C)
{
if (!grease_pencil_painting_poll(C)) {
return false;
}
if (!WM_toolsystem_active_tool_is_brush(C)) {
return false;
}
ToolSettings *ts = CTX_data_tool_settings(C);
Brush *brush = BKE_paint_brush(&ts->gp_paint->paint);
return brush && brush->gpencil_settings && brush->gpencil_tool != GPAINT_TOOL_FILL;
}
static void keymap_grease_pencil_brush_stroke(wmKeyConfig *keyconf)
{
wmKeyMap *keymap = WM_keymap_ensure(
keyconf, "Grease Pencil Brush Stroke", SPACE_EMPTY, RGN_TYPE_WINDOW);
keymap->poll = keymap_grease_pencil_brush_stroke_poll;
}
/* Enabled only for the fill tool. */
static bool keymap_grease_pencil_fill_tool_poll(bContext *C)
{
if (!grease_pencil_painting_poll(C)) {
return false;
}
if (!WM_toolsystem_active_tool_is_brush(C)) {
return false;
}
ToolSettings *ts = CTX_data_tool_settings(C);
Brush *brush = BKE_paint_brush(&ts->gp_paint->paint);
return brush && brush->gpencil_settings && brush->gpencil_tool == GPAINT_TOOL_FILL;
}
static void keymap_grease_pencil_fill_tool(wmKeyConfig *keyconf)
{
wmKeyMap *keymap = WM_keymap_ensure(
keyconf, "Grease Pencil Fill Tool", SPACE_EMPTY, RGN_TYPE_WINDOW);
keymap->poll = keymap_grease_pencil_fill_tool_poll;
}
} // namespace blender::ed::greasepencil
void ED_operatortypes_grease_pencil()
@ -198,5 +243,8 @@ void ED_keymap_grease_pencil(wmKeyConfig *keyconf)
keymap_grease_pencil_paint_mode(keyconf);
keymap_grease_pencil_sculpt_mode(keyconf);
keymap_grease_pencil_weight_paint_mode(keyconf);
keymap_grease_pencil_brush_stroke(keyconf);
keymap_grease_pencil_fill_tool(keyconf);
ED_primitivetool_modal_keymap(keyconf);
ED_filltool_modal_keymap(keyconf);
}

@ -25,6 +25,7 @@ struct Object;
struct KeyframeEditData;
struct wmKeyConfig;
struct wmOperator;
struct GPUOffScreen;
struct ToolSettings;
struct Scene;
struct UndoType;
@ -59,6 +60,7 @@ void ED_operatortypes_grease_pencil_weight_paint();
void ED_operatormacros_grease_pencil();
void ED_keymap_grease_pencil(wmKeyConfig *keyconf);
void ED_primitivetool_modal_keymap(wmKeyConfig *keyconf);
void ED_filltool_modal_keymap(wmKeyConfig *keyconf);
void GREASE_PENCIL_OT_stroke_cutter(wmOperatorType *ot);
@ -400,4 +402,139 @@ IndexRange clipboard_paste_strokes(Main &bmain,
bke::greasepencil::Drawing &drawing,
bool paste_back);
/**
* Method used by the Fill tool to fit the render buffer to strokes.
*/
enum FillToolFitMethod {
/* Use the current view projection unchanged. */
None,
/* Fit all strokes into the view (may change pixel size). */
FitToView,
};
/**
* Fill tool for generating strokes in empty areas.
*
* This uses an approximate render of strokes and boundaries,
* then fills the image starting from the mouse position.
* The outlines of the filled pixel areas are returned as curves.
*
* \param layer: The layer containing the new stroke, used for reprojecting from images.
* \param boundary_layers: Layers that are purely for boundaries, regular strokes are not rendered.
* \param src_drawings: Drawings to include as boundary strokes.
* \param invert: Construct boundary around empty areas instead.
* \param fill_point: Point from which to start the bucket fill.
* \param fit_method: View fitting method to include all strokes.
* \param stroke_material_index: Material index to use for the new strokes.
* \param keep_images: Keep the image data block after generating curves.
*/
bke::CurvesGeometry fill_strokes(const ViewContext &view_context,
const Brush &brush,
const Scene &scene,
const bke::greasepencil::Layer &layer,
const VArray<bool> &boundary_layers,
Span<DrawingInfo> src_drawings,
bool invert,
const float2 &fill_point,
FillToolFitMethod fit_method,
int stroke_material_index,
bool keep_images);
namespace image_render {
/** Region size to restore after rendering. */
struct RegionViewData {
int2 region_winsize;
rcti region_winrct;
};
/**
* Set up region to match the render buffer size.
*/
RegionViewData region_init(ARegion &region, const int2 &win_size);
/**
* Restore original region size after rendering.
*/
void region_reset(ARegion &region, const RegionViewData &data);
/**
* Create and offscreen buffer for rendering.
*/
GPUOffScreen *image_render_begin(const int2 &win_size);
/**
* Finish rendering and convert the offscreen buffer into an image.
*/
Image *image_render_end(Main &bmain, GPUOffScreen *buffer);
/**
* Set up the view matrix for world space rendering.
*
* \param win_size: Size of the render window.
* \param zoom: Zoom factor to render a smaller or larger part of the view.
* \param offset: Offset of the view relative to the overall size.
*/
void set_viewmat(const ViewContext &view_context,
const Scene &scene,
const int2 &win_size,
const float2 &zoom,
const float2 &offset);
/**
* Reset the view matrix for screen space rendering.
*/
void clear_viewmat();
/**
* Draw a dot with a given size and color.
*/
void draw_dot(const float3 &position, float point_size, const ColorGeometry4f &color);
/**
* Draw a poly line from points.
*/
void draw_polyline(IndexRange indices,
Span<float3> positions,
const VArray<ColorGeometry4f> &colors,
const float4x4 &layer_to_world,
bool cyclic,
float line_width);
/**
* Draw a curve using the Grease Pencil stroke shader.
*/
void draw_grease_pencil_stroke(const RegionView3D &rv3d,
const int2 &win_size,
const Object &object,
IndexRange indices,
Span<float3> positions,
const VArray<float> &radii,
const VArray<ColorGeometry4f> &colors,
const float4x4 &layer_to_world,
bool cyclic,
eGPDstroke_Caps cap_start,
eGPDstroke_Caps cap_end,
bool fill_stroke);
/**
* Draw points as quads or circles.
*/
void draw_dots(IndexRange indices,
Span<float3> positions,
const VArray<float> &radii,
const VArray<ColorGeometry4f> &colors,
const float4x4 &layer_to_world);
/**
* Draw curves geometry.
* \param mode: Mode of \a eMaterialGPencilStyle_Mode.
*/
void draw_grease_pencil_strokes(const RegionView3D &rv3d,
const int2 &win_size,
const Object &object,
const bke::greasepencil::Drawing &drawing,
const IndexMask &strokes_mask,
const VArray<ColorGeometry4f> &colors,
const float4x4 &layer_to_world,
int mode,
bool use_xray,
bool fill_strokes);
} // namespace image_render
} // namespace blender::ed::greasepencil

@ -42,6 +42,7 @@ set(SRC
curves_sculpt_snake_hook.cc
grease_pencil_draw_ops.cc
grease_pencil_erase.cc
grease_pencil_fill.cc
grease_pencil_paint.cc
grease_pencil_sculpt_clone.cc
grease_pencil_sculpt_common.cc

@ -2,35 +2,60 @@
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "ANIM_keyframing.hh"
#include "BKE_brush.hh"
#include "BKE_colortools.hh"
#include "BKE_context.hh"
#include "BKE_curves.hh"
#include "BKE_deform.hh"
#include "BKE_geometry_set.hh"
#include "BKE_grease_pencil.hh"
#include "BKE_material.h"
#include "BKE_object_deform.h"
#include "BKE_paint.hh"
#include "BKE_report.hh"
#include "BKE_screen.hh"
#include "DEG_depsgraph_query.hh"
#include "BLI_assert.h"
#include "BLI_math_vector.hh"
#include "BLI_rect.h"
#include "BLI_string.h"
#include "DNA_brush_types.h"
#include "DNA_grease_pencil_types.h"
#include "DNA_scene_types.h"
#include "DNA_view3d_types.h"
#include "DNA_windowmanager_types.h"
#include "DEG_depsgraph_query.hh"
#include "GEO_join_geometries.hh"
#include "ED_grease_pencil.hh"
#include "ED_image.hh"
#include "ED_object.hh"
#include "ED_screen.hh"
#include "ED_view3d.hh"
#include "ANIM_keyframing.hh"
#include "MEM_guardedalloc.h"
#include "RNA_access.hh"
#include "RNA_define.hh"
#include "UI_interface.hh"
#include "BLT_translation.hh"
#include "WM_api.hh"
#include "WM_message.hh"
#include "WM_toolsystem.hh"
#include "WM_types.hh"
#include "curves_sculpt_intern.hh"
#include "grease_pencil_intern.hh"
#include "paint_intern.hh"
#include "wm_event_types.hh"
namespace blender::ed::sculpt_paint {
@ -123,7 +148,8 @@ static GreasePencilStrokeOperation *grease_pencil_brush_stroke_operation(bContex
case GPAINT_TOOL_ERASE:
return greasepencil::new_erase_operation().release();
case GPAINT_TOOL_FILL:
return nullptr;
/* Fill tool keymap uses the paint operator as alternative mode. */
return greasepencil::new_paint_operation().release();
case GPAINT_TOOL_TINT:
return greasepencil::new_tint_operation().release();
}
@ -464,6 +490,564 @@ static void GREASE_PENCIL_OT_weight_brush_stroke(wmOperatorType *ot)
/** \} */
/* -------------------------------------------------------------------- */
/** \name Bucket Fill Operator
* \{ */
struct GreasePencilFillOpData {
blender::bke::greasepencil::Layer &layer;
/* Brush properties, some of these are modified by modal keys. */
int flag;
eGP_FillExtendModes fill_extend_mode;
float fill_extend_fac;
int material_index;
/* Mouse position where fill was initialized */
float2 fill_mouse_pos;
/* Extension lines mode is enabled (middle mouse button). */
bool is_extension_mode = false;
/* Mouse position where the extension mode was enabled. */
float2 extension_mouse_pos;
/* Toggle inverse filling. */
bool invert = false;
/* Toggle precision mode. */
bool precision = false;
~GreasePencilFillOpData()
{
// TODO Remove drawing handler.
// if (this->draw_handle_3d) {
// ED_region_draw_cb_exit(this->region_type, this->draw_handle_3d);
// }
}
static GreasePencilFillOpData from_context(bContext &C,
blender::bke::greasepencil::Layer &layer,
const int material_index)
{
using blender::bke::greasepencil::Layer;
const ToolSettings &ts = *CTX_data_tool_settings(&C);
const Brush &brush = *BKE_paint_brush(&ts.gp_paint->paint);
/* Enable custom drawing handlers to show help lines */
const bool do_extend = (brush.gpencil_settings->flag & GP_BRUSH_FILL_SHOW_EXTENDLINES);
const bool help_lines = do_extend ||
(brush.gpencil_settings->flag & GP_BRUSH_FILL_SHOW_HELPLINES);
if (help_lines) {
// TODO register 3D view overlay to render help lines.
// this->region_type = region.type;
// this->draw_handle_3d = ED_region_draw_cb_activate(
// region.type, grease_pencil_fill_draw_3d, tgpf, REGION_DRAW_POST_VIEW);
}
return {layer,
brush.gpencil_settings->flag,
eGP_FillExtendModes(brush.gpencil_settings->fill_extend_mode),
brush.gpencil_settings->fill_extend_fac,
material_index};
}
};
static void grease_pencil_fill_status_indicators(bContext &C,
const GreasePencilFillOpData &op_data)
{
const bool is_extend = (op_data.fill_extend_mode == GP_FILL_EMODE_EXTEND);
const bool use_stroke_collide = (op_data.flag & GP_BRUSH_FILL_STROKE_COLLIDE) != 0;
const float fill_extend_fac = op_data.fill_extend_fac;
char status_str[UI_MAX_DRAW_STR];
BLI_snprintf(status_str,
sizeof(status_str),
IFACE_("Fill: ESC/RMB cancel, LMB Fill, Shift Draw on Back, MMB Adjust Extend, S: "
"Switch Mode, D: "
"Stroke Collision | %s %s (%.3f)"),
(is_extend) ? IFACE_("Extend") : IFACE_("Radius"),
(is_extend && use_stroke_collide) ? IFACE_("Stroke: ON") : IFACE_("Stroke: OFF"),
fill_extend_fac);
ED_workspace_status_text(&C, status_str);
}
static void grease_pencil_update_extend(bContext &C, const GreasePencilFillOpData &op_data)
{
if (op_data.fill_extend_fac > 0.0f) {
// TODO update extension lines.
}
grease_pencil_fill_status_indicators(C, op_data);
WM_event_add_notifier(&C, NC_GPENCIL | NA_EDITED, nullptr);
}
/* Layer mode defines layers where only marked boundary strokes are used. */
static VArray<bool> get_fill_boundary_layers(const GreasePencil &grease_pencil,
eGP_FillLayerModes fill_layer_mode)
{
BLI_assert(grease_pencil.has_active_layer());
const IndexRange all_layers = grease_pencil.layers().index_range();
const int active_layer_index = *grease_pencil.get_layer_index(*grease_pencil.get_active_layer());
switch (fill_layer_mode) {
case GP_FILL_GPLMODE_ACTIVE:
return VArray<bool>::ForFunc(all_layers.size(), [active_layer_index](const int index) {
return index != active_layer_index;
});
case GP_FILL_GPLMODE_ABOVE:
return VArray<bool>::ForFunc(all_layers.size(), [active_layer_index](const int index) {
return index != active_layer_index + 1;
});
case GP_FILL_GPLMODE_BELOW:
return VArray<bool>::ForFunc(all_layers.size(), [active_layer_index](const int index) {
return index != active_layer_index - 1;
});
case GP_FILL_GPLMODE_ALL_ABOVE:
return VArray<bool>::ForFunc(all_layers.size(), [active_layer_index](const int index) {
return index <= active_layer_index;
});
case GP_FILL_GPLMODE_ALL_BELOW:
return VArray<bool>::ForFunc(all_layers.size(), [active_layer_index](const int index) {
return index >= active_layer_index;
});
case GP_FILL_GPLMODE_VISIBLE:
return VArray<bool>::ForFunc(all_layers.size(), [grease_pencil](const int index) {
return !grease_pencil.layers()[index]->is_visible();
});
}
return {};
}
/* Array of visible drawings to use as borders for generating a stroke in the editable drawing on
* the active layer. This is provided for every frame in the multi-frame edit range. */
struct FillToolTargetInfo {
ed::greasepencil::MutableDrawingInfo target;
Vector<ed::greasepencil::DrawingInfo> sources;
};
static Vector<FillToolTargetInfo> ensure_editable_drawings(const Scene &scene,
GreasePencil &grease_pencil,
bke::greasepencil::Layer &target_layer)
{
using namespace bke::greasepencil;
using ed::greasepencil::DrawingInfo;
using ed::greasepencil::MutableDrawingInfo;
const ToolSettings *toolsettings = scene.toolsettings;
const bool use_multi_frame_editing = (toolsettings->gpencil_flags &
GP_USE_MULTI_FRAME_EDITING) != 0;
const bool use_autokey = blender::animrig::is_autokey_on(&scene);
const bool use_duplicate_frame = (scene.toolsettings->gpencil_flags & GP_TOOL_FLAG_RETAIN_LAST);
const int target_layer_index = *grease_pencil.get_layer_index(target_layer);
VectorSet<int> target_frames;
/* Add drawing on the current frame. */
target_frames.add(scene.r.cfra);
/* Multi-frame edit: Add drawing on frames that are selected in any layer. */
if (use_multi_frame_editing) {
for (const Layer *layer : grease_pencil.layers()) {
for (const auto [frame_number, frame] : layer->frames().items()) {
if (frame.is_selected()) {
target_frames.add(frame_number);
}
}
}
}
/* Create new drawings when autokey is enabled. */
if (use_autokey) {
for (const int frame_number : target_frames) {
if (!target_layer.frames().contains(frame_number)) {
if (use_duplicate_frame) {
grease_pencil.insert_duplicate_frame(
target_layer, *target_layer.frame_key_at(frame_number), frame_number, false);
}
else {
grease_pencil.insert_frame(target_layer, frame_number);
}
}
}
}
Vector<FillToolTargetInfo> drawings;
for (const int frame_number : target_frames) {
if (Drawing *target_drawing = grease_pencil.get_editable_drawing_at(target_layer,
frame_number))
{
MutableDrawingInfo target = {*target_drawing, target_layer_index, frame_number, 1.0f};
Vector<DrawingInfo> sources;
for (const Layer *source_layer : grease_pencil.layers()) {
if (const Drawing *source_drawing = grease_pencil.get_drawing_at(*source_layer,
frame_number))
{
const int source_layer_index = *grease_pencil.get_layer_index(*source_layer);
sources.append({*source_drawing, source_layer_index, frame_number, 0});
}
}
drawings.append({std::move(target), std::move(sources)});
}
}
return drawings;
}
static bool grease_pencil_apply_fill(bContext &C, wmOperator &op, const wmEvent &event)
{
using bke::greasepencil::Layer;
using ed::greasepencil::DrawingInfo;
using ed::greasepencil::MutableDrawingInfo;
constexpr const ed::greasepencil::FillToolFitMethod fit_method =
ed::greasepencil::FillToolFitMethod::FitToView;
/* Debug setting: keep image data blocks for inspection. */
constexpr const bool keep_images = false;
ARegion &region = *CTX_wm_region(&C);
/* Perform bounds check. */
const bool in_bounds = BLI_rcti_isect_pt_v(&region.winrct, event.xy);
if (!in_bounds) {
return false;
}
wmWindow &win = *CTX_wm_window(&C);
const ViewContext view_context = ED_view3d_viewcontext_init(&C, CTX_data_depsgraph_pointer(&C));
const Scene &scene = *CTX_data_scene(&C);
Object &object = *CTX_data_active_object(&C);
GreasePencil &grease_pencil = *static_cast<GreasePencil *>(object.data);
auto &op_data = *static_cast<GreasePencilFillOpData *>(op.customdata);
const ToolSettings &ts = *CTX_data_tool_settings(&C);
const Brush &brush = *BKE_paint_brush(&ts.gp_paint->paint);
const float2 mouse_position = float2(event.mval);
if (!grease_pencil.has_active_layer()) {
return false;
}
/* Add drawings in the active layer if autokey is enabled. */
Vector<FillToolTargetInfo> target_drawings = ensure_editable_drawings(
scene, grease_pencil, *grease_pencil.get_active_layer());
const VArray<bool> boundary_layers = get_fill_boundary_layers(
grease_pencil, eGP_FillLayerModes(brush.gpencil_settings->fill_layer_mode));
for (const FillToolTargetInfo &info : target_drawings) {
const Layer &layer = *grease_pencil.layers()[info.target.layer_index];
bke::CurvesGeometry fill_curves = fill_strokes(view_context,
brush,
scene,
layer,
boundary_layers,
info.sources,
op_data.invert,
mouse_position,
fit_method,
op_data.material_index,
keep_images);
Curves *dst_curves_id = curves_new_nomain(std::move(info.target.drawing.strokes_for_write()));
Curves *fill_curves_id = curves_new_nomain(fill_curves);
Array<bke::GeometrySet> geometry_sets = {bke::GeometrySet::from_curves(dst_curves_id),
bke::GeometrySet::from_curves(fill_curves_id)};
bke::GeometrySet joined_geometry_set = geometry::join_geometries(geometry_sets, {});
bke::CurvesGeometry joined_curves =
(joined_geometry_set.has_curves() ?
std::move(joined_geometry_set.get_curves_for_write()->geometry.wrap()) :
bke::CurvesGeometry());
info.target.drawing.strokes_for_write() = std::move(joined_curves);
info.target.drawing.tag_topology_changed();
}
WM_cursor_modal_restore(&win);
/* Save extend value for next operation. */
brush.gpencil_settings->fill_extend_fac = op_data.fill_extend_fac;
return true;
}
static bool grease_pencil_fill_init(bContext &C, wmOperator &op)
{
using blender::bke::greasepencil::Layer;
Main &bmain = *CTX_data_main(&C);
Scene &scene = *CTX_data_scene(&C);
Object &ob = *CTX_data_active_object(&C);
GreasePencil &grease_pencil = *static_cast<GreasePencil *>(ob.data);
Paint &paint = scene.toolsettings->gp_paint->paint;
Brush &brush = *BKE_paint_brush(&paint);
Layer *layer = grease_pencil.get_active_layer();
/* Cannot paint in locked layer. */
if (layer && layer->is_locked()) {
return false;
}
if (layer == nullptr) {
layer = &grease_pencil.add_layer("GP_Layer");
}
if (brush.gpencil_settings == nullptr) {
BKE_brush_init_gpencil_settings(&brush);
}
BKE_curvemapping_init(brush.gpencil_settings->curve_sensitivity);
BKE_curvemapping_init(brush.gpencil_settings->curve_strength);
BKE_curvemapping_init(brush.gpencil_settings->curve_jitter);
BKE_curvemapping_init(brush.gpencil_settings->curve_rand_pressure);
BKE_curvemapping_init(brush.gpencil_settings->curve_rand_strength);
BKE_curvemapping_init(brush.gpencil_settings->curve_rand_uv);
BKE_curvemapping_init(brush.gpencil_settings->curve_rand_hue);
BKE_curvemapping_init(brush.gpencil_settings->curve_rand_saturation);
BKE_curvemapping_init(brush.gpencil_settings->curve_rand_value);
Material *material = BKE_grease_pencil_object_material_ensure_from_active_input_brush(
&bmain, &ob, &brush);
const int material_index = BKE_object_material_index_get(&ob, material);
op.customdata = MEM_new<GreasePencilFillOpData>(
__func__, GreasePencilFillOpData::from_context(C, *layer, material_index));
return true;
}
static void grease_pencil_fill_exit(bContext &C, wmOperator &op)
{
Object &ob = *CTX_data_active_object(&C);
GreasePencil &grease_pencil = *static_cast<GreasePencil *>(ob.data);
WM_cursor_modal_restore(CTX_wm_window(&C));
if (op.customdata) {
MEM_delete(static_cast<GreasePencilFillOpData *>(op.customdata));
op.customdata = nullptr;
}
/* Clear status message area. */
ED_workspace_status_text(&C, nullptr);
DEG_id_tag_update(&grease_pencil.id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY);
WM_main_add_notifier(NC_GEOM | ND_DATA, nullptr);
WM_event_add_notifier(&C, NC_GPENCIL | ND_DATA | NA_EDITED, nullptr);
}
static int grease_pencil_fill_invoke(bContext *C, wmOperator *op, const wmEvent * /*event*/)
{
ToolSettings &ts = *CTX_data_tool_settings(C);
Brush &brush = *BKE_paint_brush(&ts.gp_paint->paint);
Object &ob = *CTX_data_active_object(C);
GreasePencil &grease_pencil = *static_cast<GreasePencil *>(ob.data);
/* Fill tool needs a material (cannot use default material). */
if ((brush.gpencil_settings->flag & GP_BRUSH_MATERIAL_PINNED) &&
brush.gpencil_settings->material == nullptr)
{
BKE_report(op->reports, RPT_ERROR, "Fill tool needs active material");
return OPERATOR_CANCELLED;
}
if (BKE_object_material_get(&ob, ob.actcol) == nullptr) {
BKE_report(op->reports, RPT_ERROR, "Fill tool needs active material");
return OPERATOR_CANCELLED;
}
if (!grease_pencil_fill_init(*C, *op)) {
grease_pencil_fill_exit(*C, *op);
return OPERATOR_CANCELLED;
}
const auto &op_data = *static_cast<GreasePencilFillOpData *>(op->customdata);
WM_cursor_modal_set(CTX_wm_window(C), WM_CURSOR_PAINT_BRUSH);
grease_pencil_fill_status_indicators(*C, op_data);
DEG_id_tag_update(&grease_pencil.id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY);
WM_event_add_notifier(C, NC_GPENCIL | NA_EDITED, nullptr);
/* Add a modal handler for this operator. */
WM_event_add_modal_handler(C, op);
return OPERATOR_RUNNING_MODAL;
}
enum class FillToolModalKey : int8_t {
Cancel = 1,
Confirm,
GapClosureMode,
ExtensionsLengthen,
ExtensionsShorten,
ExtensionsDrag,
ExtensionsCollide,
Invert,
Precision,
};
static int grease_pencil_fill_event_modal_map(bContext *C, wmOperator *op, const wmEvent *event)
{
auto &op_data = *static_cast<GreasePencilFillOpData *>(op->customdata);
const bool show_extend = (op_data.flag & GP_BRUSH_FILL_SHOW_EXTENDLINES);
// const bool help_lines = (((op_data.flag & GP_BRUSH_FILL_SHOW_HELPLINES) || show_extend) &&
// !is_inverted);
// const bool extend_lines = (op_data.fill_extend_fac > 0.0f);
const float extension_delta = (op_data.precision ? 0.01f : 0.1f);
switch (event->val) {
case int(FillToolModalKey::Cancel):
return OPERATOR_CANCELLED;
case int(FillToolModalKey::Confirm): {
/* Ignore in extension mode. */
if (op_data.is_extension_mode) {
break;
}
op_data.fill_mouse_pos = float2(event->mval);
return (grease_pencil_apply_fill(*C, *op, *event) ? OPERATOR_FINISHED : OPERATOR_CANCELLED);
}
case int(FillToolModalKey::GapClosureMode):
if (show_extend && event->val == KM_PRESS) {
/* Toggle mode. */
if (op_data.fill_extend_mode == GP_FILL_EMODE_EXTEND) {
op_data.fill_extend_mode = GP_FILL_EMODE_RADIUS;
}
else {
op_data.fill_extend_mode = GP_FILL_EMODE_EXTEND;
}
grease_pencil_update_extend(*C, op_data);
}
break;
case int(FillToolModalKey::ExtensionsLengthen):
op_data.fill_extend_fac = std::max(op_data.fill_extend_fac - extension_delta, 0.0f);
grease_pencil_update_extend(*C, op_data);
break;
case int(FillToolModalKey::ExtensionsShorten):
op_data.fill_extend_fac = std::min(op_data.fill_extend_fac + extension_delta, 10.0f);
grease_pencil_update_extend(*C, op_data);
break;
case int(FillToolModalKey::ExtensionsDrag): {
if (event->val == KM_PRESS) {
/* Consider initial offset as zero position. */
op_data.is_extension_mode = true;
/* TODO This is the GPv2 logic and it's weird. Should be reconsidered, for now use the
* same method. */
const float2 base_pos = float2(event->mval);
constexpr const float gap = 300.0f;
op_data.extension_mouse_pos = (math::distance(base_pos, op_data.fill_mouse_pos) >= gap ?
base_pos :
base_pos - float2(gap, 0));
WM_cursor_set(CTX_wm_window(C), WM_CURSOR_EW_ARROW);
}
if (event->val == KM_RELEASE) {
WM_cursor_modal_set(CTX_wm_window(C), WM_CURSOR_PAINT_BRUSH);
op_data.is_extension_mode = false;
}
/* Update cursor line. */
WM_main_add_notifier(NC_GEOM | ND_DATA, nullptr);
WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, nullptr);
break;
}
case int(FillToolModalKey::ExtensionsCollide):
if (show_extend && event->val == KM_PRESS) {
op_data.flag ^= GP_BRUSH_FILL_STROKE_COLLIDE;
grease_pencil_update_extend(*C, op_data);
}
break;
case int(FillToolModalKey::Invert):
op_data.invert = !op_data.invert;
break;
case int(FillToolModalKey::Precision):
op_data.precision = !op_data.precision;
break;
default:
BLI_assert_unreachable();
break;
}
return OPERATOR_RUNNING_MODAL;
}
static int grease_pencil_fill_modal(bContext *C, wmOperator *op, const wmEvent *event)
{
const RegionView3D &rv3d = *CTX_wm_region_view3d(C);
auto &op_data = *static_cast<GreasePencilFillOpData *>(op->customdata);
int estate = OPERATOR_RUNNING_MODAL;
switch (event->type) {
case EVT_MODAL_MAP:
estate = grease_pencil_fill_event_modal_map(C, op, event);
break;
case MOUSEMOVE: {
if (!op_data.is_extension_mode) {
break;
}
const Object &ob = *CTX_data_active_object(C);
const float pixel_size = ED_view3d_pixel_size(&rv3d, ob.loc);
const float2 mouse_pos = float2(event->mval);
const float initial_dist = math::distance(op_data.extension_mouse_pos,
op_data.fill_mouse_pos);
const float current_dist = math::distance(mouse_pos, op_data.fill_mouse_pos);
float delta = (current_dist - initial_dist) * pixel_size * 0.5f;
op_data.fill_extend_fac = std::clamp(op_data.fill_extend_fac + delta, 0.0f, 10.0f);
/* Update cursor line and extend lines. */
WM_main_add_notifier(NC_GEOM | ND_DATA, nullptr);
WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, nullptr);
grease_pencil_update_extend(*C, op_data);
break;
}
default:
break;
}
/* Process last operations before exiting. */
switch (estate) {
case OPERATOR_FINISHED:
grease_pencil_fill_exit(*C, *op);
WM_event_add_notifier(C, NC_GPENCIL | NA_EDITED, nullptr);
break;
case OPERATOR_CANCELLED:
grease_pencil_fill_exit(*C, *op);
break;
default:
break;
}
return estate;
}
static void grease_pencil_fill_cancel(bContext *C, wmOperator *op)
{
grease_pencil_fill_exit(*C, *op);
}
static void GREASE_PENCIL_OT_fill(wmOperatorType *ot)
{
PropertyRNA *prop;
ot->name = "Grease Pencil Fill";
ot->idname = "GREASE_PENCIL_OT_fill";
ot->description = "Fill with color the shape formed by strokes";
ot->poll = ed::greasepencil::grease_pencil_painting_poll;
ot->invoke = grease_pencil_fill_invoke;
ot->modal = grease_pencil_fill_modal;
ot->cancel = grease_pencil_fill_cancel;
ot->flag = OPTYPE_UNDO | OPTYPE_REGISTER;
prop = RNA_def_boolean(ot->srna, "on_back", false, "Draw on Back", "Send new stroke to back");
RNA_def_property_flag(prop, PROP_SKIP_SAVE);
}
/** \} */
} // namespace blender::ed::sculpt_paint
/* -------------------------------------------------------------------- */
@ -476,6 +1060,49 @@ void ED_operatortypes_grease_pencil_draw()
WM_operatortype_append(GREASE_PENCIL_OT_brush_stroke);
WM_operatortype_append(GREASE_PENCIL_OT_sculpt_paint);
WM_operatortype_append(GREASE_PENCIL_OT_weight_brush_stroke);
WM_operatortype_append(GREASE_PENCIL_OT_fill);
}
void ED_filltool_modal_keymap(wmKeyConfig *keyconf)
{
using namespace blender::ed::greasepencil;
using blender::ed::sculpt_paint::FillToolModalKey;
static const EnumPropertyItem modal_items[] = {
{int(FillToolModalKey::Cancel), "CANCEL", 0, "Cancel", ""},
{int(FillToolModalKey::Confirm), "CONFIRM", 0, "Confirm", ""},
{int(FillToolModalKey::GapClosureMode), "GAP_CLOSURE_MODE", 0, "Gap Closure Mode", ""},
{int(FillToolModalKey::ExtensionsLengthen),
"EXTENSIONS_LENGTHEN",
0,
"Length Extensions",
""},
{int(FillToolModalKey::ExtensionsShorten),
"EXTENSIONS_SHORTEN",
0,
"Shorten Extensions",
""},
{int(FillToolModalKey::ExtensionsDrag), "EXTENSIONS_DRAG", 0, "Drag Extensions", ""},
{int(FillToolModalKey::ExtensionsCollide),
"EXTENSIONS_COLLIDE",
0,
"Collide Extensions",
""},
{int(FillToolModalKey::Invert), "INVERT", 0, "Invert", ""},
{int(FillToolModalKey::Precision), "PRECISION", 0, "Precision", ""},
{0, nullptr, 0, nullptr, nullptr},
};
wmKeyMap *keymap = WM_modalkeymap_find(keyconf, "Fill Tool Modal Map");
/* This function is called for each space-type, only needs to add map once. */
if (keymap && keymap->modal_items) {
return;
}
keymap = WM_modalkeymap_ensure(keyconf, "Fill Tool Modal Map", modal_items);
WM_modalkeymap_assign(keymap, "GREASE_PENCIL_OT_fill");
}
/** \} */

File diff suppressed because it is too large Load Diff

@ -418,6 +418,14 @@ static void view3d_main_region_init(wmWindowManager *wm, ARegion *region)
wm->defaultconf, "Grease Pencil Weight Paint", SPACE_EMPTY, RGN_TYPE_WINDOW);
WM_event_add_keymap_handler(&region->handlers, keymap);
keymap = WM_keymap_ensure(
wm->defaultconf, "Grease Pencil Brush Stroke", SPACE_EMPTY, RGN_TYPE_WINDOW);
WM_event_add_keymap_handler(&region->handlers, keymap);
keymap = WM_keymap_ensure(
wm->defaultconf, "Grease Pencil Fill Tool", SPACE_EMPTY, RGN_TYPE_WINDOW);
WM_event_add_keymap_handler(&region->handlers, keymap);
/* Edit-font key-map swallows almost all (because of text input). */
keymap = WM_keymap_ensure(wm->defaultconf, "Font", SPACE_EMPTY, RGN_TYPE_WINDOW);
WM_event_add_keymap_handler(&region->handlers, keymap);