diff --git a/scripts/presets/keyconfig/keymap_data/blender_default.py b/scripts/presets/keyconfig/keymap_data/blender_default.py index 077d78b368f..d2f5083759d 100644 --- a/scripts/presets/keyconfig/keymap_data/blender_default.py +++ b/scripts/presets/keyconfig/keymap_data/blender_default.py @@ -4841,7 +4841,33 @@ def km_grease_pencil_weight_paint(params): return keymap +def km_grease_pencil_vertex_paint(params): + items = [] + keymap = ( + "Grease Pencil Vertex Paint", + {"space_type": 'EMPTY', "region_type": 'WINDOW'}, + {"items": items} + ) + + items.extend([ + # Paint vertex + ("grease_pencil.vertex_brush_stroke", {"type": 'LEFTMOUSE', "value": 'PRESS'}, None), + ("grease_pencil.vertex_brush_stroke", {"type": 'LEFTMOUSE', "value": 'PRESS', "ctrl": True}, + {"properties": [("mode", 'INVERT')]}), + # Increase/Decrease brush size + ("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)]}), + # Radial controls + *_template_paint_radial_control("gpencil_vertex_paint"), + ]) + + return keymap + # Grease Pencil v3 Fill Tool. + + def km_grease_pencil_fill_tool(_params): items = [] keymap = ( @@ -9147,6 +9173,7 @@ 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_vertex_paint(params), km_grease_pencil_brush_stroke(params), km_grease_pencil_fill_tool(params), # Object mode. diff --git a/scripts/startup/bl_ui/properties_grease_pencil_common.py b/scripts/startup/bl_ui/properties_grease_pencil_common.py index eea305b32a0..03ce43ace74 100644 --- a/scripts/startup/bl_ui/properties_grease_pencil_common.py +++ b/scripts/startup/bl_ui/properties_grease_pencil_common.py @@ -93,7 +93,7 @@ class GreasePencilDisplayPanel: settings = tool_settings.gpencil_sculpt_paint elif context.mode == 'WEIGHT_GPENCIL' or context.mode == 'WEIGHT_GREASE_PENCIL': settings = tool_settings.gpencil_weight_paint - elif context.mode == 'VERTEX_GPENCIL': + elif context.mode == 'VERTEX_GPENCIL' or context.mode == 'VERTEX_GREASE_PENCIL': settings = tool_settings.gpencil_vertex_paint brush = settings.brush if brush: @@ -111,7 +111,7 @@ class GreasePencilDisplayPanel: settings = tool_settings.gpencil_sculpt_paint elif context.mode == 'WEIGHT_GPENCIL' or context.mode == 'WEIGHT_GREASE_PENCIL': settings = tool_settings.gpencil_weight_paint - elif context.mode == 'VERTEX_GPENCIL': + elif context.mode == 'VERTEX_GPENCIL' or context.mode == 'VERTEX_GREASE_PENCIL': settings = tool_settings.gpencil_vertex_paint brush = settings.brush gp_settings = brush.gpencil_settings @@ -146,7 +146,7 @@ class GreasePencilDisplayPanel: col.prop(brush, "cursor_color_add", text="Cursor Color") - elif ob.mode == 'VERTEX_GPENCIL': + elif ob.mode == 'VERTEX_GPENCIL' or ob.mode == 'VERTEX_GREASE_PENCIL': row = layout.row(align=True) row.prop(settings, "show_brush", text="Display Cursor") diff --git a/scripts/startup/bl_ui/properties_paint_common.py b/scripts/startup/bl_ui/properties_paint_common.py index 58ad25497d5..8da6a2e771d 100644 --- a/scripts/startup/bl_ui/properties_paint_common.py +++ b/scripts/startup/bl_ui/properties_paint_common.py @@ -162,6 +162,8 @@ class UnifiedPaintPanel: return tool_settings.gpencil_sculpt_paint elif mode == 'WEIGHT_GREASE_PENCIL': return tool_settings.gpencil_weight_paint + elif mode == 'VERTEX_GREASE_PENCIL': + return tool_settings.gpencil_vertex_paint return None @staticmethod @@ -1749,6 +1751,37 @@ def brush_basic_grease_pencil_weight_settings(layout, context, brush, *, compact layout.prop(brush, "direction", expand=True, text="" if compact else "Direction") +def brush_basic_grease_pencil_vertex_settings(layout, context, brush, *, compact=False): + UnifiedPaintPanel.prop_unified( + layout, + context, + brush, + "size", + pressure_name="use_pressure_size", + unified_name="use_unified_size", + text="Radius", + slider=True, + header=compact, + ) + + if brush.gpencil_vertex_tool in {'DRAW', 'BLUR', 'SMEAR'}: + UnifiedPaintPanel.prop_unified( + layout, + context, + brush, + "strength", + pressure_name="use_pressure_strength", + unified_name="use_unified_strength", + text="Strength", + header=compact, + ) + + gp_settings = brush.gpencil_settings + if brush.gpencil_vertex_tool in {'DRAW', 'REPLACE'}: + row = layout.row(align=True) + row.prop(gp_settings, "vertex_mode", text="Mode") + + classes = ( VIEW3D_MT_tools_projectpaint_clone, ) diff --git a/scripts/startup/bl_ui/space_toolsystem_toolbar.py b/scripts/startup/bl_ui/space_toolsystem_toolbar.py index 24c1354dc34..1db8aef7e2a 100644 --- a/scripts/startup/bl_ui/space_toolsystem_toolbar.py +++ b/scripts/startup/bl_ui/space_toolsystem_toolbar.py @@ -3026,6 +3026,44 @@ class _defs_grease_pencil_weight: ] +class _defs_grease_pencil_vertex: + + @staticmethod + def poll_select_mask(context): + if context is None: + return False + ob = context.active_object + tool_settings = context.scene.tool_settings + return ( + ob is not None and + ob.type == 'GREASEPENCIL' and ( + tool_settings.use_gpencil_vertex_select_mask_point or + tool_settings.use_gpencil_vertex_select_mask_stroke or + tool_settings.use_gpencil_vertex_select_mask_segment + ) + ) + + @staticmethod + def generate_from_brushes(context): + # Though `data_block` is conceptually unnecessary with a single brush tool, + # it's still used because many areas assume that brush tools have it set #bToolRef. + tool = None + if context: + brush = context.tool_settings.gpencil_vertex_paint.brush + if brush: + tool = brush.gpencil_vertex_tool + return [ + ToolDef.from_dict( + dict( + idname="builtin.brush", + label="Brush", + icon="brush.sculpt.paint", + data_block=tool + ) + ) + ] + + class _defs_curves_sculpt: @staticmethod @@ -3890,6 +3928,17 @@ class VIEW3D_PT_tools_active(ToolSelectPanelHelper, Panel): else () ), ], + 'VERTEX_GREASE_PENCIL': [ + _defs_grease_pencil_vertex.generate_from_brushes, + None, + *_tools_annotate, + None, + lambda context: ( + VIEW3D_PT_tools_active._tools_select + if _defs_grease_pencil_vertex.poll_select_mask(context) + else () + ), + ], 'SCULPT_CURVES': [ _defs_curves_sculpt.generate_from_brushes, None, diff --git a/scripts/startup/bl_ui/space_view3d.py b/scripts/startup/bl_ui/space_view3d.py index 734f335aa8c..710442ba11c 100644 --- a/scripts/startup/bl_ui/space_view3d.py +++ b/scripts/startup/bl_ui/space_view3d.py @@ -13,6 +13,7 @@ from bl_ui.properties_paint_common import ( brush_basic_texpaint_settings, brush_basic_gpencil_weight_settings, brush_basic_grease_pencil_weight_settings, + brush_basic_grease_pencil_vertex_settings, BrushAssetShelf, ) from bl_ui.properties_grease_pencil_common import ( @@ -144,7 +145,7 @@ class VIEW3D_HT_tool_header(Header): elif tool_mode == 'WEIGHT_GPENCIL' or tool_mode == 'WEIGHT_GREASE_PENCIL': if is_valid_context: layout.popover("VIEW3D_PT_tools_grease_pencil_weight_appearance") - elif tool_mode == 'VERTEX_GPENCIL': + elif tool_mode == 'VERTEX_GPENCIL' or tool_mode == 'VERTEX_GREASE_PENCIL': if is_valid_context: layout.popover("VIEW3D_PT_tools_grease_pencil_vertex_appearance") @@ -551,7 +552,6 @@ class _draw_tool_settings_context_mode: brush = paint.brush row = layout.row(align=True) - settings = tool_settings.gpencil_vertex_paint BrushAssetShelf.draw_popup_selector(layout, context, brush) @@ -567,6 +567,27 @@ class _draw_tool_settings_context_mode: return True + @staticmethod + def VERTEX_GREASE_PENCIL(context, layout, tool): + if (tool is None) or (not tool.has_datablock): + return False + + tool_settings = context.tool_settings + paint = tool_settings.gpencil_vertex_paint + brush = paint.brush + + BrushAssetShelf.draw_popup_selector(layout, context, brush) + + if brush.gpencil_vertex_tool not in {'BLUR', 'AVERAGE', 'SMEAR'}: + layout.separator(factor=0.4) + ups = context.tool_settings.unified_paint_settings + prop_owner = ups if ups.use_unified_color else brush + layout.prop_with_popover(prop_owner, "color", text="", panel="TOPBAR_PT_grease_pencil_vertex_color") + + brush_basic_grease_pencil_vertex_settings(layout, context, brush, compact=True) + + return True + @staticmethod def PARTICLE(context, layout, tool): if (tool is None) or (not tool.has_datablock): @@ -938,18 +959,23 @@ class VIEW3D_HT_header(Header): row.separator(factor=0.4) row.prop(tool_settings, "use_gpencil_draw_additive", text="", icon='FREEZE') - # Select mode for Sculpt if object_mode == 'SCULPT_GPENCIL': row = layout.row(align=True) row.prop(tool_settings, "use_gpencil_select_mask_point", text="") row.prop(tool_settings, "use_gpencil_select_mask_stroke", text="") row.prop(tool_settings, "use_gpencil_select_mask_segment", text="") - if object_mode in {'PAINT_GPENCIL', 'EDIT', 'SCULPT_GPENCIL', 'WEIGHT_GPENCIL'}: + if object_mode == 'VERTEX_GPENCIL': + row = layout.row(align=True) + row.prop(tool_settings, "use_gpencil_vertex_select_mask_point", text="") + row.prop(tool_settings, "use_gpencil_vertex_select_mask_stroke", text="") + row.prop(tool_settings, "use_gpencil_vertex_select_mask_segment", text="") + + if object_mode in {'PAINT_GPENCIL', 'EDIT', 'SCULPT_GPENCIL', 'WEIGHT_GPENCIL', 'VERTEX_GPENCIL'}: row = layout.row(align=True) row.prop(tool_settings, "use_grease_pencil_multi_frame_editing", text="") - if object_mode in {'EDIT', 'SCULPT_GPENCIL', 'WEIGHT_GPENCIL'}: + if object_mode in {'EDIT', 'SCULPT_GPENCIL', 'WEIGHT_GPENCIL', 'VERTEX_GPENCIL'}: sub = row.row(align=True) sub.enabled = tool_settings.use_grease_pencil_multi_frame_editing sub.popover( @@ -1288,6 +1314,7 @@ class VIEW3D_MT_editor_menus(Menu): layout.menu("VIEW3D_MT_select_paint_mask_vertex") elif mode_string not in { 'SCULPT', 'SCULPT_CURVES', 'PAINT_GREASE_PENCIL', 'SCULPT_GREASE_PENCIL', 'WEIGHT_GREASE_PENCIL', + 'VERTEX_GREASE_PENCIL', }: layout.menu("VIEW3D_MT_select_" + mode_string.lower()) @@ -1341,7 +1368,7 @@ class VIEW3D_MT_editor_menus(Menu): layout.menu("VIEW3D_MT_edit_greasepencil_stroke") elif obj: - if mode_string not in {'PAINT_TEXTURE', 'SCULPT_CURVES', 'SCULPT_GREASE_PENCIL'}: + if mode_string not in {'PAINT_TEXTURE', 'SCULPT_CURVES', 'SCULPT_GREASE_PENCIL', 'VERTEX_GREASE_PENCIL'}: layout.menu("VIEW3D_MT_" + mode_string.lower()) if mode_string == 'SCULPT': layout.menu("VIEW3D_MT_mask") @@ -1351,6 +1378,10 @@ class VIEW3D_MT_editor_menus(Menu): layout.menu("VIEW3D_MT_select_sculpt_curves") layout.menu("VIEW3D_MT_sculpt_curves") layout.template_node_operator_asset_root_items() + elif mode_string == 'VERTEX_GREASE_PENCIL': + layout.menu("VIEW3D_MT_select_edit_grease_pencil") + layout.menu("VIEW3D_MT_paint_vertex_grease_pencil") + layout.template_node_operator_asset_root_items() else: layout.template_node_operator_asset_root_items() @@ -2369,6 +2400,13 @@ class VIEW3D_MT_paint_gpencil(Menu): layout.operator("gpencil.vertex_color_brightness_contrast", text="Brightness/Contrast") +class VIEW3D_MT_paint_vertex_grease_pencil(Menu): + bl_label = "Paint" + + def draw(self, _context): + pass + + class VIEW3D_MT_select_edit_gpencil(Menu): bl_label = "Select" @@ -9274,6 +9312,41 @@ class TOPBAR_PT_gpencil_vertexcolor(GreasePencilVertexcolorPanel, Panel): return ob and ob.type == 'GPENCIL' +class TOPBAR_PT_grease_pencil_vertex_color(Panel): + bl_space_type = 'VIEW_3D' + bl_region_type = 'HEADER' + bl_label = "Color Attribute" + bl_ui_units_x = 10 + + @classmethod + def poll(cls, context): + ob = context.object + return ob and ob.type == 'GREASEPENCIL' and context.mode == 'VERTEX_GREASE_PENCIL' + + def draw(self, context): + layout = self.layout + layout.use_property_split = True + layout.use_property_decorate = False + + gpencil_paint = context.scene.tool_settings.gpencil_vertex_paint + brush = gpencil_paint.brush + + col = layout.column() + col.template_color_picker(brush, "color", value_slider=True) + + sub_row = layout.row(align=True) + UnifiedPaintPanel.prop_unified_color(sub_row, context, brush, "color", text="") + UnifiedPaintPanel.prop_unified_color(sub_row, context, brush, "secondary_color", text="") + + # TODO + # sub_row.operator("gpencil.tint_flip", icon='FILE_REFRESH', text="") + + row = layout.row(align=True) + row.template_ID(gpencil_paint, "palette", new="palette.new") + if gpencil_paint.palette: + layout.template_palette(gpencil_paint, "palette", color=True) + + class VIEW3D_PT_curves_sculpt_add_shape(Panel): # Only for popover, these are dummy values. bl_space_type = 'VIEW_3D' @@ -9559,6 +9632,7 @@ classes = ( VIEW3D_MT_greasepencil_material_active, VIEW3D_MT_paint_grease_pencil, VIEW3D_MT_paint_gpencil, + VIEW3D_MT_paint_vertex_grease_pencil, VIEW3D_MT_draw_gpencil, VIEW3D_MT_assign_material, VIEW3D_MT_edit_gpencil, @@ -9688,6 +9762,7 @@ classes = ( VIEW3D_PT_sculpt_context_menu, TOPBAR_PT_gpencil_materials, TOPBAR_PT_gpencil_vertexcolor, + TOPBAR_PT_grease_pencil_vertex_color, TOPBAR_PT_annotation_layers, VIEW3D_PT_curves_sculpt_add_shape, VIEW3D_PT_curves_sculpt_parameter_falloff, diff --git a/source/blender/blenkernel/BKE_context.hh b/source/blender/blenkernel/BKE_context.hh index aa1e11591e9..a9de346bcdb 100644 --- a/source/blender/blenkernel/BKE_context.hh +++ b/source/blender/blenkernel/BKE_context.hh @@ -145,8 +145,9 @@ enum eContextObjectMode { CTX_MODE_PAINT_GREASE_PENCIL, CTX_MODE_SCULPT_GREASE_PENCIL, CTX_MODE_WEIGHT_GREASE_PENCIL, + CTX_MODE_VERTEX_GREASE_PENCIL, }; -#define CTX_MODE_NUM (CTX_MODE_WEIGHT_GREASE_PENCIL + 1) +#define CTX_MODE_NUM (CTX_MODE_VERTEX_GREASE_PENCIL + 1) /* Context */ diff --git a/source/blender/blenkernel/intern/context.cc b/source/blender/blenkernel/intern/context.cc index c9d48bb7d76..3133e772770 100644 --- a/source/blender/blenkernel/intern/context.cc +++ b/source/blender/blenkernel/intern/context.cc @@ -1254,7 +1254,12 @@ enum eContextObjectMode CTX_data_mode_enum_ex(const Object *obedit, } } if (object_mode & OB_MODE_VERTEX_GPENCIL_LEGACY) { - return CTX_MODE_VERTEX_GPENCIL_LEGACY; + if (ob->type == OB_GPENCIL_LEGACY) { + return CTX_MODE_VERTEX_GPENCIL_LEGACY; + } + if (ob->type == OB_GREASE_PENCIL) { + return CTX_MODE_VERTEX_GREASE_PENCIL; + } } if (object_mode & OB_MODE_SCULPT_CURVES) { return CTX_MODE_SCULPT_CURVES; @@ -1304,6 +1309,7 @@ static const char *data_mode_strings[] = { "grease_pencil_paint", "grease_pencil_sculpt", "grease_pencil_weight", + "grease_pencil_vertex", nullptr, }; BLI_STATIC_ASSERT(ARRAY_SIZE(data_mode_strings) == CTX_MODE_NUM + 1, diff --git a/source/blender/blenkernel/intern/paint.cc b/source/blender/blenkernel/intern/paint.cc index 60073382cb3..9cd098e121a 100644 --- a/source/blender/blenkernel/intern/paint.cc +++ b/source/blender/blenkernel/intern/paint.cc @@ -548,6 +548,8 @@ PaintMode BKE_paintmode_get_active_from_context(const bContext *C) return PaintMode::GPencil; case OB_MODE_WEIGHT_GPENCIL_LEGACY: return PaintMode::WeightGPencil; + case OB_MODE_VERTEX_GPENCIL_LEGACY: + return PaintMode::VertexGPencil; case OB_MODE_VERTEX_PAINT: return PaintMode::Vertex; case OB_MODE_WEIGHT_PAINT: @@ -583,6 +585,7 @@ PaintMode BKE_paintmode_get_from_tool(const bToolRef *tref) return PaintMode::GPencil; case CTX_MODE_PAINT_TEXTURE: return PaintMode::Texture3D; + case CTX_MODE_VERTEX_GREASE_PENCIL: case CTX_MODE_VERTEX_GPENCIL_LEGACY: return PaintMode::VertexGPencil; case CTX_MODE_SCULPT_GPENCIL_LEGACY: diff --git a/source/blender/blenloader/intern/versioning_400.cc b/source/blender/blenloader/intern/versioning_400.cc index 6ab82511a4b..85713992735 100644 --- a/source/blender/blenloader/intern/versioning_400.cc +++ b/source/blender/blenloader/intern/versioning_400.cc @@ -2653,6 +2653,7 @@ static void update_paint_modes_for_brush_assets(Main &bmain) CTX_MODE_SCULPT_GREASE_PENCIL, CTX_MODE_WEIGHT_GPENCIL_LEGACY, CTX_MODE_WEIGHT_GREASE_PENCIL, + CTX_MODE_VERTEX_GREASE_PENCIL, CTX_MODE_VERTEX_GPENCIL_LEGACY, CTX_MODE_SCULPT_CURVES)) { diff --git a/source/blender/draw/engines/overlay/overlay_engine.cc b/source/blender/draw/engines/overlay/overlay_engine.cc index d32ea080d27..8e709464cec 100644 --- a/source/blender/draw/engines/overlay/overlay_engine.cc +++ b/source/blender/draw/engines/overlay/overlay_engine.cc @@ -185,6 +185,7 @@ static void OVERLAY_cache_init(void *vedata) case CTX_MODE_SCULPT_GREASE_PENCIL: case CTX_MODE_EDIT_GREASE_PENCIL: case CTX_MODE_WEIGHT_GREASE_PENCIL: + case CTX_MODE_VERTEX_GREASE_PENCIL: OVERLAY_edit_grease_pencil_cache_init(data); break; case CTX_MODE_PARTICLE: @@ -782,6 +783,7 @@ static void OVERLAY_draw_scene(void *vedata) case CTX_MODE_EDIT_GREASE_PENCIL: case CTX_MODE_SCULPT_GREASE_PENCIL: case CTX_MODE_WEIGHT_GREASE_PENCIL: + case CTX_MODE_VERTEX_GREASE_PENCIL: OVERLAY_edit_grease_pencil_draw(data); break; default: diff --git a/source/blender/editors/gpencil_legacy/gpencil_edit.cc b/source/blender/editors/gpencil_legacy/gpencil_edit.cc index 354568c5b76..128f4f8fa51 100644 --- a/source/blender/editors/gpencil_legacy/gpencil_edit.cc +++ b/source/blender/editors/gpencil_legacy/gpencil_edit.cc @@ -755,51 +755,72 @@ void GPENCIL_OT_weightmode_toggle(wmOperatorType *ot) /** \name Toggle Vertex Paint Mode Operator * \{ */ +static bool grease_pencil_poll_vertex_cursor(bContext *C) +{ + Object *ob = CTX_data_active_object(C); + return ob && (ob->mode & OB_MODE_VERTEX_GPENCIL_LEGACY) && (ob->type == OB_GREASE_PENCIL) && + CTX_wm_region_view3d(C) && WM_toolsystem_active_tool_is_brush(C); +} + static bool gpencil_vertexmode_toggle_poll(bContext *C) { /* if using gpencil object, use this gpd */ Object *ob = CTX_data_active_object(C); - if ((ob) && (ob->type == OB_GPENCIL_LEGACY)) { + if ((ob) && ELEM(ob->type, OB_GPENCIL_LEGACY, OB_GREASE_PENCIL)) { return ob->data != nullptr; } - return ED_gpencil_data_get_active(C) != nullptr; + return false; } + static int gpencil_vertexmode_toggle_exec(bContext *C, wmOperator *op) { const bool back = RNA_boolean_get(op->ptr, "back"); wmMsgBus *mbus = CTX_wm_message_bus(C); Main *bmain = CTX_data_main(C); - bGPdata *gpd = ED_gpencil_data_get_active(C); + Scene *scene = CTX_data_scene(C); ToolSettings *ts = CTX_data_tool_settings(C); bool is_object = false; short mode; /* if using a gpencil object, use this datablock */ Object *ob = CTX_data_active_object(C); + const bool is_mode_set = (ob->mode & OB_MODE_VERTEX_GPENCIL_LEGACY) != 0; if ((ob) && (ob->type == OB_GPENCIL_LEGACY)) { - gpd = static_cast(ob->data); + bGPdata *gpd = static_cast(ob->data); is_object = true; - } - if (gpd == nullptr) { - return OPERATOR_CANCELLED; - } + if (gpd == nullptr) { + return OPERATOR_CANCELLED; + } - /* Just toggle paintmode flag... */ - gpd->flag ^= GP_DATA_STROKE_VERTEXMODE; - /* set mode */ - if (gpd->flag & GP_DATA_STROKE_VERTEXMODE) { - mode = OB_MODE_VERTEX_GPENCIL_LEGACY; + /* Just toggle paintmode flag... */ + gpd->flag ^= GP_DATA_STROKE_VERTEXMODE; + /* Set mode. */ + if (gpd->flag & GP_DATA_STROKE_VERTEXMODE) { + mode = OB_MODE_VERTEX_GPENCIL_LEGACY; + } + else { + mode = OB_MODE_OBJECT; + } } - else { - mode = OB_MODE_OBJECT; + else if ((ob) && (ob->type == OB_GREASE_PENCIL)) { + is_object = true; + if (!is_mode_set) { + mode = OB_MODE_VERTEX_GPENCIL_LEGACY; + } + else { + mode = OB_MODE_OBJECT; + } } if (is_object) { - /* try to back previous mode */ - if ((ob->restore_mode) && ((gpd->flag & GP_DATA_STROKE_VERTEXMODE) == 0) && (back == 1)) { - mode = ob->restore_mode; + if (ob->type == OB_GPENCIL_LEGACY) { + bGPdata *gpd = static_cast(ob->data); + /* Try to go to back previous mode. */ + if ((ob->restore_mode) && ((gpd->flag & GP_DATA_STROKE_VERTEXMODE) == 0) && (back == 1)) { + mode = ob->restore_mode; + } } ob->restore_mode = ob->mode; ob->mode = mode; @@ -810,17 +831,29 @@ static int gpencil_vertexmode_toggle_exec(bContext *C, wmOperator *op) * Need Draw as well (used for Palettes). */ BKE_paint_ensure(bmain, ts, (Paint **)&ts->gp_paint); BKE_paint_ensure(bmain, ts, (Paint **)&ts->gp_vertexpaint); + Paint *vertex_paint = BKE_paint_get_active_from_paintmode(scene, PaintMode::VertexGPencil); - BKE_paint_brushes_validate(bmain, &ts->gp_vertexpaint->paint); + BKE_paint_brushes_validate(bmain, vertex_paint); + + if (ob->type == OB_GREASE_PENCIL) { + ED_paint_cursor_start(vertex_paint, grease_pencil_poll_vertex_cursor); + } /* Ensure Palette by default. */ - BKE_gpencil_palette_ensure(bmain, CTX_data_scene(C)); + BKE_gpencil_palette_ensure(bmain, scene); } - /* setup other modes */ - ED_gpencil_setup_modes(C, gpd, mode); - /* set cache as dirty */ - DEG_id_tag_update(&gpd->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY); + /* Setup other modes. */ + if (ob->type == OB_GPENCIL_LEGACY) { + bGPdata *gpd = static_cast(ob->data); + ED_gpencil_setup_modes(C, gpd, mode); + /* Set cache as dirty. */ + DEG_id_tag_update(&gpd->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY); + } + else if (ob->type == OB_GREASE_PENCIL) { + GreasePencil *grease_pencil = static_cast(ob->data); + DEG_id_tag_update(&grease_pencil->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY); + } WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | ND_GPENCIL_EDITMODE, nullptr); WM_event_add_notifier(C, NC_SCENE | ND_MODE, nullptr); diff --git a/source/blender/editors/grease_pencil/intern/grease_pencil_ops.cc b/source/blender/editors/grease_pencil/intern/grease_pencil_ops.cc index 72c3309d23d..2f767c8febe 100644 --- a/source/blender/editors/grease_pencil/intern/grease_pencil_ops.cc +++ b/source/blender/editors/grease_pencil/intern/grease_pencil_ops.cc @@ -117,6 +117,22 @@ bool grease_pencil_weight_painting_poll(bContext *C) return true; } +bool grease_pencil_vertex_painting_poll(bContext *C) +{ + if (!active_grease_pencil_poll(C)) { + return false; + } + Object *object = CTX_data_active_object(C); + if ((object->mode & OB_MODE_VERTEX_GPENCIL_LEGACY) == 0) { + return false; + } + ToolSettings *ts = CTX_data_tool_settings(C); + if (!ts || !ts->gp_vertexpaint) { + return false; + } + return true; +} + static void keymap_grease_pencil_edit_mode(wmKeyConfig *keyconf) { wmKeyMap *keymap = WM_keymap_ensure( @@ -145,6 +161,13 @@ static void keymap_grease_pencil_weight_paint_mode(wmKeyConfig *keyconf) keymap->poll = grease_pencil_weight_painting_poll; } +static void keymap_grease_pencil_vertex_paint_mode(wmKeyConfig *keyconf) +{ + wmKeyMap *keymap = WM_keymap_ensure( + keyconf, "Grease Pencil Vertex Paint", SPACE_EMPTY, RGN_TYPE_WINDOW); + keymap->poll = grease_pencil_vertex_painting_poll; +} + /* Enabled for all tools except the fill tool. */ static bool keymap_grease_pencil_brush_stroke_poll(bContext *C) { @@ -246,6 +269,7 @@ 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_vertex_paint_mode(keyconf); keymap_grease_pencil_brush_stroke(keyconf); keymap_grease_pencil_fill_tool(keyconf); ED_primitivetool_modal_keymap(keyconf); diff --git a/source/blender/editors/grease_pencil/intern/grease_pencil_utils.cc b/source/blender/editors/grease_pencil/intern/grease_pencil_utils.cc index 352a4766a4e..99ab2e8cf30 100644 --- a/source/blender/editors/grease_pencil/intern/grease_pencil_utils.cc +++ b/source/blender/editors/grease_pencil/intern/grease_pencil_utils.cc @@ -875,6 +875,21 @@ static VectorSet get_hidden_material_indices(Object &object) return hidden_material_indices; } +static VectorSet get_fill_material_indices(Object &object) +{ + BLI_assert(object.type == OB_GREASE_PENCIL); + VectorSet fill_material_indices; + for (const int mat_i : IndexRange(object.totcol)) { + Material *material = BKE_object_material_get(&object, mat_i + 1); + if (material != nullptr && material->gp_style != nullptr && + (material->gp_style->flag & GP_MATERIAL_FILL_SHOW) != 0) + { + fill_material_indices.add_new(mat_i); + } + } + return fill_material_indices; +} + IndexMask retrieve_editable_strokes(Object &object, const bke::greasepencil::Drawing &drawing, int layer_index, @@ -916,6 +931,50 @@ IndexMask retrieve_editable_strokes(Object &object, }); } +IndexMask retrieve_editable_fill_strokes(Object &object, + const bke::greasepencil::Drawing &drawing, + int layer_index, + IndexMaskMemory &memory) +{ + using namespace blender; + const bke::CurvesGeometry &curves = drawing.strokes(); + const IndexRange curves_range = drawing.strokes().curves_range(); + + if (object.totcol == 0) { + return IndexMask(curves_range); + } + + /* Get all the editable material indices */ + const VectorSet editable_material_indices = get_editable_material_indices(object); + if (editable_material_indices.is_empty()) { + return {}; + } + + const bke::AttributeAccessor attributes = curves.attributes(); + GreasePencil &grease_pencil = *static_cast(object.data); + const bke::greasepencil::Layer &layer = *grease_pencil.layers()[layer_index]; + const VectorSet fill_material_indices = get_fill_material_indices(object); + + const VArray materials = *attributes.lookup("material_index", bke::AttrDomain::Curve); + if (!materials) { + /* If the attribute does not exist then the default is the first material. */ + if (editable_material_indices.contains(0)) { + return curves_range; + } + return {}; + } + /* Get all the strokes that have their material unlocked. */ + return IndexMask::from_predicate( + curves_range, GrainSize(4096), memory, [&](const int64_t curve_i) { + const int material_index = materials[curve_i]; + /* The stroke is editable if the material is editable. If the material is not editable, + * then the stroke is only editable if the layer disables the locked material option. */ + return (editable_material_indices.contains(material_index) || + layer.use_locked_material()) && + fill_material_indices.contains(material_index); + }); +} + IndexMask retrieve_editable_strokes_by_material(Object &object, const bke::greasepencil::Drawing &drawing, const int mat_i, @@ -1136,6 +1195,21 @@ IndexMask retrieve_editable_and_selected_strokes(Object &object, return IndexMask::from_intersection(editable_strokes, selected_strokes, memory); } +IndexMask retrieve_editable_and_selected_fill_strokes(Object &object, + const bke::greasepencil::Drawing &drawing, + int layer_index, + IndexMaskMemory &memory) +{ + using namespace blender; + const bke::CurvesGeometry &curves = drawing.strokes(); + + const IndexMask editable_strokes = retrieve_editable_fill_strokes( + object, drawing, layer_index, memory); + const IndexMask selected_strokes = ed::curves::retrieve_selected_curves(curves, memory); + + return IndexMask::from_intersection(editable_strokes, selected_strokes, memory); +} + IndexMask retrieve_editable_and_selected_points(Object &object, const bke::greasepencil::Drawing &drawing, int layer_index, diff --git a/source/blender/editors/include/ED_grease_pencil.hh b/source/blender/editors/include/ED_grease_pencil.hh index 0ece6a742bb..57702707a63 100644 --- a/source/blender/editors/include/ED_grease_pencil.hh +++ b/source/blender/editors/include/ED_grease_pencil.hh @@ -270,6 +270,7 @@ bool editable_grease_pencil_point_selection_poll(bContext *C); bool grease_pencil_painting_poll(bContext *C); bool grease_pencil_sculpting_poll(bContext *C); bool grease_pencil_weight_painting_poll(bContext *C); +bool grease_pencil_vertex_painting_poll(bContext *C); float opacity_from_input_sample(const float pressure, const Brush *brush, @@ -323,6 +324,10 @@ IndexMask retrieve_editable_strokes(Object &grease_pencil_object, const bke::greasepencil::Drawing &drawing, int layer_index, IndexMaskMemory &memory); +IndexMask retrieve_editable_fill_strokes(Object &grease_pencil_object, + const bke::greasepencil::Drawing &drawing, + int layer_index, + IndexMaskMemory &memory); IndexMask retrieve_editable_strokes_by_material(Object &object, const bke::greasepencil::Drawing &drawing, const int mat_i, @@ -357,6 +362,10 @@ IndexMask retrieve_editable_and_selected_strokes(Object &grease_pencil_object, const bke::greasepencil::Drawing &drawing, int layer_index, IndexMaskMemory &memory); +IndexMask retrieve_editable_and_selected_fill_strokes(Object &grease_pencil_object, + const bke::greasepencil::Drawing &drawing, + int layer_index, + IndexMaskMemory &memory); IndexMask retrieve_editable_and_selected_points(Object &object, const bke::greasepencil::Drawing &drawing, int layer_index, diff --git a/source/blender/editors/object/object_modes.cc b/source/blender/editors/object/object_modes.cc index e5b4de52782..96d035aaeb3 100644 --- a/source/blender/editors/object/object_modes.cc +++ b/source/blender/editors/object/object_modes.cc @@ -145,7 +145,7 @@ bool mode_compat_test(const Object *ob, eObjectMode mode) break; case OB_GREASE_PENCIL: if (mode & (OB_MODE_EDIT | OB_MODE_PAINT_GPENCIL_LEGACY | OB_MODE_SCULPT_GPENCIL_LEGACY | - OB_MODE_WEIGHT_GPENCIL_LEGACY)) + OB_MODE_WEIGHT_GPENCIL_LEGACY | OB_MODE_VERTEX_GPENCIL_LEGACY)) { return true; } diff --git a/source/blender/editors/sculpt_paint/CMakeLists.txt b/source/blender/editors/sculpt_paint/CMakeLists.txt index 3d73920ea14..27d944723f3 100644 --- a/source/blender/editors/sculpt_paint/CMakeLists.txt +++ b/source/blender/editors/sculpt_paint/CMakeLists.txt @@ -52,8 +52,8 @@ set(SRC grease_pencil_fill.cc grease_pencil_interpolate.cc grease_pencil_paint.cc + grease_pencil_paint_common.cc grease_pencil_sculpt_clone.cc - grease_pencil_sculpt_common.cc grease_pencil_sculpt_grab.cc grease_pencil_sculpt_pinch.cc grease_pencil_sculpt_push.cc @@ -69,6 +69,11 @@ set(SRC grease_pencil_weight_blur.cc grease_pencil_weight_draw.cc grease_pencil_weight_smear.cc + grease_pencil_vertex_average.cc + grease_pencil_vertex_blur.cc + grease_pencil_vertex_paint.cc + grease_pencil_vertex_replace.cc + grease_pencil_vertex_smear.cc paint_canvas.cc paint_cursor.cc paint_curve.cc diff --git a/source/blender/editors/sculpt_paint/grease_pencil_draw_ops.cc b/source/blender/editors/sculpt_paint/grease_pencil_draw_ops.cc index 22cc43083fb..1c5880d2548 100644 --- a/source/blender/editors/sculpt_paint/grease_pencil_draw_ops.cc +++ b/source/blender/editors/sculpt_paint/grease_pencil_draw_ops.cc @@ -147,6 +147,24 @@ static std::unique_ptr get_stroke_operation(bContex return greasepencil::new_weight_paint_smear_operation(); } } + else if (mode == PaintMode::VertexGPencil) { + switch (eBrushGPVertexType(brush.gpencil_vertex_brush_type)) { + case GPVERTEX_BRUSH_TYPE_DRAW: + return greasepencil::new_vertex_paint_operation(stroke_mode); + case GPVERTEX_BRUSH_TYPE_BLUR: + return greasepencil::new_vertex_blur_operation(); + case GPVERTEX_BRUSH_TYPE_AVERAGE: + return greasepencil::new_vertex_average_operation(); + case GPVERTEX_BRUSH_TYPE_SMEAR: + return greasepencil::new_vertex_smear_operation(); + case GPVERTEX_BRUSH_TYPE_REPLACE: + return greasepencil::new_vertex_replace_operation(); + case GPVERTEX_BRUSH_TYPE_TINT: + /* Unused. */ + BLI_assert_unreachable(); + return nullptr; + } + } return nullptr; } @@ -327,7 +345,6 @@ static int grease_pencil_sculpt_paint_invoke(bContext *C, wmOperator *op, const } bke::greasepencil::Layer &active_layer = *grease_pencil.get_active_layer(); - if (!active_layer.is_editable()) { BKE_report(op->reports, RPT_ERROR, "Active layer is locked or hidden"); return OPERATOR_CANCELLED; @@ -380,7 +397,7 @@ static void GREASE_PENCIL_OT_sculpt_paint(wmOperatorType *ot) { ot->name = "Grease Pencil Draw"; ot->idname = "GREASE_PENCIL_OT_sculpt_paint"; - ot->description = "Draw a new stroke in the active Grease Pencil object"; + ot->description = "Sculpt strokes in the active Grease Pencil object"; ot->poll = grease_pencil_sculpt_paint_poll; ot->invoke = grease_pencil_sculpt_paint_invoke; @@ -487,6 +504,111 @@ static void GREASE_PENCIL_OT_weight_brush_stroke(wmOperatorType *ot) /** \} */ +/* -------------------------------------------------------------------- */ +/** \name Vertex Brush Stroke Operator + * \{ */ + +static bool grease_pencil_vertex_brush_stroke_poll(bContext *C) +{ + if (!ed::greasepencil::grease_pencil_vertex_painting_poll(C)) { + return false; + } + if (!WM_toolsystem_active_tool_is_brush(C)) { + return false; + } + return true; +} + +static int grease_pencil_vertex_brush_stroke_invoke(bContext *C, + wmOperator *op, + const wmEvent *event) +{ + const Object *object = CTX_data_active_object(C); + if (!object || object->type != OB_GREASE_PENCIL) { + return OPERATOR_CANCELLED; + } + + GreasePencil &grease_pencil = *static_cast(object->data); + if (!grease_pencil.has_active_layer()) { + BKE_report(op->reports, RPT_ERROR, "No active Grease Pencil layer"); + return OPERATOR_CANCELLED; + } + + bke::greasepencil::Layer &active_layer = *grease_pencil.get_active_layer(); + if (!active_layer.is_editable()) { + BKE_report(op->reports, RPT_ERROR, "Active layer is locked or hidden"); + return OPERATOR_CANCELLED; + } + + const Paint *paint = BKE_paint_get_active_from_context(C); + const Brush *brush = BKE_paint_brush_for_read(paint); + if (brush == nullptr) { + return OPERATOR_CANCELLED; + } + + /* Ensure a drawing at the current keyframe. */ + bool inserted_keyframe = false; + /* For the vertex paint tools, we don't want the auto-key to create an empty keyframe, so we + * duplicate the previous key. */ + const bool use_duplicate_previous_key = true; + if (!ed::greasepencil::ensure_active_keyframe( + C, grease_pencil, use_duplicate_previous_key, inserted_keyframe)) + { + BKE_report(op->reports, RPT_ERROR, "No Grease Pencil frame to draw on"); + return OPERATOR_CANCELLED; + } + if (inserted_keyframe) { + WM_event_add_notifier(C, NC_GPENCIL | NA_EDITED, nullptr); + } + + op->customdata = paint_stroke_new(C, + op, + stroke_get_location, + stroke_test_start, + stroke_update_step, + stroke_redraw, + stroke_done, + event->type); + + const int return_value = op->type->modal(C, op, event); + if (return_value == OPERATOR_FINISHED) { + return OPERATOR_FINISHED; + } + + WM_event_add_modal_handler(C, op); + return OPERATOR_RUNNING_MODAL; +} + +static int grease_pencil_vertex_brush_stroke_modal(bContext *C, + wmOperator *op, + const wmEvent *event) +{ + return paint_stroke_modal(C, op, event, reinterpret_cast(&op->customdata)); +} + +static void grease_pencil_vertex_brush_stroke_cancel(bContext *C, wmOperator *op) +{ + paint_stroke_cancel(C, op, static_cast(op->customdata)); +} + +static void GREASE_PENCIL_OT_vertex_brush_stroke(wmOperatorType *ot) +{ + ot->name = "Grease Pencil Paint Vertex"; + ot->idname = "GREASE_PENCIL_OT_vertex_brush_stroke"; + ot->description = "Draw on vertex colors in the active Grease Pencil object"; + + ot->poll = grease_pencil_vertex_brush_stroke_poll; + ot->invoke = grease_pencil_vertex_brush_stroke_invoke; + ot->modal = grease_pencil_vertex_brush_stroke_modal; + ot->cancel = grease_pencil_vertex_brush_stroke_cancel; + + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + paint_stroke_operator_properties(ot); +} + +/** \} */ + /* -------------------------------------------------------------------- */ /** \name Bucket Fill Operator * \{ */ @@ -1602,6 +1724,7 @@ 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_vertex_brush_stroke); WM_operatortype_append(GREASE_PENCIL_OT_fill); } diff --git a/source/blender/editors/sculpt_paint/grease_pencil_intern.hh b/source/blender/editors/sculpt_paint/grease_pencil_intern.hh index 6f19dd8a395..70bc30997ff 100644 --- a/source/blender/editors/sculpt_paint/grease_pencil_intern.hh +++ b/source/blender/editors/sculpt_paint/grease_pencil_intern.hh @@ -44,28 +44,39 @@ class GreasePencilStrokeOperation : public PaintModeData { namespace greasepencil { /* Get list of drawings the tool should be operating on. */ -Vector get_drawings_for_sculpt(const bContext &C); +Vector get_drawings_for_painting(const bContext &C); +/* Get the brush radius accounting for pen pressure. */ +float brush_radius(const Scene &scene, const Brush &brush, float pressure); /* Make sure the brush has all necessary grease pencil settings. */ void init_brush(Brush &brush); /* Index mask of all points within the brush radius. */ -IndexMask brush_influence_mask(const Scene &scene, - const Brush &brush, - const float2 &mouse_position, - float pressure, - float multi_frame_falloff, - const IndexMask &selection, - Span view_positions, - Vector &influences, - IndexMaskMemory &memory); +IndexMask brush_point_influence_mask(const Scene &scene, + const Brush &brush, + const float2 &mouse_position, + float pressure, + float multi_frame_falloff, + const IndexMask &selection, + Span view_positions, + Vector &influences, + IndexMaskMemory &memory); /* Influence value at point co for the brush. */ -float brush_influence(const Scene &scene, - const Brush &brush, - const float2 &co, - const InputSample &sample, - float multi_frame_falloff); +float brush_point_influence(const Scene &scene, + const Brush &brush, + const float2 &co, + const InputSample &sample, + float multi_frame_falloff); +/* Compute the closest distance to a polygon. If the point is inside the polygon, the distance is + * 0.0f. If the point is outside the polygon, the distance to the closest point is returned. */ +float closest_distance_to_surface_2d(const float2 pt, const Span verts); +/* Influence value for an entire fill. */ +float brush_fill_influence(const Scene &scene, + const Brush &brush, + Span fill_positions, + const InputSample &sample, + float multi_frame_falloff); /* True if influence of the brush should be inverted. */ bool is_brush_inverted(const Brush &brush, BrushStrokeMode stroke_mode); @@ -95,7 +106,15 @@ struct GreasePencilStrokeParams { }; /* Point index mask for a drawing based on selection tool settings. */ -IndexMask point_selection_mask(const GreasePencilStrokeParams ¶ms, IndexMaskMemory &memory); +IndexMask point_selection_mask(const GreasePencilStrokeParams ¶ms, + const bool use_masking, + IndexMaskMemory &memory); +IndexMask stroke_selection_mask(const GreasePencilStrokeParams ¶ms, + const bool use_masking, + IndexMaskMemory &memory); +IndexMask fill_selection_mask(const GreasePencilStrokeParams ¶ms, + const bool use_masking, + IndexMaskMemory &memory); bke::crazyspace::GeometryDeformation get_drawing_deformation( const GreasePencilStrokeParams ¶ms); @@ -103,6 +122,11 @@ bke::crazyspace::GeometryDeformation get_drawing_deformation( /* Project points from layer space into 2D view space. */ Array calculate_view_positions(const GreasePencilStrokeParams ¶ms, const IndexMask &selection); +Array calculate_view_radii(const GreasePencilStrokeParams ¶ms, + const IndexMask &selection); + +bool do_vertex_color_points(const Brush &brush); +bool do_vertex_color_fill(const Brush &brush); /* Stroke operation base class that performs various common initializations. */ class GreasePencilStrokeOperationCommon : public GreasePencilStrokeOperation { @@ -130,11 +154,17 @@ class GreasePencilStrokeOperationCommon : public GreasePencilStrokeOperation { void foreach_editable_drawing( const bContext &C, FunctionRef fn) const; + void foreach_editable_drawing( + const bContext &C, + GrainSize grain_size, + FunctionRef fn) const; void foreach_editable_drawing(const bContext &C, FunctionRef fn) const; }; +/* Operations */ + std::unique_ptr new_paint_operation(); std::unique_ptr new_erase_operation(bool temp_eraser); std::unique_ptr new_tint_operation(); @@ -152,6 +182,12 @@ std::unique_ptr new_push_operation(BrushStrokeMode std::unique_ptr new_pinch_operation(BrushStrokeMode stroke_mode); std::unique_ptr new_twist_operation(BrushStrokeMode stroke_mode); std::unique_ptr new_clone_operation(BrushStrokeMode stroke_mode); +std::unique_ptr new_vertex_average_operation(); +std::unique_ptr new_vertex_blur_operation(); +std::unique_ptr new_vertex_paint_operation( + BrushStrokeMode stroke_mode); +std::unique_ptr new_vertex_replace_operation(); +std::unique_ptr new_vertex_smear_operation(); } // namespace greasepencil diff --git a/source/blender/editors/sculpt_paint/grease_pencil_sculpt_common.cc b/source/blender/editors/sculpt_paint/grease_pencil_paint_common.cc similarity index 61% rename from source/blender/editors/sculpt_paint/grease_pencil_sculpt_common.cc rename to source/blender/editors/sculpt_paint/grease_pencil_paint_common.cc index e7e02fcb5cb..ea77cc0e90d 100644 --- a/source/blender/editors/sculpt_paint/grease_pencil_sculpt_common.cc +++ b/source/blender/editors/sculpt_paint/grease_pencil_paint_common.cc @@ -30,7 +30,7 @@ namespace blender::ed::sculpt_paint::greasepencil { -Vector get_drawings_for_sculpt(const bContext &C) +Vector get_drawings_for_painting(const bContext &C) { using namespace blender::bke::greasepencil; @@ -62,6 +62,7 @@ void init_brush(Brush &brush) BKE_brush_init_gpencil_settings(&brush); } BLI_assert(brush.gpencil_settings != nullptr); + BKE_curvemapping_init(brush.curve); BKE_curvemapping_init(brush.gpencil_settings->curve_strength); BKE_curvemapping_init(brush.gpencil_settings->curve_sensitivity); BKE_curvemapping_init(brush.gpencil_settings->curve_jitter); @@ -73,7 +74,7 @@ void init_brush(Brush &brush) BKE_curvemapping_init(brush.gpencil_settings->curve_rand_value); } -static float brush_radius(const Scene &scene, const Brush &brush, const float pressure = 1.0f) +float brush_radius(const Scene &scene, const Brush &brush, const float pressure = 1.0f) { float radius = BKE_brush_size_get(&scene, &brush); if (BKE_brush_use_size_pressure(&brush)) { @@ -82,11 +83,11 @@ static float brush_radius(const Scene &scene, const Brush &brush, const float pr return radius; } -float brush_influence(const Scene &scene, - const Brush &brush, - const float2 &co, - const InputSample &sample, - const float multi_frame_falloff) +float brush_point_influence(const Scene &scene, + const Brush &brush, + const float2 &co, + const InputSample &sample, + const float multi_frame_falloff) { const float radius = brush_radius(scene, brush, sample.pressure); /* Basic strength factor from brush settings. */ @@ -103,15 +104,56 @@ float brush_influence(const Scene &scene, return influence_base * brush_falloff; } -IndexMask brush_influence_mask(const Scene &scene, - const Brush &brush, - const float2 &mouse_position, - const float pressure, - const float multi_frame_falloff, - const IndexMask &selection, - const Span view_positions, - Vector &influences, - IndexMaskMemory &memory) +/* Compute the closest distance to the "surface". When the point is outside the polygon, compute + * the closest distance to the polygon points. When the point is inside the polygon return 0.*/ +float closest_distance_to_surface_2d(const float2 pt, const Span verts) +{ + int j = verts.size() - 1; + bool isect = false; + float distance = FLT_MAX; + for (int i = 0; i < verts.size(); i++) { + /* Based on implementation of #isect_point_poly_v2. */ + if (((verts[i].y > pt.y) != (verts[j].y > pt.y)) && + (pt.x < + (verts[j].x - verts[i].x) * (pt.y - verts[i].y) / (verts[j].y - verts[i].y) + verts[i].x)) + { + isect = !isect; + } + distance = math::min(distance, math::distance(pt, verts[i])); + j = i; + } + return isect ? 0.0f : distance; +} + +float brush_fill_influence(const Scene &scene, + const Brush &brush, + const Span fill_positions, + const InputSample &sample, + const float multi_frame_falloff) +{ + const float radius = brush_radius(scene, brush, sample.pressure); + /* Basic strength factor from brush settings. */ + const float brush_pressure = BKE_brush_use_alpha_pressure(&brush) ? sample.pressure : 1.0f; + const float influence_base = BKE_brush_alpha_get(&scene, &brush) * brush_pressure * + multi_frame_falloff; + + /* Distance falloff. */ + const float distance = closest_distance_to_surface_2d(sample.mouse_position, fill_positions); + /* Apply Brush curve. */ + const float brush_falloff = BKE_brush_curve_strength(&brush, distance, radius); + + return influence_base * brush_falloff; +} + +IndexMask brush_point_influence_mask(const Scene &scene, + const Brush &brush, + const float2 &mouse_position, + const float pressure, + const float multi_frame_falloff, + const IndexMask &selection, + const Span view_positions, + Vector &influences, + IndexMaskMemory &memory) { if (selection.is_empty()) { return {}; @@ -177,13 +219,33 @@ GreasePencilStrokeParams GreasePencilStrokeParams::from_context( drawing}; } -IndexMask point_selection_mask(const GreasePencilStrokeParams ¶ms, IndexMaskMemory &memory) +IndexMask point_selection_mask(const GreasePencilStrokeParams ¶ms, + const bool use_masking, + IndexMaskMemory &memory) { - const bool is_masking = GPENCIL_ANY_SCULPT_MASK( - eGP_Sculpt_SelectMaskFlag(params.toolsettings.gpencil_selectmode_sculpt)); - return (is_masking ? ed::greasepencil::retrieve_editable_and_selected_points( - params.ob_eval, params.drawing, params.layer_index, memory) : - params.drawing.strokes().points_range()); + + return (use_masking ? ed::greasepencil::retrieve_editable_and_selected_points( + params.ob_eval, params.drawing, params.layer_index, memory) : + params.drawing.strokes().points_range()); +} + +IndexMask stroke_selection_mask(const GreasePencilStrokeParams ¶ms, + const bool use_masking, + IndexMaskMemory &memory) +{ + + return (use_masking ? ed::greasepencil::retrieve_editable_and_selected_strokes( + params.ob_eval, params.drawing, params.layer_index, memory) : + params.drawing.strokes().curves_range()); +} + +IndexMask fill_selection_mask(const GreasePencilStrokeParams ¶ms, + const bool use_masking, + IndexMaskMemory &memory) +{ + return (use_masking ? ed::greasepencil::retrieve_editable_and_selected_fill_strokes( + params.ob_eval, params.drawing, params.layer_index, memory) : + params.drawing.strokes().curves_range()); } bke::crazyspace::GeometryDeformation get_drawing_deformation( @@ -216,6 +278,39 @@ Array calculate_view_positions(const GreasePencilStrokeParams ¶ms, return view_positions; } +Array calculate_view_radii(const GreasePencilStrokeParams ¶ms, + const IndexMask &selection) +{ + const RegionView3D *rv3d = static_cast(params.region.regiondata); + bke::crazyspace::GeometryDeformation deformation = get_drawing_deformation(params); + + const VArray radii = params.drawing.radii(); + Array view_radii(radii.size()); + /* Compute screen space radii. */ + const float4x4 transform = params.layer.to_world_space(params.ob_eval); + selection.foreach_index(GrainSize(4096), [&](const int64_t point_i) { + const float pixel_size = ED_view3d_pixel_size( + rv3d, math::transform_point(transform, deformation.positions[point_i])); + view_radii[point_i] = radii[point_i] / pixel_size; + }); + + return view_radii; +} + +bool do_vertex_color_points(const Brush &brush) +{ + return brush.gpencil_settings != nullptr && + ((brush.gpencil_settings->vertex_mode == GPPAINT_MODE_STROKE) || + (brush.gpencil_settings->vertex_mode == GPPAINT_MODE_BOTH)); +} + +bool do_vertex_color_fill(const Brush &brush) +{ + return brush.gpencil_settings != nullptr && + ((brush.gpencil_settings->vertex_mode == GPPAINT_MODE_FILL) || + (brush.gpencil_settings->vertex_mode == GPPAINT_MODE_BOTH)); +} + bool GreasePencilStrokeOperationCommon::is_inverted(const Brush &brush) const { return is_brush_inverted(brush, this->stroke_mode); @@ -238,8 +333,9 @@ void GreasePencilStrokeOperationCommon::foreach_editable_drawing( GreasePencil &grease_pencil = *static_cast(object.data); std::atomic changed = false; - const Vector drawings = get_drawings_for_sculpt(C); - threading::parallel_for_each(drawings, [&](const MutableDrawingInfo &info) { + const Vector drawings = get_drawings_for_painting(C); + for (const int64_t i : drawings.index_range()) { + const MutableDrawingInfo &info = drawings[i]; GreasePencilStrokeParams params = GreasePencilStrokeParams::from_context( scene, depsgraph, @@ -252,6 +348,45 @@ void GreasePencilStrokeOperationCommon::foreach_editable_drawing( if (fn(params)) { changed = true; } + } + + if (changed) { + DEG_id_tag_update(&grease_pencil.id, ID_RECALC_GEOMETRY); + WM_event_add_notifier(&C, NC_GEOM | ND_DATA, &grease_pencil); + } +} + +void GreasePencilStrokeOperationCommon::foreach_editable_drawing( + const bContext &C, + const GrainSize grain_size, + FunctionRef fn) const +{ + using namespace blender::bke::greasepencil; + + const Scene &scene = *CTX_data_scene(&C); + Depsgraph &depsgraph = *CTX_data_depsgraph_pointer(&C); + ARegion ®ion = *CTX_wm_region(&C); + Object &object = *CTX_data_active_object(&C); + GreasePencil &grease_pencil = *static_cast(object.data); + + std::atomic changed = false; + const Vector drawings = get_drawings_for_painting(C); + threading::parallel_for(drawings.index_range(), grain_size.value, [&](const IndexRange range) { + for (const int64_t i : range) { + const MutableDrawingInfo &info = drawings[i]; + GreasePencilStrokeParams params = GreasePencilStrokeParams::from_context( + scene, + depsgraph, + region, + object, + info.layer_index, + info.frame_number, + info.multi_frame_falloff, + info.drawing); + if (fn(params)) { + changed = true; + } + } }); if (changed) { @@ -276,7 +411,7 @@ void GreasePencilStrokeOperationCommon::foreach_editable_drawing( GreasePencil &grease_pencil = *static_cast(object.data); std::atomic changed = false; - const Vector drawings = get_drawings_for_sculpt(C); + const Vector drawings = get_drawings_for_painting(C); threading::parallel_for_each(drawings, [&](const MutableDrawingInfo &info) { const Layer &layer = *grease_pencil.layer(info.layer_index); diff --git a/source/blender/editors/sculpt_paint/grease_pencil_sculpt_grab.cc b/source/blender/editors/sculpt_paint/grease_pencil_sculpt_grab.cc index f39a1860a11..30f3975ebac 100644 --- a/source/blender/editors/sculpt_paint/grease_pencil_sculpt_grab.cc +++ b/source/blender/editors/sculpt_paint/grease_pencil_sculpt_grab.cc @@ -135,11 +135,14 @@ void GrabOperation::on_stroke_begin(const bContext &C, const InputSample &start_ Object &ob_eval = *DEG_get_evaluated_object(&depsgraph, &ob_orig); GreasePencil &grease_pencil = *static_cast(ob_orig.data); + const bool is_masking = GPENCIL_ANY_SCULPT_MASK( + eGP_Sculpt_SelectMaskFlag(scene.toolsettings->gpencil_selectmode_sculpt)); + init_brush(brush); this->prev_mouse_position = start_sample.mouse_position; - const Vector drawings = get_drawings_for_sculpt(C); + const Vector drawings = get_drawings_for_painting(C); this->drawing_data.reinitialize(drawings.size()); threading::parallel_for_each(drawings.index_range(), [&](const int i) { const MutableDrawingInfo &info = drawings[i]; @@ -158,23 +161,22 @@ void GrabOperation::on_stroke_begin(const bContext &C, const InputSample &start_ info.frame_number, info.multi_frame_falloff, info.drawing}; - IndexMaskMemory selection_memory; - IndexMask selection = point_selection_mask(params, selection_memory); + IndexMask selection = point_selection_mask(params, is_masking, selection_memory); Array view_positions = calculate_view_positions(params, selection); /* Cache points under brush influence. */ Vector weights; - IndexMask point_mask = brush_influence_mask(scene, - brush, - start_sample.mouse_position, - 1.0f, - info.multi_frame_falloff, - selection, - view_positions, - weights, - data.memory); + IndexMask point_mask = brush_point_influence_mask(scene, + brush, + start_sample.mouse_position, + 1.0f, + info.multi_frame_falloff, + selection, + view_positions, + weights, + data.memory); if (point_mask.is_empty()) { /* Set empty point mask to skip. */ diff --git a/source/blender/editors/sculpt_paint/grease_pencil_sculpt_pinch.cc b/source/blender/editors/sculpt_paint/grease_pencil_sculpt_pinch.cc index df691bd9b94..c5536ca29ec 100644 --- a/source/blender/editors/sculpt_paint/grease_pencil_sculpt_pinch.cc +++ b/source/blender/editors/sculpt_paint/grease_pencil_sculpt_pinch.cc @@ -38,13 +38,15 @@ void PinchOperation::on_stroke_extended(const bContext &C, const InputSample &ex Paint &paint = *BKE_paint_get_active_from_context(&C); const Brush &brush = *BKE_paint_brush(&paint); const bool invert = this->is_inverted(brush); + const bool is_masking = GPENCIL_ANY_SCULPT_MASK( + eGP_Sculpt_SelectMaskFlag(scene.toolsettings->gpencil_selectmode_sculpt)); this->foreach_editable_drawing( C, [&](const GreasePencilStrokeParams ¶ms, const ed::greasepencil::DrawingPlacement &placement) { IndexMaskMemory selection_memory; - const IndexMask selection = point_selection_mask(params, selection_memory); + const IndexMask selection = point_selection_mask(params, is_masking, selection_memory); if (selection.is_empty()) { return false; } @@ -57,7 +59,7 @@ void PinchOperation::on_stroke_extended(const bContext &C, const InputSample &ex selection.foreach_index(GrainSize(4096), [&](const int64_t point_i) { const float2 &co = view_positions[point_i]; - const float influence = brush_influence( + const float influence = brush_point_influence( scene, brush, co, extension_sample, params.multi_frame_falloff); if (influence <= 0.0f) { return; diff --git a/source/blender/editors/sculpt_paint/grease_pencil_sculpt_push.cc b/source/blender/editors/sculpt_paint/grease_pencil_sculpt_push.cc index e16a6c17bbe..de87f6be25c 100644 --- a/source/blender/editors/sculpt_paint/grease_pencil_sculpt_push.cc +++ b/source/blender/editors/sculpt_paint/grease_pencil_sculpt_push.cc @@ -38,12 +38,15 @@ void PushOperation::on_stroke_extended(const bContext &C, const InputSample &ext Paint &paint = *BKE_paint_get_active_from_context(&C); const Brush &brush = *BKE_paint_brush(&paint); + const bool is_masking = GPENCIL_ANY_SCULPT_MASK( + eGP_Sculpt_SelectMaskFlag(scene.toolsettings->gpencil_selectmode_sculpt)); + this->foreach_editable_drawing( C, [&](const GreasePencilStrokeParams ¶ms, const ed::greasepencil::DrawingPlacement &placement) { IndexMaskMemory selection_memory; - const IndexMask selection = point_selection_mask(params, selection_memory); + const IndexMask selection = point_selection_mask(params, is_masking, selection_memory); if (selection.is_empty()) { return false; } @@ -56,7 +59,7 @@ void PushOperation::on_stroke_extended(const bContext &C, const InputSample &ext selection.foreach_index(GrainSize(4096), [&](const int64_t point_i) { const float2 &co = view_positions[point_i]; - const float influence = brush_influence( + const float influence = brush_point_influence( scene, brush, co, extension_sample, params.multi_frame_falloff); if (influence <= 0.0f) { return; diff --git a/source/blender/editors/sculpt_paint/grease_pencil_sculpt_randomize.cc b/source/blender/editors/sculpt_paint/grease_pencil_sculpt_randomize.cc index 3adc0d53e5f..fa2835f6096 100644 --- a/source/blender/editors/sculpt_paint/grease_pencil_sculpt_randomize.cc +++ b/source/blender/editors/sculpt_paint/grease_pencil_sculpt_randomize.cc @@ -58,6 +58,9 @@ void RandomizeOperation::on_stroke_extended(const bContext &C, const InputSample const Brush &brush = *BKE_paint_brush(&paint); const int sculpt_mode_flag = brush.gpencil_settings->sculpt_mode_flag; + const bool is_masking = GPENCIL_ANY_SCULPT_MASK( + eGP_Sculpt_SelectMaskFlag(scene.toolsettings->gpencil_selectmode_sculpt)); + this->foreach_editable_drawing( C, [&](const GreasePencilStrokeParams ¶ms, @@ -65,7 +68,7 @@ void RandomizeOperation::on_stroke_extended(const bContext &C, const InputSample const uint32_t seed = this->unique_seed(); IndexMaskMemory selection_memory; - const IndexMask selection = point_selection_mask(params, selection_memory); + const IndexMask selection = point_selection_mask(params, is_masking, selection_memory); if (selection.is_empty()) { return false; } @@ -84,7 +87,7 @@ void RandomizeOperation::on_stroke_extended(const bContext &C, const InputSample selection.foreach_index(GrainSize(4096), [&](const int64_t point_i) { const float2 &co = view_positions[point_i]; - const float influence = brush_influence( + const float influence = brush_point_influence( scene, brush, co, extension_sample, params.multi_frame_falloff); if (influence <= 0.0f) { return; @@ -100,7 +103,7 @@ void RandomizeOperation::on_stroke_extended(const bContext &C, const InputSample MutableSpan opacities = params.drawing.opacities_for_write(); selection.foreach_index(GrainSize(4096), [&](const int64_t point_i) { const float2 &co = view_positions[point_i]; - const float influence = brush_influence( + const float influence = brush_point_influence( scene, brush, co, extension_sample, params.multi_frame_falloff); if (influence <= 0.0f) { return; @@ -114,7 +117,7 @@ void RandomizeOperation::on_stroke_extended(const bContext &C, const InputSample const MutableSpan radii = params.drawing.radii_for_write(); selection.foreach_index(GrainSize(4096), [&](const int64_t point_i) { const float2 &co = view_positions[point_i]; - const float influence = brush_influence( + const float influence = brush_point_influence( scene, brush, co, extension_sample, params.multi_frame_falloff); if (influence <= 0.0f) { return; @@ -130,7 +133,7 @@ void RandomizeOperation::on_stroke_extended(const bContext &C, const InputSample attributes.lookup_or_add_for_write_span("rotation", bke::AttrDomain::Point); selection.foreach_index(GrainSize(4096), [&](const int64_t point_i) { const float2 &co = view_positions[point_i]; - const float influence = brush_influence( + const float influence = brush_point_influence( scene, brush, co, extension_sample, params.multi_frame_falloff); if (influence <= 0.0f) { return; diff --git a/source/blender/editors/sculpt_paint/grease_pencil_sculpt_smooth.cc b/source/blender/editors/sculpt_paint/grease_pencil_sculpt_smooth.cc index 9d73cbfcd1f..b2968aeac83 100644 --- a/source/blender/editors/sculpt_paint/grease_pencil_sculpt_smooth.cc +++ b/source/blender/editors/sculpt_paint/grease_pencil_sculpt_smooth.cc @@ -46,9 +46,12 @@ void SmoothOperation::on_stroke_extended(const bContext &C, const InputSample &e const Brush &brush = *BKE_paint_brush(&paint); const int sculpt_mode_flag = brush.gpencil_settings->sculpt_mode_flag; + const bool is_masking = GPENCIL_ANY_SCULPT_MASK( + eGP_Sculpt_SelectMaskFlag(scene.toolsettings->gpencil_selectmode_sculpt)); + this->foreach_editable_drawing(C, [&](const GreasePencilStrokeParams ¶ms) { IndexMaskMemory selection_memory; - const IndexMask selection = point_selection_mask(params, selection_memory); + const IndexMask selection = point_selection_mask(params, is_masking, selection_memory); if (selection.is_empty()) { return false; } @@ -62,7 +65,7 @@ void SmoothOperation::on_stroke_extended(const bContext &C, const InputSample &e const VArray influences = VArray::ForFunc( view_positions.size(), [&](const int64_t point_) { - return brush_influence( + return brush_point_influence( scene, brush, view_positions[point_], extension_sample, params.multi_frame_falloff); }); Array selection_array(curves.points_num()); diff --git a/source/blender/editors/sculpt_paint/grease_pencil_sculpt_strength.cc b/source/blender/editors/sculpt_paint/grease_pencil_sculpt_strength.cc index 3038f4ccecd..e75c208758f 100644 --- a/source/blender/editors/sculpt_paint/grease_pencil_sculpt_strength.cc +++ b/source/blender/editors/sculpt_paint/grease_pencil_sculpt_strength.cc @@ -40,9 +40,12 @@ void StrengthOperation::on_stroke_extended(const bContext &C, const InputSample const Brush &brush = *BKE_paint_brush(&paint); const bool invert = this->is_inverted(brush); + const bool is_masking = GPENCIL_ANY_SCULPT_MASK( + eGP_Sculpt_SelectMaskFlag(scene.toolsettings->gpencil_selectmode_sculpt)); + this->foreach_editable_drawing(C, [&](const GreasePencilStrokeParams ¶ms) { IndexMaskMemory selection_memory; - const IndexMask selection = point_selection_mask(params, selection_memory); + const IndexMask selection = point_selection_mask(params, is_masking, selection_memory); if (selection.is_empty()) { return false; } @@ -52,7 +55,7 @@ void StrengthOperation::on_stroke_extended(const bContext &C, const InputSample selection.foreach_index(GrainSize(4096), [&](const int64_t point_i) { float &opacity = opacities[point_i]; - const float influence = brush_influence( + const float influence = brush_point_influence( scene, brush, view_positions[point_i], extension_sample, params.multi_frame_falloff); /* Brush influence mapped to opacity by a factor of 0.125. */ const float delta_opacity = (invert ? -influence : influence) * 0.125f; diff --git a/source/blender/editors/sculpt_paint/grease_pencil_sculpt_thickness.cc b/source/blender/editors/sculpt_paint/grease_pencil_sculpt_thickness.cc index 78be495bca4..99cfb7b5694 100644 --- a/source/blender/editors/sculpt_paint/grease_pencil_sculpt_thickness.cc +++ b/source/blender/editors/sculpt_paint/grease_pencil_sculpt_thickness.cc @@ -41,9 +41,12 @@ void ThicknessOperation::on_stroke_extended(const bContext &C, const InputSample const Brush &brush = *BKE_paint_brush(&paint); const bool invert = this->is_inverted(brush); + const bool is_masking = GPENCIL_ANY_SCULPT_MASK( + eGP_Sculpt_SelectMaskFlag(scene.toolsettings->gpencil_selectmode_sculpt)); + this->foreach_editable_drawing(C, [&](const GreasePencilStrokeParams ¶ms) { IndexMaskMemory selection_memory; - const IndexMask selection = point_selection_mask(params, selection_memory); + const IndexMask selection = point_selection_mask(params, is_masking, selection_memory); if (selection.is_empty()) { return false; } @@ -55,7 +58,7 @@ void ThicknessOperation::on_stroke_extended(const bContext &C, const InputSample selection.foreach_index(GrainSize(4096), [&](const int64_t point_i) { float &radius = radii[point_i]; - const float influence = brush_influence( + const float influence = brush_point_influence( scene, brush, view_positions[point_i], extension_sample, params.multi_frame_falloff); /* Factor 1/1000 is used to map arbitrary influence value to a sensible radius. */ const float delta_radius = (invert ? -influence : influence) * 0.001f; diff --git a/source/blender/editors/sculpt_paint/grease_pencil_sculpt_twist.cc b/source/blender/editors/sculpt_paint/grease_pencil_sculpt_twist.cc index 30eca5a9cc7..df7100109a0 100644 --- a/source/blender/editors/sculpt_paint/grease_pencil_sculpt_twist.cc +++ b/source/blender/editors/sculpt_paint/grease_pencil_sculpt_twist.cc @@ -48,12 +48,15 @@ void TwistOperation::on_stroke_extended(const bContext &C, const InputSample &ex const Brush &brush = *BKE_paint_brush(&paint); const bool invert = this->is_inverted(brush); + const bool is_masking = GPENCIL_ANY_SCULPT_MASK( + eGP_Sculpt_SelectMaskFlag(scene.toolsettings->gpencil_selectmode_sculpt)); + this->foreach_editable_drawing( C, [&](const GreasePencilStrokeParams ¶ms, const ed::greasepencil::DrawingPlacement &placement) { IndexMaskMemory selection_memory; - const IndexMask selection = point_selection_mask(params, selection_memory); + const IndexMask selection = point_selection_mask(params, is_masking, selection_memory); if (selection.is_empty()) { return false; } @@ -66,7 +69,7 @@ void TwistOperation::on_stroke_extended(const bContext &C, const InputSample &ex selection.foreach_index(GrainSize(4096), [&](const int64_t point_i) { const float2 &co = view_positions[point_i]; - const float influence = brush_influence( + const float influence = brush_point_influence( scene, brush, co, extension_sample, params.multi_frame_falloff); if (influence <= 0.0f) { return; diff --git a/source/blender/editors/sculpt_paint/grease_pencil_vertex_average.cc b/source/blender/editors/sculpt_paint/grease_pencil_vertex_average.cc new file mode 100644 index 00000000000..11af5893328 --- /dev/null +++ b/source/blender/editors/sculpt_paint/grease_pencil_vertex_average.cc @@ -0,0 +1,136 @@ +/* SPDX-FileCopyrightText: 2024 Blender Authors + * + * SPDX-License-Identifier: GPL-2.0-or-later */ + +#include "BKE_brush.hh" +#include "BKE_context.hh" +#include "BKE_curves.hh" +#include "BKE_grease_pencil.hh" +#include "BKE_paint.hh" + +#include "grease_pencil_intern.hh" + +namespace blender::ed::sculpt_paint::greasepencil { + +class VertexAverageOperation : public GreasePencilStrokeOperationCommon { + using GreasePencilStrokeOperationCommon::GreasePencilStrokeOperationCommon; + + public: + void on_stroke_begin(const bContext &C, const InputSample &start_sample) override; + void on_stroke_extended(const bContext &C, const InputSample &extension_sample) override; + void on_stroke_done(const bContext &C) override; +}; + +void VertexAverageOperation::on_stroke_begin(const bContext &C, const InputSample &start_sample) +{ + this->init_stroke(C, start_sample); + this->on_stroke_extended(C, start_sample); +} + +void VertexAverageOperation::on_stroke_extended(const bContext &C, + const InputSample &extension_sample) +{ + const Scene &scene = *CTX_data_scene(&C); + Paint &paint = *BKE_paint_get_active_from_context(&C); + const Brush &brush = *BKE_paint_brush(&paint); + const float radius = brush_radius(scene, brush, extension_sample.pressure); + const float radius_squared = radius * radius; + + const bool is_masking = GPENCIL_ANY_VERTEX_MASK( + eGP_vertex_SelectMaskFlag(scene.toolsettings->gpencil_selectmode_vertex)); + + const bool do_points = do_vertex_color_points(brush); + const bool do_fill = do_vertex_color_fill(brush); + + /* Compute the average color under the brush. */ + float3 average_color; + int color_count = 0; + this->foreach_editable_drawing(C, [&](const GreasePencilStrokeParams ¶ms) { + IndexMaskMemory memory; + const IndexMask point_selection = point_selection_mask(params, is_masking, memory); + if (!point_selection.is_empty() && do_points) { + const Array view_positions = calculate_view_positions(params, point_selection); + const VArray vertex_colors = params.drawing.vertex_colors(); + + point_selection.foreach_index([&](const int64_t point_i) { + const ColorGeometry4f color = vertex_colors[point_i]; + if (color.a > 0.0f && math::distance_squared(extension_sample.mouse_position, + view_positions[point_i]) < radius_squared) + { + average_color += float3(color.r, color.g, color.b); + color_count++; + } + }); + } + const IndexMask fill_selection = fill_selection_mask(params, is_masking, memory); + if (!fill_selection.is_empty() && do_fill) { + const OffsetIndices points_by_curve = params.drawing.strokes().points_by_curve(); + const Array view_positions = calculate_view_positions(params, point_selection); + const VArray fill_colors = params.drawing.fill_colors(); + + fill_selection.foreach_index([&](const int64_t curve_i) { + const IndexRange points = points_by_curve[curve_i]; + const Span curve_view_positions = view_positions.as_span().slice(points); + const ColorGeometry4f color = fill_colors[curve_i]; + if (color.a > 0.0f && closest_distance_to_surface_2d(extension_sample.mouse_position, + curve_view_positions) < radius) + { + average_color += float3(color.r, color.g, color.b); + color_count++; + } + }); + } + return true; + }); + + if (color_count <= 0) { + return; + } + average_color = average_color / color_count; + /* The average color is the color that will be mixed in. */ + const ColorGeometry4f mix_color(average_color.x, average_color.y, average_color.z, 1.0f); + + this->foreach_editable_drawing(C, GrainSize(1), [&](const GreasePencilStrokeParams ¶ms) { + IndexMaskMemory memory; + const IndexMask point_selection = point_selection_mask(params, is_masking, memory); + if (!point_selection.is_empty() && do_points) { + const Array view_positions = calculate_view_positions(params, point_selection); + MutableSpan vertex_colors = params.drawing.vertex_colors_for_write(); + + point_selection.foreach_index(GrainSize(4096), [&](const int64_t point_i) { + const float influence = brush_point_influence( + scene, brush, view_positions[point_i], extension_sample, params.multi_frame_falloff); + + ColorGeometry4f &color = vertex_colors[point_i]; + color = math::interpolate(color, mix_color, influence); + }); + } + + const IndexMask fill_selection = fill_selection_mask(params, is_masking, memory); + if (!fill_selection.is_empty() && do_fill) { + const OffsetIndices points_by_curve = params.drawing.strokes().points_by_curve(); + const Array view_positions = calculate_view_positions(params, point_selection); + MutableSpan fill_colors = params.drawing.fill_colors_for_write(); + + fill_selection.foreach_index(GrainSize(1024), [&](const int64_t curve_i) { + const IndexRange points = points_by_curve[curve_i]; + const Span curve_view_positions = view_positions.as_span().slice(points); + const float influence = brush_fill_influence( + scene, brush, curve_view_positions, extension_sample, params.multi_frame_falloff); + + ColorGeometry4f &color = fill_colors[curve_i]; + color = math::interpolate(color, mix_color, influence); + }); + } + return true; + }); +} + +void VertexAverageOperation::on_stroke_done(const bContext & /*C*/) {} + +std::unique_ptr new_vertex_average_operation() +{ + return std::make_unique(); +} + +} // namespace blender::ed::sculpt_paint::greasepencil diff --git a/source/blender/editors/sculpt_paint/grease_pencil_vertex_blur.cc b/source/blender/editors/sculpt_paint/grease_pencil_vertex_blur.cc new file mode 100644 index 00000000000..cad36071af3 --- /dev/null +++ b/source/blender/editors/sculpt_paint/grease_pencil_vertex_blur.cc @@ -0,0 +1,93 @@ +/* SPDX-FileCopyrightText: 2024 Blender Authors + * + * SPDX-License-Identifier: GPL-2.0-or-later */ + +#include "BKE_brush.hh" +#include "BKE_context.hh" +#include "BKE_curves.hh" +#include "BKE_grease_pencil.hh" +#include "BKE_paint.hh" + +#include "grease_pencil_intern.hh" + +namespace blender::ed::sculpt_paint::greasepencil { + +class VertexBlurOperation : public GreasePencilStrokeOperationCommon { + using GreasePencilStrokeOperationCommon::GreasePencilStrokeOperationCommon; + + public: + void on_stroke_begin(const bContext &C, const InputSample &start_sample) override; + void on_stroke_extended(const bContext &C, const InputSample &extension_sample) override; + void on_stroke_done(const bContext &C) override; +}; + +void VertexBlurOperation::on_stroke_begin(const bContext &C, const InputSample &start_sample) +{ + this->init_stroke(C, start_sample); + this->on_stroke_extended(C, start_sample); +} + +void VertexBlurOperation::on_stroke_extended(const bContext &C, + const InputSample &extension_sample) +{ + const Scene &scene = *CTX_data_scene(&C); + Paint &paint = *BKE_paint_get_active_from_context(&C); + const Brush &brush = *BKE_paint_brush(&paint); + const float radius = brush_radius(scene, brush, extension_sample.pressure); + const float radius_squared = radius * radius; + + const bool is_masking = GPENCIL_ANY_VERTEX_MASK( + eGP_vertex_SelectMaskFlag(scene.toolsettings->gpencil_selectmode_vertex)); + + this->foreach_editable_drawing(C, GrainSize(1), [&](const GreasePencilStrokeParams ¶ms) { + IndexMaskMemory memory; + const IndexMask stroke_selection = stroke_selection_mask(params, is_masking, memory); + if (stroke_selection.is_empty()) { + return false; + } + const IndexMask point_selection = point_selection_mask(params, is_masking, memory); + const Array view_positions = calculate_view_positions(params, point_selection); + const OffsetIndices points_by_curve = params.drawing.strokes().points_by_curve(); + MutableSpan vertex_colors = params.drawing.vertex_colors_for_write(); + stroke_selection.foreach_index(GrainSize(1024), [&](const int64_t curve) { + const IndexRange points = points_by_curve[curve]; + + float3 average_color(0.0f); + int color_count = 0; + for (const int point : points) { + const ColorGeometry4f color = vertex_colors[point]; + const float distance = math::distance_squared(extension_sample.mouse_position, + view_positions[point]); + if (color.a > 0.0f && distance < radius_squared) { + average_color += float3(color.r, color.g, color.b); + color_count++; + } + } + + if (color_count == 0) { + return; + } + average_color = average_color / color_count; + const ColorGeometry4f mix_color(average_color.x, average_color.y, average_color.z, 1.0f); + + for (const int point : points) { + const float influence = brush_point_influence( + scene, brush, view_positions[point], extension_sample, params.multi_frame_falloff); + ColorGeometry4f &color = vertex_colors[point]; + if (color.a > 0.0f && influence > 0.0f) { + color = math::interpolate(color, mix_color, influence); + } + } + }); + return true; + }); +} + +void VertexBlurOperation::on_stroke_done(const bContext & /*C*/) {} + +std::unique_ptr new_vertex_blur_operation() +{ + return std::make_unique(); +} + +} // namespace blender::ed::sculpt_paint::greasepencil diff --git a/source/blender/editors/sculpt_paint/grease_pencil_vertex_paint.cc b/source/blender/editors/sculpt_paint/grease_pencil_vertex_paint.cc new file mode 100644 index 00000000000..5769490c280 --- /dev/null +++ b/source/blender/editors/sculpt_paint/grease_pencil_vertex_paint.cc @@ -0,0 +1,121 @@ +/* SPDX-FileCopyrightText: 2024 Blender Authors + * + * SPDX-License-Identifier: GPL-2.0-or-later */ + +#include "BLI_math_color.hh" + +#include "BKE_brush.hh" +#include "BKE_context.hh" +#include "BKE_curves.hh" +#include "BKE_grease_pencil.hh" +#include "BKE_paint.hh" + +#include "grease_pencil_intern.hh" + +namespace blender::ed::sculpt_paint::greasepencil { + +class VertexPaintOperation : public GreasePencilStrokeOperationCommon { + using GreasePencilStrokeOperationCommon::GreasePencilStrokeOperationCommon; + + public: + void on_stroke_begin(const bContext &C, const InputSample &start_sample) override; + void on_stroke_extended(const bContext &C, const InputSample &extension_sample) override; + void on_stroke_done(const bContext & /*C*/) override {} +}; + +void VertexPaintOperation::on_stroke_begin(const bContext &C, const InputSample &start_sample) +{ + this->init_stroke(C, start_sample); + this->on_stroke_extended(C, start_sample); +} + +void VertexPaintOperation::on_stroke_extended(const bContext &C, + const InputSample &extension_sample) +{ + const Scene &scene = *CTX_data_scene(&C); + Paint &paint = *BKE_paint_get_active_from_context(&C); + const Brush &brush = *BKE_paint_brush(&paint); + const bool invert = this->is_inverted(brush); + + const bool is_masking = GPENCIL_ANY_VERTEX_MASK( + eGP_vertex_SelectMaskFlag(scene.toolsettings->gpencil_selectmode_vertex)); + + const bool do_points = do_vertex_color_points(brush); + const bool do_fill = do_vertex_color_fill(brush); + + float color_linear[3]; + srgb_to_linearrgb_v3_v3(color_linear, BKE_brush_color_get(&scene, &brush)); + const ColorGeometry4f mix_color(color_linear[0], color_linear[1], color_linear[2], 1.0f); + + this->foreach_editable_drawing(C, GrainSize(1), [&](const GreasePencilStrokeParams ¶ms) { + IndexMaskMemory memory; + const IndexMask point_selection = point_selection_mask(params, is_masking, memory); + if (!point_selection.is_empty() && do_points) { + Array view_positions = calculate_view_positions(params, point_selection); + MutableSpan vertex_colors = params.drawing.vertex_colors_for_write(); + + if (invert) { + /* Erase vertex colors. */ + point_selection.foreach_index(GrainSize(4096), [&](const int64_t point_i) { + const float influence = brush_point_influence( + scene, brush, view_positions[point_i], extension_sample, params.multi_frame_falloff); + + ColorGeometry4f &color = vertex_colors[point_i]; + color.a -= influence; + color.a = math::max(color.a, 0.0f); + }); + } + else { + /* Mix brush color into vertex colors by influence using alpha over. */ + point_selection.foreach_index(GrainSize(4096), [&](const int64_t point_i) { + const float influence = brush_point_influence( + scene, brush, view_positions[point_i], extension_sample, params.multi_frame_falloff); + + ColorGeometry4f &color = vertex_colors[point_i]; + color = math::interpolate(color, mix_color, influence); + }); + } + } + + const IndexMask fill_selection = fill_selection_mask(params, is_masking, memory); + if (!fill_selection.is_empty() && do_fill) { + const OffsetIndices points_by_curve = params.drawing.strokes().points_by_curve(); + Array view_positions = calculate_view_positions(params, point_selection); + MutableSpan fill_colors = params.drawing.fill_colors_for_write(); + + if (invert) { + fill_selection.foreach_index(GrainSize(1024), [&](const int64_t curve_i) { + const IndexRange points = points_by_curve[curve_i]; + const Span curve_view_positions = view_positions.as_span().slice(points); + const float influence = brush_fill_influence( + scene, brush, curve_view_positions, extension_sample, params.multi_frame_falloff); + + ColorGeometry4f &color = fill_colors[curve_i]; + color.a -= influence; + color.a = math::max(color.a, 0.0f); + }); + } + else { + fill_selection.foreach_index(GrainSize(1024), [&](const int64_t curve_i) { + const IndexRange points = points_by_curve[curve_i]; + const Span curve_view_positions = view_positions.as_span().slice(points); + const float influence = brush_fill_influence( + scene, brush, curve_view_positions, extension_sample, params.multi_frame_falloff); + + ColorGeometry4f &color = fill_colors[curve_i]; + color = math::interpolate(color, mix_color, influence); + }); + } + } + + return true; + }); +} + +std::unique_ptr new_vertex_paint_operation( + const BrushStrokeMode stroke_mode) +{ + return std::make_unique(stroke_mode); +} + +} // namespace blender::ed::sculpt_paint::greasepencil diff --git a/source/blender/editors/sculpt_paint/grease_pencil_vertex_replace.cc b/source/blender/editors/sculpt_paint/grease_pencil_vertex_replace.cc new file mode 100644 index 00000000000..8f63c130b51 --- /dev/null +++ b/source/blender/editors/sculpt_paint/grease_pencil_vertex_replace.cc @@ -0,0 +1,89 @@ +/* SPDX-FileCopyrightText: 2024 Blender Authors + * + * SPDX-License-Identifier: GPL-2.0-or-later */ + +#include "BKE_brush.hh" +#include "BKE_context.hh" +#include "BKE_curves.hh" +#include "BKE_grease_pencil.hh" +#include "BKE_paint.hh" + +#include "grease_pencil_intern.hh" + +namespace blender::ed::sculpt_paint::greasepencil { + +class VertexReplaceOperation : public GreasePencilStrokeOperationCommon { + using GreasePencilStrokeOperationCommon::GreasePencilStrokeOperationCommon; + + public: + void on_stroke_begin(const bContext &C, const InputSample &start_sample) override; + void on_stroke_extended(const bContext &C, const InputSample &extension_sample) override; + void on_stroke_done(const bContext &C) override; +}; + +void VertexReplaceOperation::on_stroke_begin(const bContext &C, const InputSample &start_sample) +{ + this->init_stroke(C, start_sample); + this->on_stroke_extended(C, start_sample); +} + +void VertexReplaceOperation::on_stroke_extended(const bContext &C, + const InputSample &extension_sample) +{ + const Scene &scene = *CTX_data_scene(&C); + Paint &paint = *BKE_paint_get_active_from_context(&C); + const Brush &brush = *BKE_paint_brush(&paint); + + const bool is_masking = GPENCIL_ANY_VERTEX_MASK( + eGP_vertex_SelectMaskFlag(scene.toolsettings->gpencil_selectmode_vertex)); + + const bool do_points = do_vertex_color_points(brush); + const bool do_fill = do_vertex_color_fill(brush); + + float3 color_linear; + srgb_to_linearrgb_v3_v3(color_linear, BKE_brush_color_get(&scene, &brush)); + const ColorGeometry4f replace_color(color_linear.x, color_linear.y, color_linear.z, 1.0f); + + this->foreach_editable_drawing(C, GrainSize(1), [&](const GreasePencilStrokeParams ¶ms) { + IndexMaskMemory memory; + const IndexMask point_selection = point_selection_mask(params, is_masking, memory); + if (!point_selection.is_empty() && do_points) { + Array view_positions = calculate_view_positions(params, point_selection); + MutableSpan vertex_colors = params.drawing.vertex_colors_for_write(); + point_selection.foreach_index(GrainSize(4096), [&](const int64_t point_i) { + const float influence = brush_point_influence( + scene, brush, view_positions[point_i], extension_sample, params.multi_frame_falloff); + if (influence > 0.0f && vertex_colors[point_i].a > 0.0f) { + vertex_colors[point_i] = replace_color; + } + }); + } + + const IndexMask fill_selection = fill_selection_mask(params, is_masking, memory); + if (!fill_selection.is_empty() && do_fill) { + const OffsetIndices points_by_curve = params.drawing.strokes().points_by_curve(); + Array view_positions = calculate_view_positions(params, point_selection); + MutableSpan fill_colors = params.drawing.fill_colors_for_write(); + + fill_selection.foreach_index(GrainSize(1024), [&](const int64_t curve_i) { + const IndexRange points = points_by_curve[curve_i]; + const Span curve_view_positions = view_positions.as_span().slice(points); + const float influence = brush_fill_influence( + scene, brush, curve_view_positions, extension_sample, params.multi_frame_falloff); + if (influence > 0.0f && fill_colors[curve_i].a > 0.0f) { + fill_colors[curve_i] = replace_color; + } + }); + } + return true; + }); +} + +void VertexReplaceOperation::on_stroke_done(const bContext & /*C*/) {} + +std::unique_ptr new_vertex_replace_operation() +{ + return std::make_unique(); +} + +} // namespace blender::ed::sculpt_paint::greasepencil diff --git a/source/blender/editors/sculpt_paint/grease_pencil_vertex_smear.cc b/source/blender/editors/sculpt_paint/grease_pencil_vertex_smear.cc new file mode 100644 index 00000000000..e289bb4a3ae --- /dev/null +++ b/source/blender/editors/sculpt_paint/grease_pencil_vertex_smear.cc @@ -0,0 +1,200 @@ +/* SPDX-FileCopyrightText: 2024 Blender Authors + * + * SPDX-License-Identifier: GPL-2.0-or-later */ + +#include + +#include "BKE_context.hh" +#include "BKE_curves.hh" +#include "BKE_grease_pencil.hh" +#include "BKE_paint.hh" + +#include "BLI_task.hh" + +#include "grease_pencil_intern.hh" + +namespace blender::ed::sculpt_paint::greasepencil { + +struct ColorGrid { + /* Flat array of colors. The length of this is size^2. */ + Array colors; + /* Size of the grid. Used as the width and height. Should be divisible by 2. */ + int size; + /* The size of each cell in pixels (screen space). Used as the cell width and height. */ + int cell_size_px; + /* The center position of the grid (screen space). */ + float2 center; + + /* Compute the screen space position based on a grid position and a center. */ + float2 pos_to_coords(const int2 pos, const float2 center) const + { + const float2 centered = float2(pos - this->size / 2) + float2(0.5f); + return (centered * this->cell_size_px) + center; + } + + /* Compute a grid position based on a screen space position and a center. */ + int2 coords_to_pos(const float2 coord, const float2 center) const + { + const int2 pos = int2(math::floor((coord - center) / float(this->cell_size_px))); + return pos + ((this->size + 1) / 2); + } + + /* Compute a grid index (into the colors array) based on a grid position. Returns -1 if the + * position is out of bounds. */ + int pos_to_index(const int2 pos) const + { + if (pos.x >= 0 && pos.x < this->size && pos.y >= 0 && pos.y < this->size) { + return pos.y * this->size + pos.x; + } + return -1; + } +}; + +class VertexSmearOperation : public GreasePencilStrokeOperationCommon { + using GreasePencilStrokeOperationCommon::GreasePencilStrokeOperationCommon; + + public: + void on_stroke_begin(const bContext &C, const InputSample &start_sample) override; + void on_stroke_extended(const bContext &C, const InputSample &extension_sample) override; + void on_stroke_done(const bContext &C) override; + + private: + ColorGrid color_grid_; + void init_color_grid(const bContext &C, float2 start_position); +}; + +void VertexSmearOperation::init_color_grid(const bContext &C, const float2 start_position) +{ + const Scene &scene = *CTX_data_scene(&C); + Paint &paint = *BKE_paint_get_active_from_context(&C); + const Brush &brush = *BKE_paint_brush(&paint); + const bool is_masking = GPENCIL_ANY_VERTEX_MASK( + eGP_vertex_SelectMaskFlag(scene.toolsettings->gpencil_selectmode_vertex)); + const float radius = brush_radius(scene, brush, 1.0f); + + /* Setup grid values. */ + /* TODO: Make this a setting. */ + color_grid_.cell_size_px = 10.0f; + color_grid_.center = start_position; + color_grid_.size = int(math::ceil((radius * 2.0f) / color_grid_.cell_size_px)); + + /* Initialize the color array. */ + const int grid_array_length = color_grid_.size * color_grid_.size; + color_grid_.colors.reinitialize(grid_array_length); + color_grid_.colors.fill(float4(0.0f)); + + /* Initialize grid values. */ + this->foreach_editable_drawing(C, [&](const GreasePencilStrokeParams ¶ms) { + IndexMaskMemory memory; + const IndexMask point_selection = point_selection_mask(params, is_masking, memory); + if (point_selection.is_empty()) { + return false; + } + const Array view_positions = calculate_view_positions(params, point_selection); + const Array radii = calculate_view_radii(params, point_selection); + const VArray vertex_colors = params.drawing.vertex_colors(); + /* Compute the colors in the grid by averaging the vertex colors of the points that + * intersect each cell. */ + Array points_per_cell(grid_array_length, 0); + point_selection.foreach_index([&](const int point_i) { + const float2 view_pos = view_positions[point_i]; + const float view_radius = radii[point_i]; + const ColorGeometry4f color = vertex_colors[point_i]; + + const int bounds_size = math::floor(view_radius / color_grid_.cell_size_px) * 2 + 1; + const int2 bounds_center = color_grid_.coords_to_pos(view_pos, color_grid_.center); + const int2 bounds_min = bounds_center - (bounds_size / 2); + const int2 bounds_max = bounds_center + (bounds_size / 2); + if (!(bounds_min.x < color_grid_.size && bounds_max.x >= 0 && + bounds_min.y < color_grid_.size && bounds_max.y >= 0)) + { + /* Point is out of bounds. */ + return; + } + for (int y = bounds_min.y; y <= bounds_max.y; y++) { + for (int x = bounds_min.x; x <= bounds_max.x; x++) { + const int2 grid_pos = int2(x, y); + const int cell_i = color_grid_.pos_to_index(grid_pos); + if (cell_i == -1) { + continue; + } + const float2 cell_pos = color_grid_.pos_to_coords(grid_pos, color_grid_.center); + if (math::distance_squared(cell_pos, view_pos) <= view_radius * view_radius) { + color_grid_.colors[cell_i] += float4(color.r, color.g, color.b, 1.0f); + points_per_cell[cell_i]++; + } + } + } + }); + /* Divide by the total to get the average color per cell. */ + for (const int cell_i : color_grid_.colors.index_range()) { + if (points_per_cell[cell_i] > 0) { + color_grid_.colors[cell_i] *= 1.0f / float(points_per_cell[cell_i]); + } + } + /* Don't trigger updates for the grid initialization. */ + return false; + }); +} + +void VertexSmearOperation::on_stroke_begin(const bContext &C, const InputSample &start_sample) +{ + this->init_stroke(C, start_sample); + this->init_color_grid(C, start_sample.mouse_position); +} + +void VertexSmearOperation::on_stroke_extended(const bContext &C, + const InputSample &extension_sample) +{ + const Scene &scene = *CTX_data_scene(&C); + Paint &paint = *BKE_paint_get_active_from_context(&C); + const Brush &brush = *BKE_paint_brush(&paint); + const float radius = brush_radius(scene, brush, extension_sample.pressure); + + const bool is_masking = GPENCIL_ANY_VERTEX_MASK( + eGP_vertex_SelectMaskFlag(scene.toolsettings->gpencil_selectmode_vertex)); + + this->foreach_editable_drawing(C, GrainSize(1), [&](const GreasePencilStrokeParams ¶ms) { + IndexMaskMemory memory; + const IndexMask point_selection = point_selection_mask(params, is_masking, memory); + if (point_selection.is_empty()) { + return false; + } + const Array view_positions = calculate_view_positions(params, point_selection); + MutableSpan vertex_colors = params.drawing.vertex_colors_for_write(); + point_selection.foreach_index(GrainSize(1024), [&](const int64_t point_i) { + const float2 view_pos = view_positions[point_i]; + const int2 grid_pos = color_grid_.coords_to_pos(view_pos, extension_sample.mouse_position); + const int cell_i = color_grid_.pos_to_index(grid_pos); + if (cell_i == -1 || color_grid_.colors[cell_i][3] == 0.0f) { + return; + } + const ColorGeometry4f mix_color = ColorGeometry4f(color_grid_.colors[cell_i]); + + const float distance_falloff = math::clamp( + 1.0f - (math::distance(color_grid_.center, view_pos) / radius * 2), 0.0f, 1.0f); + const float influence = brush_point_influence(scene, + brush, + view_pos, + extension_sample, + params.multi_frame_falloff) * + distance_falloff; + if (influence > 0.0f) { + ColorGeometry4f &color = vertex_colors[point_i]; + const float alpha = color.a; + color = math::interpolate(color, mix_color, influence); + color.a = alpha; + } + }); + return true; + }); +} + +void VertexSmearOperation::on_stroke_done(const bContext & /*C*/) {} + +std::unique_ptr new_vertex_smear_operation() +{ + return std::make_unique(); +} + +} // namespace blender::ed::sculpt_paint::greasepencil diff --git a/source/blender/editors/sculpt_paint/paint_cursor.cc b/source/blender/editors/sculpt_paint/paint_cursor.cc index 39c2abe18a2..4f561a88f7f 100644 --- a/source/blender/editors/sculpt_paint/paint_cursor.cc +++ b/source/blender/editors/sculpt_paint/paint_cursor.cc @@ -1451,7 +1451,7 @@ static void paint_update_mouse_cursor(PaintCursorContext *pcontext) * with the UI (dragging a number button for e.g.), see: #102792. */ return; } - if (pcontext->mode == PaintMode::GPencil) { + if (ELEM(pcontext->mode, PaintMode::GPencil, PaintMode::VertexGPencil)) { WM_cursor_set(pcontext->win, WM_CURSOR_DOT); } else { @@ -1528,8 +1528,10 @@ static void grease_pencil_brush_cursor_draw(PaintCursorContext *pcontext) return; } - /* default radius and color */ - pcontext->pixel_radius = brush->size; + /* Hide the cursor while drawing. */ + if (grease_pencil->runtime->is_drawing_stroke) { + return; + } float3 color(1.0f); const int x = pcontext->x; @@ -1544,10 +1546,10 @@ static void grease_pencil_brush_cursor_draw(PaintCursorContext *pcontext) /* If we use the eraser from the draw tool with a "scene" radius unit, we need to draw the * cursor with the appropriate size. */ if (grease_pencil->runtime->temp_use_eraser && (brush->flag & BRUSH_LOCK_SIZE) != 0) { - pcontext->pixel_radius = grease_pencil->runtime->temp_eraser_size; + pcontext->pixel_radius = int(grease_pencil->runtime->temp_eraser_size); } else { - pcontext->pixel_radius = float(pcontext->brush->size); + pcontext->pixel_radius = brush->size; } grease_pencil_eraser_draw(pcontext); return; @@ -1596,6 +1598,10 @@ static void grease_pencil_brush_cursor_draw(PaintCursorContext *pcontext) color = scale * float3(paint->paint_cursor_col); } } + else if (pcontext->mode == PaintMode::VertexGPencil) { + pcontext->pixel_radius = BKE_brush_size_get(pcontext->vc.scene, brush); + color = BKE_brush_color_get(pcontext->vc.scene, brush); + } GPU_line_width(1.0f); /* Inner Ring: Color from UI panel */ @@ -1611,13 +1617,12 @@ static void grease_pencil_brush_cursor_draw(PaintCursorContext *pcontext) static void paint_draw_2D_view_brush_cursor(PaintCursorContext *pcontext) { switch (pcontext->mode) { - case PaintMode::GPencil: { + case PaintMode::GPencil: + case PaintMode::VertexGPencil: grease_pencil_brush_cursor_draw(pcontext); break; - } - default: { + default: paint_draw_2D_view_brush_cursor_default(pcontext); - } } } diff --git a/source/blender/editors/space_view3d/space_view3d.cc b/source/blender/editors/space_view3d/space_view3d.cc index 2143057f2e3..080d2eba5a8 100644 --- a/source/blender/editors/space_view3d/space_view3d.cc +++ b/source/blender/editors/space_view3d/space_view3d.cc @@ -418,6 +418,10 @@ 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(®ion->handlers, keymap); + keymap = WM_keymap_ensure( + wm->defaultconf, "Grease Pencil Vertex Paint", SPACE_EMPTY, RGN_TYPE_WINDOW); + WM_event_add_keymap_handler(®ion->handlers, keymap); + keymap = WM_keymap_ensure( wm->defaultconf, "Grease Pencil Brush Stroke", SPACE_EMPTY, RGN_TYPE_WINDOW); WM_event_add_keymap_handler(®ion->handlers, keymap); @@ -1731,6 +1735,9 @@ void ED_view3d_buttons_region_layout_ex(const bContext *C, case CTX_MODE_WEIGHT_GREASE_PENCIL: ARRAY_SET_ITEMS(contexts, ".greasepencil_weight"); break; + case CTX_MODE_VERTEX_GREASE_PENCIL: + ARRAY_SET_ITEMS(contexts, ".grease_pencil_vertex"); + break; case CTX_MODE_EDIT_POINT_CLOUD: ARRAY_SET_ITEMS(contexts, ".point_cloud_edit"); break; diff --git a/source/blender/makesrna/intern/rna_context.cc b/source/blender/makesrna/intern/rna_context.cc index 6aa1688870f..f9d35e55f8c 100644 --- a/source/blender/makesrna/intern/rna_context.cc +++ b/source/blender/makesrna/intern/rna_context.cc @@ -48,6 +48,7 @@ const EnumPropertyItem rna_enum_context_mode_items[] = { {CTX_MODE_PAINT_GREASE_PENCIL, "PAINT_GREASE_PENCIL", 0, "Grease Pencil Paint", ""}, {CTX_MODE_SCULPT_GREASE_PENCIL, "SCULPT_GREASE_PENCIL", 0, "Grease Pencil Sculpt", ""}, {CTX_MODE_WEIGHT_GREASE_PENCIL, "WEIGHT_GREASE_PENCIL", 0, "Grease Pencil Weight Paint", ""}, + {CTX_MODE_VERTEX_GREASE_PENCIL, "VERTEX_GREASE_PENCIL", 0, "Grease Pencil Vertex Paint", ""}, {0, nullptr, 0, nullptr, nullptr}, }; diff --git a/source/blender/windowmanager/intern/wm_keymap_utils.cc b/source/blender/windowmanager/intern/wm_keymap_utils.cc index 074ad9df090..33fe2d20c5a 100644 --- a/source/blender/windowmanager/intern/wm_keymap_utils.cc +++ b/source/blender/windowmanager/intern/wm_keymap_utils.cc @@ -162,6 +162,9 @@ wmKeyMap *WM_keymap_guess_from_context(const bContext *C) case CTX_MODE_WEIGHT_GREASE_PENCIL: km_id = "Grease Pencil Weight Mode"; break; + case CTX_MODE_VERTEX_GREASE_PENCIL: + km_id = "Grease Pencil Vertex Mode"; + break; } } else if (sl->spacetype == SPACE_IMAGE) { diff --git a/source/blender/windowmanager/intern/wm_toolsystem.cc b/source/blender/windowmanager/intern/wm_toolsystem.cc index 62923ef80f2..bfc6cedc776 100644 --- a/source/blender/windowmanager/intern/wm_toolsystem.cc +++ b/source/blender/windowmanager/intern/wm_toolsystem.cc @@ -670,6 +670,7 @@ static const char *toolsystem_default_tool(const bToolKey *tkey) case CTX_MODE_WEIGHT_GPENCIL_LEGACY: case CTX_MODE_WEIGHT_GREASE_PENCIL: case CTX_MODE_VERTEX_GPENCIL_LEGACY: + case CTX_MODE_VERTEX_GREASE_PENCIL: case CTX_MODE_SCULPT_CURVES: return "builtin.brush"; case CTX_MODE_PARTICLE: