GPv3: Weight Paint tools (Draw, Blur, Average, Smear, Sample weight) #118347

Merged
Falk David merged 48 commits from SietseB/blender:gpv3-weight-paint-tools into main 2024-04-25 15:21:26 +02:00
25 changed files with 1760 additions and 41 deletions

View File

@ -4699,6 +4699,58 @@ def km_grease_pencil_sculpt_mode(params):
return keymap
def km_grease_pencil_weight_paint(params):
# NOTE: This keymap falls through to "Pose" when an armature modifying the GP object
# is selected in weight paint mode. When editing the key-map take care that pose operations
# (such as transforming bones) is not impacted.
items = []
keymap = (
"Grease Pencil Weight Paint",
{"space_type": 'EMPTY', "region_type": 'WINDOW'},
{"items": items},
)
items.extend([
# Paint weight
("grease_pencil.weight_brush_stroke", {"type": 'LEFTMOUSE', "value": 'PRESS'}, None),
("grease_pencil.weight_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_weight_paint"),
("wm.radial_control", {"type": 'F', "value": 'PRESS', "ctrl": True},
radial_control_properties("gpencil_weight_paint", 'weight', 'use_unified_weight')),
# Toggle Add/Subtract for weight draw tool
("grease_pencil.weight_toggle_direction", {"type": 'D', "value": 'PRESS'}, None),
# Sample weight
("grease_pencil.weight_sample", {"type": 'X', "value": 'PRESS', "shift": True}, None),
# Context menu
*_template_items_context_panel("VIEW3D_PT_gpencil_weight_context_menu", params.context_menu_event),
# Show/hide layer
*_template_items_hide_reveal_actions("grease_pencil.layer_hide", "grease_pencil.layer_reveal"),
])
if params.select_mouse == 'LEFTMOUSE':
# Bone selection for combined weight paint + pose mode (Alt).
items.extend([
("view3d.select", {"type": 'LEFTMOUSE', "value": 'PRESS', "alt": True}, None),
("view3d.select", {"type": 'LEFTMOUSE', "value": 'PRESS', "shift": True, "alt": True},
{"properties": [("toggle", True)]}),
# Ctrl-Shift-LMB is needed for MMB emulation (which conflicts with Alt).
# NOTE: this works reasonably well for pose-mode where typically selecting a single bone is sufficient.
# For selecting faces/vertices, this is less useful. Selection tools are needed in this case.
("view3d.select", {"type": 'LEFTMOUSE', "value": 'PRESS', "ctrl": True, "shift": True}, None),
])
return keymap
# ------------------------------------------------------------------------------
# Object/Pose Modes
@ -8091,6 +8143,7 @@ def km_3d_view_tool_paint_weight_sample_weight(params):
{"space_type": 'VIEW_3D', "region_type": 'WINDOW'},
{"items": [
("paint.weight_sample", {"type": params.tool_mouse, "value": 'PRESS'}, None),
("grease_pencil.weight_sample", {"type": params.tool_mouse, "value": 'PRESS'}, None),
]},
)
@ -8785,6 +8838,7 @@ def generate_keymaps(params=None):
km_grease_pencil_paint_mode(params),
km_grease_pencil_edit_mode(params),
km_grease_pencil_sculpt_mode(params),
km_grease_pencil_weight_paint(params),
# Object mode.
km_object_mode(params),
km_object_non_modal(params),

View File

@ -84,7 +84,7 @@ class GreasePencilDisplayPanel:
settings = tool_settings.gpencil_paint
elif context.mode == 'SCULPT_GPENCIL':
settings = tool_settings.gpencil_sculpt_paint
elif context.mode == 'WEIGHT_GPENCIL':
elif context.mode == 'WEIGHT_GPENCIL' or context.mode == 'WEIGHT_GREASE_PENCIL':
settings = tool_settings.gpencil_weight_paint
elif context.mode == 'VERTEX_GPENCIL':
settings = tool_settings.gpencil_vertex_paint
@ -102,7 +102,7 @@ class GreasePencilDisplayPanel:
settings = tool_settings.gpencil_paint
elif context.mode == 'SCULPT_GPENCIL':
settings = tool_settings.gpencil_sculpt_paint
elif context.mode == 'WEIGHT_GPENCIL':
elif context.mode == 'WEIGHT_GPENCIL' or context.mode == 'WEIGHT_GREASE_PENCIL':
settings = tool_settings.gpencil_weight_paint
elif context.mode == 'VERTEX_GPENCIL':
settings = tool_settings.gpencil_vertex_paint
@ -156,7 +156,7 @@ class GreasePencilBrushFalloff:
settings = tool_settings.gpencil_paint
if context.mode == 'SCULPT_GPENCIL':
settings = tool_settings.gpencil_sculpt_paint
elif context.mode == 'WEIGHT_GPENCIL':
elif context.mode == 'WEIGHT_GPENCIL' or context.mode == 'WEIGHT_GREASE_PENCIL':
settings = tool_settings.gpencil_weight_paint
elif context.mode == 'VERTEX_GPENCIL':
settings = tool_settings.gpencil_vertex_paint
@ -931,7 +931,7 @@ class GreasePencilFlipTintColors(Operator):
settings = tool_settings.gpencil_paint
if context.mode == 'SCULPT_GPENCIL':
settings = tool_settings.gpencil_sculpt_paint
elif context.mode == 'WEIGHT_GPENCIL':
elif context.mode == 'WEIGHT_GPENCIL' or context.mode == 'WEIGHT_GREASE_PENCIL':
settings = tool_settings.gpencil_weight_paint
elif context.mode == 'VERTEX_GPENCIL':
settings = tool_settings.gpencil_vertex_paint
@ -945,7 +945,7 @@ class GreasePencilFlipTintColors(Operator):
settings = tool_settings.gpencil_paint
if context.mode == 'SCULPT_GPENCIL':
settings = tool_settings.gpencil_sculpt_paint
elif context.mode == 'WEIGHT_GPENCIL':
elif context.mode == 'WEIGHT_GPENCIL' or context.mode == 'WEIGHT_GREASE_PENCIL':
settings = tool_settings.gpencil_weight_paint
elif context.mode == 'VERTEX_GPENCIL':
settings = tool_settings.gpencil_vertex_paint

View File

@ -86,6 +86,8 @@ class UnifiedPaintPanel:
return tool_settings.gpencil_paint
elif mode == 'SCULPT_GREASE_PENCIL':
return tool_settings.gpencil_sculpt_paint
elif mode == 'WEIGHT_GREASE_PENCIL':
return tool_settings.gpencil_weight_paint
return None
@staticmethod
@ -1580,6 +1582,46 @@ def brush_basic_gpencil_vertex_settings(layout, _context, brush, *, compact=Fals
row.prop(gp_settings, "vertex_mode", text="Mode")
def brush_basic_grease_pencil_weight_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,
)
capabilities = brush.sculpt_capabilities
pressure_name = "use_pressure_strength" if capabilities.has_strength_pressure else None
UnifiedPaintPanel.prop_unified(
layout,
context,
brush,
"strength",
pressure_name=pressure_name,
unified_name="use_unified_strength",
text="Strength",
header=compact,
)
if brush.gpencil_weight_tool in {'WEIGHT'}:
UnifiedPaintPanel.prop_unified(
layout,
context,
brush,
"weight",
unified_name="use_unified_weight",
text="Weight",
slider=True,
header=compact,
)
layout.prop(brush, "direction", expand=True, text="" if compact else "Direction")
classes = (
VIEW3D_MT_tools_projectpaint_clone,
)

View File

@ -2678,9 +2678,9 @@ class _defs_grease_pencil_weight:
type=bpy.types.Brush,
# Uses GPv2 tool settings
attr="gpencil_weight_tool",
# tooldef_keywords=dict(
# operator="grease_pencil.weight_paint",
# ),
tooldef_keywords=dict(
operator="grease_pencil.weight_brush_stroke",
),
)

View File

@ -12,6 +12,7 @@ from bl_ui.properties_paint_common import (
UnifiedPaintPanel,
brush_basic_texpaint_settings,
brush_basic_gpencil_weight_settings,
brush_basic_grease_pencil_weight_settings,
)
from bl_ui.properties_grease_pencil_common import (
AnnotationDataPanel,
@ -128,7 +129,7 @@ class VIEW3D_HT_tool_header(Header):
if tool in {'SMOOTH', 'RANDOMIZE'}:
layout.popover("VIEW3D_PT_tools_grease_pencil_sculpt_brush_popover")
layout.popover("VIEW3D_PT_tools_grease_pencil_sculpt_appearance")
elif tool_mode == 'WEIGHT_GPENCIL':
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':
@ -496,6 +497,25 @@ class _draw_tool_settings_context_mode:
return True
@staticmethod
def WEIGHT_GREASE_PENCIL(context, layout, tool):
if (tool is None) or (not tool.has_datablock):
return False
paint = context.tool_settings.gpencil_weight_paint
layout.template_ID_preview(paint, "brush", rows=3, cols=8, hide_buttons=True)
brush = paint.brush
if brush is None:
return False
brush_basic_grease_pencil_weight_settings(layout, context, brush, compact=True)
layout.popover("VIEW3D_PT_tools_grease_pencil_weight_options", text="Options")
layout.popover("VIEW3D_PT_tools_grease_pencil_brush_weight_falloff", text="Falloff")
return True
@staticmethod
def VERTEX_GPENCIL(context, layout, tool):
if (tool is None) or (not tool.has_datablock):
@ -874,11 +894,11 @@ class VIEW3D_HT_header(Header):
row = layout.row(align=True)
row.prop(tool_settings, "use_gpencil_draw_additive", text="", icon='FREEZE')
if object_mode in {'PAINT_GPENCIL', 'EDIT', 'WEIGHT_PAINT'}:
if object_mode in {'PAINT_GPENCIL', 'EDIT', 'WEIGHT_GPENCIL'}:
row = layout.row(align=True)
row.prop(tool_settings, "use_grease_pencil_multi_frame_editing", text="")
if object_mode in {'EDIT', 'WEIGHT_PAINT'}:
if object_mode in {'EDIT', 'WEIGHT_GPENCIL'}:
sub = row.row(align=True)
sub.enabled = tool_settings.use_grease_pencil_multi_frame_editing
sub.popover(
@ -7945,6 +7965,7 @@ class VIEW3D_PT_overlay_grease_pencil_options(Panel):
layout.label(text={
'PAINT_GREASE_PENCIL': iface_("Draw Grease Pencil"),
'EDIT_GREASE_PENCIL': iface_("Edit Grease Pencil"),
'WEIGHT_GREASE_PENCIL': iface_("Weight Grease Pencil"),
'OBJECT': iface_("Grease Pencil"),
}[context.mode], translate=False)
@ -8495,7 +8516,11 @@ class VIEW3D_PT_gpencil_weight_context_menu(Panel):
layout = self.layout
# Weight settings
brush_basic_gpencil_weight_settings(layout, context, brush)
if context.mode == 'WEIGHT_GPENCIL':
brush_basic_gpencil_weight_settings(layout, context, brush)
else:
# Grease Pencil v3
brush_basic_grease_pencil_weight_settings(layout, context, brush)
# Layers
draw_gpencil_layer_active(context, layout)

View File

@ -76,7 +76,7 @@ class VIEW3D_MT_brush_gpencil_context_menu(Menu):
settings = tool_settings.gpencil_paint
if context.mode == 'SCULPT_GPENCIL':
settings = tool_settings.gpencil_sculpt_paint
elif context.mode == 'WEIGHT_GPENCIL':
elif context.mode == 'WEIGHT_GPENCIL' or context.mode == 'WEIGHT_GREASE_PENCIL':
settings = tool_settings.gpencil_weight_paint
elif context.mode == 'VERTEX_GPENCIL':
settings = tool_settings.gpencil_vertex_paint
@ -2110,6 +2110,9 @@ class GreasePencilWeightPanel:
@classmethod
def poll(cls, context):
if context.space_data.type in {'VIEW_3D', 'PROPERTIES'}:
if context.object and context.object.type == 'GREASEPENCIL' and context.mode == 'WEIGHT_GREASE_PENCIL':
return True
if context.gpencil_data is None:
return False
@ -2136,7 +2139,7 @@ class VIEW3D_PT_tools_grease_pencil_weight_paint_select(View3DPanel, Panel, Grea
col = row.column()
col.menu("VIEW3D_MT_brush_gpencil_context_menu", icon='DOWNARROW_HLT', text="")
if context.mode == 'WEIGHT_GPENCIL':
if context.mode in {'WEIGHT_GPENCIL', 'WEIGHT_GREASE_PENCIL'}:
brush = tool_settings.gpencil_weight_paint.brush
if brush is not None:
col.prop(brush, "use_custom_icon", toggle=True, icon='FILE_IMAGE', text="")
@ -2160,10 +2163,17 @@ class VIEW3D_PT_tools_grease_pencil_weight_paint_settings(Panel, View3DPanel, Gr
settings = tool_settings.gpencil_weight_paint
brush = settings.brush
from bl_ui.properties_paint_common import (
brush_basic_gpencil_weight_settings,
)
brush_basic_gpencil_weight_settings(layout, context, brush)
if context.mode == 'WEIGHT_GPENCIL':
from bl_ui.properties_paint_common import (
brush_basic_gpencil_weight_settings,
)
brush_basic_gpencil_weight_settings(layout, context, brush)
else:
# Grease Pencil v3
from bl_ui.properties_paint_common import (
brush_basic_grease_pencil_weight_settings,
)
brush_basic_grease_pencil_weight_settings(layout, context, brush)
class VIEW3D_PT_tools_grease_pencil_brush_weight_falloff(GreasePencilBrushFalloff, Panel, View3DPaintPanel):

View File

@ -16,6 +16,9 @@ namespace blender::bke::greasepencil {
/** Make sure drawings only contain vertex groups of the #GreasePencil. */
void validate_drawing_vertex_groups(GreasePencil &grease_pencil);
/** Find or create a vertex group in a drawing. */
int ensure_vertex_group(const StringRef name, ListBase &vertex_group_names);
/** Assign selected vertices to the vertex group. */
void assign_to_vertex_group(GreasePencil &grease_pencil, StringRef name, float weight);

View File

@ -52,6 +52,19 @@ void validate_drawing_vertex_groups(GreasePencil &grease_pencil)
}
}
int ensure_vertex_group(const StringRef name, ListBase &vertex_group_names)
{
int def_nr = BLI_findstringindex(&vertex_group_names, name.data(), offsetof(bDeformGroup, name));
if (def_nr < 0) {
bDeformGroup *defgroup = MEM_cnew<bDeformGroup>(__func__);
STRNCPY(defgroup->name, name.data());
BLI_addtail(&vertex_group_names, defgroup);
def_nr = BLI_listbase_count(&vertex_group_names) - 1;
BLI_assert(def_nr >= 0);
}
return def_nr;
}
void assign_to_vertex_group(GreasePencil &grease_pencil, const StringRef name, const float weight)
{
for (GreasePencilDrawingBase *base : grease_pencil.drawings()) {

View File

@ -715,9 +715,21 @@ bool BKE_object_defgroup_check_lock_relative_multi(int defbase_tot,
bool BKE_object_defgroup_active_is_locked(const Object *ob)
{
Mesh *mesh = static_cast<Mesh *>(ob->data);
bDeformGroup *dg = static_cast<bDeformGroup *>(
BLI_findlink(&mesh->vertex_group_names, mesh->vertex_group_active_index - 1));
bDeformGroup *dg;
switch (ob->type) {
SietseB marked this conversation as resolved Outdated

Seems like this should use a switch rather than an early return

Seems like this should use a switch rather than an early return
case OB_GREASE_PENCIL: {
GreasePencil *grease_pencil = static_cast<GreasePencil *>(ob->data);
dg = static_cast<bDeformGroup *>(BLI_findlink(&grease_pencil->vertex_group_names,
grease_pencil->vertex_group_active_index - 1));
break;
}
default: {
Mesh *mesh = static_cast<Mesh *>(ob->data);
dg = static_cast<bDeformGroup *>(
BLI_findlink(&mesh->vertex_group_names, mesh->vertex_group_active_index - 1));
break;
}
}
return dg->flag & DG_LOCK_WEIGHT;
}

View File

@ -635,6 +635,13 @@ void GPENCIL_OT_sculptmode_toggle(wmOperatorType *ot)
/* Stroke Weight Paint Mode Management */
static bool grease_pencil_poll_weight_cursor(bContext *C)
{
Object *ob = CTX_data_active_object(C);
return ob && (ob->mode & OB_MODE_WEIGHT_GPENCIL_LEGACY) && (ob->type == OB_GREASE_PENCIL) &&
CTX_wm_region_view3d(C) && WM_toolsystem_active_tool_is_brush(C);
}
static bool gpencil_weightmode_toggle_poll(bContext *C)
{
/* if using gpencil object, use this gpd */
@ -648,6 +655,7 @@ static bool gpencil_weightmode_toggle_poll(bContext *C)
static int gpencil_weightmode_toggle_exec(bContext *C, wmOperator *op)
{
Main *bmain = CTX_data_main(C);
Scene *scene = CTX_data_scene(C);
ToolSettings *ts = CTX_data_tool_settings(C);
const bool back = RNA_boolean_get(op->ptr, "back");
@ -702,12 +710,17 @@ static int gpencil_weightmode_toggle_exec(bContext *C, wmOperator *op)
if (mode == OB_MODE_WEIGHT_GPENCIL_LEGACY) {
/* Be sure we have brushes. */
BKE_paint_ensure(ts, (Paint **)&ts->gp_weightpaint);
Paint *weight_paint = BKE_paint_get_active_from_paintmode(scene, PaintMode::WeightGPencil);
BKE_paint_ensure(ts, &weight_paint);
const bool reset_mode = (BKE_paint_brush(&ts->gp_weightpaint->paint) == nullptr);
if (ob->type == OB_GREASE_PENCIL) {
ED_paint_cursor_start(weight_paint, grease_pencil_poll_weight_cursor);
}
const bool reset_mode = (BKE_paint_brush(weight_paint) == nullptr);
BKE_brush_gpencil_weight_presets(bmain, ts, reset_mode);
BKE_paint_toolslots_brush_validate(bmain, &ts->gp_weightpaint->paint);
BKE_paint_toolslots_brush_validate(bmain, weight_paint);
}
/* setup other modes */

View File

@ -34,6 +34,7 @@ set(SRC
intern/grease_pencil_select.cc
intern/grease_pencil_undo.cc
intern/grease_pencil_utils.cc
intern/grease_pencil_weight_paint.cc
)
set(LIB

View File

@ -98,6 +98,22 @@ bool grease_pencil_sculpting_poll(bContext *C)
return true;
}
bool grease_pencil_weight_painting_poll(bContext *C)
{
if (!active_grease_pencil_poll(C)) {
return false;
}
Object *object = CTX_data_active_object(C);
if ((object->mode & OB_MODE_WEIGHT_GPENCIL_LEGACY) == 0) {
return false;
}
ToolSettings *ts = CTX_data_tool_settings(C);
if (!ts || !ts->gp_weightpaint) {
return false;
}
return true;
}
static void keymap_grease_pencil_edit_mode(wmKeyConfig *keyconf)
{
wmKeyMap *keymap = WM_keymap_ensure(
@ -119,6 +135,13 @@ static void keymap_grease_pencil_sculpt_mode(wmKeyConfig *keyconf)
keymap->poll = grease_pencil_sculpting_poll;
}
static void keymap_grease_pencil_weight_paint_mode(wmKeyConfig *keyconf)
{
wmKeyMap *keymap = WM_keymap_ensure(
keyconf, "Grease Pencil Weight Paint", SPACE_EMPTY, RGN_TYPE_WINDOW);
keymap->poll = grease_pencil_weight_painting_poll;
}
} // namespace blender::ed::greasepencil
void ED_operatortypes_grease_pencil()
@ -130,6 +153,7 @@ void ED_operatortypes_grease_pencil()
ED_operatortypes_grease_pencil_edit();
ED_operatortypes_grease_pencil_material();
ED_operatortypes_grease_pencil_primitives();
ED_operatortypes_grease_pencil_weight_paint();
}
void ED_operatormacros_grease_pencil()
@ -163,5 +187,6 @@ void ED_keymap_grease_pencil(wmKeyConfig *keyconf)
keymap_grease_pencil_edit_mode(keyconf);
keymap_grease_pencil_paint_mode(keyconf);
keymap_grease_pencil_sculpt_mode(keyconf);
keymap_grease_pencil_weight_paint_mode(keyconf);
ED_primitivetool_modal_keymap(keyconf);
}

View File

@ -454,6 +454,91 @@ Vector<MutableDrawingInfo> retrieve_editable_drawings_with_falloff(const Scene &
return editable_drawings;
}
Array<Vector<MutableDrawingInfo>> retrieve_editable_drawings_grouped_per_frame(
const Scene &scene, GreasePencil &grease_pencil)
{
using namespace blender::bke::greasepencil;
int current_frame = scene.r.cfra;
const ToolSettings *toolsettings = scene.toolsettings;
const bool use_multi_frame_editing = (toolsettings->gpencil_flags &
GP_USE_MULTI_FRAME_EDITING) != 0;
const bool use_multi_frame_falloff = use_multi_frame_editing &&
(toolsettings->gp_sculpt.flag &
GP_SCULPT_SETT_FLAG_FRAME_FALLOFF) != 0;
if (use_multi_frame_falloff) {
BKE_curvemapping_init(toolsettings->gp_sculpt.cur_falloff);
}
/* Get a set of unique frame numbers with editable drawings on them. */
VectorSet<int> selected_frames;
int frame_min = current_frame, frame_max = current_frame;
Span<const Layer *> layers = grease_pencil.layers();
if (use_multi_frame_editing) {
for (const int layer_i : layers.index_range()) {
const Layer &layer = *layers[layer_i];
if (!layer.is_editable()) {
continue;
}
for (const auto [frame_number, frame] : layer.frames().items()) {
if (frame_number != current_frame && frame.is_selected()) {
selected_frames.add(frame_number);
frame_min = math::min(frame_min, frame_number);
frame_max = math::max(frame_max, frame_number);
}
}
}
}
selected_frames.add(current_frame);
/* Get multi frame falloff factor per selected frame. */
Array<float> falloff_per_selected_frame(selected_frames.size(), 1.0f);
if (use_multi_frame_falloff) {
int frame_group = 0;
for (const int frame_number : selected_frames) {
falloff_per_selected_frame[frame_group] = get_multi_frame_falloff(
frame_number, current_frame, frame_min, frame_max, toolsettings->gp_sculpt.cur_falloff);
frame_group++;
}
}
/* Get drawings grouped per frame. */
Array<Vector<MutableDrawingInfo>> drawings_grouped_per_frame(selected_frames.size());
Set<int> added_drawings;
for (const int layer_i : layers.index_range()) {
const Layer &layer = *layers[layer_i];
if (!layer.is_editable()) {
continue;
}
/* In multi frame editing mode, add drawings at selected frames. */
if (use_multi_frame_editing) {
for (const auto [frame_number, frame] : layer.frames().items()) {
if (!frame.is_selected() || added_drawings.contains(frame.drawing_index)) {
continue;
}
if (Drawing *drawing = grease_pencil.get_editable_drawing_at(layer, frame_number)) {
const int frame_group = selected_frames.index_of(frame_number);
drawings_grouped_per_frame[frame_group].append(
{*drawing, layer_i, frame_number, falloff_per_selected_frame[frame_group]});
added_drawings.add(frame.drawing_index);
}
}
}
/* Add drawing at current frame. */
const int drawing_index_current_frame = layer.drawing_index_at(current_frame);
if (!added_drawings.contains(drawing_index_current_frame)) {
if (Drawing *drawing = grease_pencil.get_editable_drawing_at(layer, current_frame)) {
const int frame_group = selected_frames.index_of(current_frame);
drawings_grouped_per_frame[frame_group].append(
{*drawing, layer_i, current_frame, falloff_per_selected_frame[frame_group]});
added_drawings.add(drawing_index_current_frame);
}
}
}
return drawings_grouped_per_frame;
}
Vector<MutableDrawingInfo> retrieve_editable_drawings_from_layer(
const Scene &scene,
GreasePencil &grease_pencil,
@ -688,6 +773,39 @@ IndexMask retrieve_visible_strokes(Object &object,
});
}
IndexMask retrieve_visible_points(Object &object,
const bke::greasepencil::Drawing &drawing,
IndexMaskMemory &memory)
{
/* Get all the hidden material indices. */
VectorSet<int> hidden_material_indices = get_hidden_material_indices(object);
if (hidden_material_indices.is_empty()) {
return drawing.strokes().points_range();
}
const bke::CurvesGeometry &curves = drawing.strokes();
const IndexRange points_range = curves.points_range();
const bke::AttributeAccessor attributes = curves.attributes();
/* Propagate the material index to the points. */
const VArray<int> materials = *attributes.lookup_or_default<int>(
"material_index", bke::AttrDomain::Point, 0);
if (const std::optional<int> single_material = materials.get_if_single()) {
if (!hidden_material_indices.contains(*single_material)) {
return points_range;
}
return {};
}
/* Get all the points that are part of a stroke with a visible material. */
return IndexMask::from_predicate(
points_range, GrainSize(4096), memory, [&](const int64_t point_i) {
const int material_index = materials[point_i];
return !hidden_material_indices.contains(material_index);
});
}
IndexMask retrieve_editable_and_selected_strokes(Object &object,
const bke::greasepencil::Drawing &drawing,
IndexMaskMemory &memory)

View File

@ -0,0 +1,365 @@
/* SPDX-FileCopyrightText: 2024 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup edgreasepencil
*/
#include "BKE_brush.hh"
#include "BKE_context.hh"
#include "BKE_crazyspace.hh"
#include "BKE_deform.hh"
#include "BKE_grease_pencil.hh"
#include "BKE_modifier.hh"
#include "BKE_paint.hh"
#include "DNA_meshdata_types.h"
#include "ED_curves.hh"
#include "ED_view3d.hh"
#include "DEG_depsgraph_query.hh"
#include "ED_grease_pencil.hh"
namespace blender::ed::greasepencil {
Set<std::string> get_bone_deformed_vertex_group_names(const Object &object)
{
/* Get all vertex group names in the object. */
const ListBase *defbase = BKE_object_defgroup_list(&object);
Set<std::string> defgroups;
LISTBASE_FOREACH (bDeformGroup *, dg, defbase) {
defgroups.add(dg->name);
}
SietseB marked this conversation as resolved Outdated

This function does multiple things that are not obvious when looking at its name. It should be split.
This function can take a name and just do:

if (name.is_empty()) {
   /* Create a vertex group with a general name. */
   BKE_object_defgroup_add(&ob);
   return 0;
}
const int def_nr = BKE_object_defgroup_name_index(&ob, name);
if (def_nr == -1) {
   BKE_object_defgroup_add_name(&ob, name);
   return BKE_object_defgroup_active_index_get(&ob) - 1;
}
return def_nr;

Then finding the active bone channel name can be it's own function, e.g. (find_active_bone_channel_name).

This function does multiple things that are not obvious when looking at its name. It should be split. This function can take a `name` and just do: ``` if (name.is_empty()) { /* Create a vertex group with a general name. */ BKE_object_defgroup_add(&ob); return 0; } const int def_nr = BKE_object_defgroup_name_index(&ob, name); if (def_nr == -1) { BKE_object_defgroup_add_name(&ob, name); return BKE_object_defgroup_active_index_get(&ob) - 1; } return def_nr; ``` Then finding the active bone channel name can be it's own function, e.g. (`find_active_bone_channel_name`).
/* Inspect all armature modifiers in the object. */
Set<std::string> bone_deformed_vgroups;
VirtualModifierData virtual_modifier_data;
ModifierData *md = BKE_modifiers_get_virtual_modifierlist(&object, &virtual_modifier_data);
for (; md; md = md->next) {
SietseB marked this conversation as resolved Outdated

Use C++ casts, so reinterpret_cast here.

Use C++ casts, so `reinterpret_cast` here.
if (!(md->mode & (eModifierMode_Realtime | eModifierMode_Virtual)) ||
md->type != eModifierType_Armature)
{
continue;
}
ArmatureModifierData *amd = reinterpret_cast<ArmatureModifierData *>(md);
if (!amd->object || !amd->object->pose) {
continue;
}
SietseB marked this conversation as resolved Outdated

else after return is unnecessary

else after return is unnecessary
bPose *pose = amd->object->pose;
LISTBASE_FOREACH (bPoseChannel *, channel, &pose->chanbase) {
if (channel->bone->flag & BONE_NO_DEFORM) {
continue;
}
/* When a vertex group name matches the bone name, it is bone-deformed. */
if (defgroups.contains(channel->name)) {
bone_deformed_vgroups.add(channel->name);
}
}
SietseB marked this conversation as resolved
Review

get_bone_deformed_vertex_group_names

`get_bone_deformed_vertex_group_names`
}
return bone_deformed_vgroups;
}
/* Normalize the weights of vertex groups deformed by bones so that the sum is 1.0f.
* Returns false when the normalization failed due to too many locked vertex groups. In that case a
* second pass can be done with the active vertex group unlocked.
*/
static bool normalize_vertex_weights_try(MDeformVert &dvert,
const int vertex_groups_num,
const Span<bool> vertex_group_is_bone_deformed,
SietseB marked this conversation as resolved Outdated

Would be a bit more readable if this loop was outside the lambda.

Would be a bit more readable if this loop was outside the lambda.
const FunctionRef<bool(int)> vertex_group_is_locked)
{
/* Nothing to normalize when there are less than two vertex group weights. */
if (dvert.totweight <= 1) {
return true;
}
/* Get the sum of weights of bone-deformed vertex groups. */
float sum_weights_total = 0.0f;
float sum_weights_locked = 0.0f;
float sum_weights_unlocked = 0.0f;
int locked_num = 0;
int unlocked_num = 0;
for (const int i : IndexRange(dvert.totweight)) {
MDeformWeight &dw = dvert.dw[i];
/* Auto-normalize is only applied on bone-deformed vertex groups that have weight already. */
if (dw.def_nr >= vertex_groups_num || !vertex_group_is_bone_deformed[dw.def_nr] ||
dw.weight <= FLT_EPSILON)
{
continue;
}
sum_weights_total += dw.weight;
SietseB marked this conversation as resolved
Review

This should be

VirtualModifierData virtual_modifier_data;
ModifierData *md = BKE_modifiers_get_virtual_modifierlist(&object, &virtual_modifier_data);
for (; md; md = md->next) {
   find_pose_channels(md);
}

BKE_modifiers_get_virtual_modifierlist prepends to the objects modifier list.

This should be ``` VirtualModifierData virtual_modifier_data; ModifierData *md = BKE_modifiers_get_virtual_modifierlist(&object, &virtual_modifier_data); for (; md; md = md->next) { find_pose_channels(md); } ``` `BKE_modifiers_get_virtual_modifierlist` prepends to the objects modifier list.
if (vertex_group_is_locked(dw.def_nr)) {
locked_num++;
sum_weights_locked += dw.weight;
}
else {
unlocked_num++;
sum_weights_unlocked += dw.weight;
}
}
/* Already normalized? */
SietseB marked this conversation as resolved Outdated

const MDeformVert &dvert -> MDeformVert &dvert
You actually change this object inside a function, no reason to mark this as const.

`const MDeformVert &dvert` -> `MDeformVert &dvert` You actually change this object inside a function, no reason to mark this as const.

Note: maybe instead of locked_active_vertex_groupand vertex_group_is_locked you could pass a lambda that checkes both the span and the active index.

Note: maybe instead of `locked_active_vertex_group`and `vertex_group_is_locked` you could pass a lambda that checkes both the span and the active index.
if (sum_weights_total == 1.0f) {
return true;
SietseB marked this conversation as resolved
Review

I'd expect the same functionality to be needed for mesh weight painting. Did you look into sharing code with that?

I'd expect the same functionality to be needed for mesh weight painting. Did you look into sharing code with that?
Review

I did indeed, when I wrote the normalization for GPv2. The short answer is: they differ a bit, therefore the code can't be shared just out of the box.
The longer answer is: the starting point of painting weight on meshes differs from GP, since armatures are parented with automatic weights most of the time – so it's more about correcting weights. In GP parenting with automatic weights hardly works, so weight painting starts with vertex groups all at zero. That gives other edge cases.
Of course it must be possible to merge the two, but my proposal would be to create a task for that separate from this PR, with the Animation module heavily involved. I wouldn't feel comfortable making changes just for GP's sake in code that is heavily depended on by many studios and animators.

I did indeed, when I wrote the normalization for GPv2. The short answer is: they differ a bit, therefore the code can't be shared just out of the box. The longer answer is: the starting point of painting weight on meshes differs from GP, since armatures are parented _with automatic weights_ most of the time – so it's more about correcting weights. In GP parenting with automatic weights hardly works, so weight painting starts with vertex groups all at zero. That gives other edge cases. Of course it must be possible to merge the two, but my proposal would be to create a task for that separate from this PR, with the Animation module heavily involved. I wouldn't feel comfortable making changes just for GP's sake in code that is heavily depended on by many studios and animators.
Review

Okay, fair enough!

Okay, fair enough!
}
/* Any unlocked vertex group to normalize? */
SietseB marked this conversation as resolved
Review

In general Span should be used instead of references to other containers: const Span<bool>

In general Span should be used instead of references to other containers: `const Span<bool>`
if (unlocked_num == 0) {
/* We don't need a second pass when there is only one locked group (the active group). */
return (locked_num == 1);
}
/* Locked groups can make it impossible to fully normalize. */
if (sum_weights_locked >= 1.0f - VERTEX_WEIGHT_LOCK_EPSILON) {
SietseB marked this conversation as resolved Outdated

Don't define multiple variables at single line.

Don't define multiple variables at single line.
/* Zero out the weights we are allowed to touch and return false, indicating a second pass is
* needed. */
SietseB marked this conversation as resolved Outdated

for (const int i : IndexRange(dvert.totweight))

`for (const int i : IndexRange(dvert.totweight))`
for (const int i : IndexRange(dvert.totweight)) {
MDeformWeight &dw = dvert.dw[i];
if (dw.def_nr < vertex_groups_num && vertex_group_is_bone_deformed[dw.def_nr] &&
!vertex_group_is_locked(dw.def_nr))
{
dw.weight = 0.0f;
}
}
return (sum_weights_locked == 1.0f);
}
/* When the sum of the unlocked weights isn't zero, we can use a multiplier to normalize them
* to 1.0f. */
if (sum_weights_unlocked != 0.0f) {
const float normalize_factor = (1.0f - sum_weights_locked) / sum_weights_unlocked;
for (const int i : IndexRange(dvert.totweight)) {
MDeformWeight &dw = dvert.dw[i];
if (dw.def_nr < vertex_groups_num && vertex_group_is_bone_deformed[dw.def_nr] &&
dw.weight > FLT_EPSILON && !vertex_group_is_locked(dw.def_nr))
{
dw.weight = math::clamp(dw.weight * normalize_factor, 0.0f, 1.0f);
}
}
return true;
}
/* Spread out the remainder of the locked weights over the unlocked weights. */
SietseB marked this conversation as resolved Outdated

return (locked_num == 1); -> return locked_num == 1;

`return (locked_num == 1);` -> `return locked_num == 1;`
const float weight_remainder = math::clamp(
(1.0f - sum_weights_locked) / unlocked_num, 0.0f, 1.0f);
for (const int i : IndexRange(dvert.totweight)) {
MDeformWeight &dw = dvert.dw[i];
if (dw.def_nr < vertex_groups_num && vertex_group_is_bone_deformed[dw.def_nr] &&
dw.weight > FLT_EPSILON && !vertex_group_is_locked(dw.def_nr))
{
dw.weight = weight_remainder;
}
}
return true;
}
void normalize_vertex_weights(MDeformVert &dvert,
const int active_vertex_group,
const Span<bool> vertex_group_is_locked,
const Span<bool> vertex_group_is_bone_deformed)
{
/* Try to normalize the weights with both active and explicitly locked vertex groups restricted
* from change. */
const auto active_vertex_group_is_locked = [&](const int vertex_group_index) {
return vertex_group_is_locked[vertex_group_index] || vertex_group_index == active_vertex_group;
};
const bool success = normalize_vertex_weights_try(dvert,
vertex_group_is_locked.size(),
vertex_group_is_bone_deformed,
active_vertex_group_is_locked);
if (success) {
return;
}
/* Do a second pass with the active vertex group unlocked. */
const auto active_vertex_group_is_unlocked = [&](const int vertex_group_index) {
return vertex_group_is_locked[vertex_group_index];
};
normalize_vertex_weights_try(dvert,
vertex_group_is_locked.size(),
vertex_group_is_bone_deformed,
SietseB marked this conversation as resolved Outdated

Same as mentioned by @mod_moder, for (const int i : IndexRange(dvert.totweight))

Same as mentioned by @mod_moder, `for (const int i : IndexRange(dvert.totweight))`
active_vertex_group_is_unlocked);
}
struct ClosestGreasePencilDrawing {
const bke::greasepencil::Drawing *drawing = nullptr;
int active_defgroup_index;
ed::curves::FindClosestData elem = {};
};
static int weight_sample_invoke(bContext *C, wmOperator * /*op*/, const wmEvent *event)
{
Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C);
ViewContext vc = ED_view3d_viewcontext_init(C, depsgraph);
/* Get the active vertex group. */
SietseB marked this conversation as resolved Outdated

const Span<bool> &vertex_group_is_locked -> const Span<bool> vertex_group_is_locked

`const Span<bool> &vertex_group_is_locked` -> `const Span<bool> vertex_group_is_locked`
const int object_defgroup_nr = BKE_object_defgroup_active_index_get(vc.obact) - 1;
if (object_defgroup_nr == -1) {
return OPERATOR_CANCELLED;
}
const bDeformGroup *object_defgroup = static_cast<const bDeformGroup *>(
BLI_findlink(BKE_object_defgroup_list(vc.obact), object_defgroup_nr));
/* Collect visible drawings. */
const Object *ob_eval = DEG_get_evaluated_object(vc.depsgraph, const_cast<Object *>(vc.obact));
GreasePencil &grease_pencil = *static_cast<GreasePencil *>(vc.obact->data);
const Vector<DrawingInfo> drawings = retrieve_visible_drawings(*vc.scene, grease_pencil, false);
SietseB marked this conversation as resolved Outdated
if (success) {
  return;
}
```Cpp if (success) { return; } ```
/* Find stroke points closest to mouse cursor position. */
const ClosestGreasePencilDrawing closest = threading::parallel_reduce(
drawings.index_range(),
1L,
ClosestGreasePencilDrawing(),
[&](const IndexRange range, const ClosestGreasePencilDrawing &init) {
ClosestGreasePencilDrawing new_closest = init;
for (const int i : range) {
DrawingInfo info = drawings[i];
const bke::greasepencil::Layer &layer = *grease_pencil.layers()[info.layer_index];
/* Skip drawing when it doesn't use the active vertex group. */
const int drawing_defgroup_nr = BLI_findstringindex(
&info.drawing.strokes().vertex_group_names,
object_defgroup->name,
offsetof(bDeformGroup, name));
if (drawing_defgroup_nr == -1) {
continue;
}
SietseB marked this conversation as resolved
Review

weight_sample_invoke

`weight_sample_invoke`
/* Get deformation by modifiers. */
bke::crazyspace::GeometryDeformation deformation =
bke::crazyspace::get_evaluated_grease_pencil_drawing_deformation(
ob_eval, *vc.obact, info.layer_index, info.frame_number);
IndexMaskMemory memory;
const IndexMask points = retrieve_visible_points(*vc.obact, info.drawing, memory);
if (points.is_empty()) {
continue;
}
const float4x4 layer_to_world = layer.to_world_space(*ob_eval);
const float4x4 projection = ED_view3d_ob_project_mat_get_from_obmat(vc.rv3d,
layer_to_world);
std::optional<ed::curves::FindClosestData> new_closest_elem =
SietseB marked this conversation as resolved
Review

ed::greasepencil should be unnecessary here, since this code is also in the same namespace

`ed::greasepencil` should be unnecessary here, since this code is also in the same namespace
ed::curves::closest_elem_find_screen_space(vc,
info.drawing.strokes().points_by_curve(),
deformation.positions,
projection,
points,
bke::AttrDomain::Point,
event->mval,
new_closest.elem);
if (new_closest_elem) {
new_closest.elem = *new_closest_elem;
new_closest.drawing = &info.drawing;
new_closest.active_defgroup_index = drawing_defgroup_nr;
}
}
return new_closest;
},
[](const ClosestGreasePencilDrawing &a, const ClosestGreasePencilDrawing &b) {
return (a.elem.distance < b.elem.distance) ? a : b;
});
if (!closest.drawing) {
return OPERATOR_CANCELLED;
}
/* From the closest point found, get the vertex weight in the active vertex group. */
const VArray<float> point_weights = bke::varray_for_deform_verts(
closest.drawing->strokes().deform_verts(), closest.active_defgroup_index);
const float new_weight = math::clamp(point_weights[closest.elem.index], 0.0f, 1.0f);
/* Set the new brush weight. */
const ToolSettings *ts = vc.scene->toolsettings;
Brush *brush = BKE_paint_brush(&ts->wpaint->paint);
BKE_brush_weight_set(vc.scene, brush, new_weight);
/* Update brush settings in UI. */
WM_main_add_notifier(NC_BRUSH | NA_EDITED, nullptr);
return OPERATOR_FINISHED;
}
static void GREASE_PENCIL_OT_weight_sample(wmOperatorType *ot)
{
/* Identifiers. */
ot->name = "Weight Paint Sample Weight";
ot->idname = "GREASE_PENCIL_OT_weight_sample";
ot->description =
"Set the weight of the Draw tool to the weight of the vertex under the mouse cursor";
/* Callbacks. */
ot->poll = grease_pencil_weight_painting_poll;
ot->invoke = weight_sample_invoke;
/* Flags. */
ot->flag = OPTYPE_UNDO | OPTYPE_DEPENDS_ON_CURSOR;
}
static int toggle_weight_tool_direction(bContext *C, wmOperator * /*op*/)
{
Paint *paint = BKE_paint_get_active_from_context(C);
Brush *brush = BKE_paint_brush(paint);
/* Toggle direction flag. */
brush->flag ^= BRUSH_DIR_IN;
SietseB marked this conversation as resolved Outdated

IMO it would look a bit nicer with the varray declared as a variable on a separate line

IMO it would look a bit nicer with the varray declared as a variable on a separate line
/* Update brush settings in UI. */
WM_main_add_notifier(NC_BRUSH | NA_EDITED, nullptr);
return OPERATOR_FINISHED;
}
static bool toggle_weight_tool_direction_poll(bContext *C)
{
if (!grease_pencil_weight_painting_poll(C)) {
return false;
}
Paint *paint = BKE_paint_get_active_from_context(C);
if (paint == nullptr) {
return false;
}
Brush *brush = BKE_paint_brush(paint);
if (brush == nullptr) {
return false;
}
return brush->gpencil_weight_tool == GPWEIGHT_TOOL_DRAW;
}
static void GREASE_PENCIL_OT_weight_toggle_direction(wmOperatorType *ot)
{
/* Identifiers. */
ot->name = "Weight Paint Toggle Direction";
ot->idname = "GREASE_PENCIL_OT_weight_toggle_direction";
ot->description = "Toggle Add/Subtract for the weight paint draw tool";
/* Callbacks. */
ot->poll = toggle_weight_tool_direction_poll;
ot->exec = toggle_weight_tool_direction;
/* Flags. */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
}
} // namespace blender::ed::greasepencil
void ED_operatortypes_grease_pencil_weight_paint()
{
using namespace blender::ed::greasepencil;
WM_operatortype_append(GREASE_PENCIL_OT_weight_toggle_direction);
WM_operatortype_append(GREASE_PENCIL_OT_weight_sample);
}

View File

@ -13,6 +13,7 @@
#include "BLI_generic_span.hh"
#include "BLI_index_mask_fwd.hh"
#include "BLI_math_matrix_types.hh"
#include "BLI_set.hh"
#include "ED_keyframes_edit.hh"
@ -53,6 +54,7 @@ void ED_operatortypes_grease_pencil_select();
void ED_operatortypes_grease_pencil_edit();
void ED_operatortypes_grease_pencil_material();
void ED_operatortypes_grease_pencil_primitives();
void ED_operatortypes_grease_pencil_weight_paint();
void ED_operatormacros_grease_pencil();
void ED_keymap_grease_pencil(wmKeyConfig *keyconf);
void ED_primitivetool_modal_keymap(wmKeyConfig *keyconf);
@ -216,6 +218,7 @@ bool active_grease_pencil_layer_poll(bContext *C);
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);
float opacity_from_input_sample(const float pressure,
const Brush *brush,
@ -249,6 +252,8 @@ Vector<MutableDrawingInfo> retrieve_editable_drawings(const Scene &scene,
GreasePencil &grease_pencil);
Vector<MutableDrawingInfo> retrieve_editable_drawings_with_falloff(const Scene &scene,
GreasePencil &grease_pencil);
Array<Vector<MutableDrawingInfo>> retrieve_editable_drawings_grouped_per_frame(
SietseB marked this conversation as resolved Outdated

const is meaningless for arguments passed by value in the declaration, we typically don't include it for that reason.

`const` is meaningless for arguments passed by value in the declaration, we typically don't include it for that reason.
const Scene &scene, GreasePencil &grease_pencil);
SietseB marked this conversation as resolved Outdated

const not needed in the declaration for arguments that are passed by value.

`const` not needed in the declaration for arguments that are passed by value.

const Object &

`const Object &`
Vector<MutableDrawingInfo> retrieve_editable_drawings_from_layer(
const Scene &scene, GreasePencil &grease_pencil, const bke::greasepencil::Layer &layer);
Vector<DrawingInfo> retrieve_visible_drawings(const Scene &scene,
@ -273,6 +278,9 @@ IndexMask retrieve_editable_elements(Object &object,
IndexMask retrieve_visible_strokes(Object &grease_pencil_object,
const bke::greasepencil::Drawing &drawing,
IndexMaskMemory &memory);
IndexMask retrieve_visible_points(Object &object,
const bke::greasepencil::Drawing &drawing,
IndexMaskMemory &memory);
IndexMask retrieve_editable_and_selected_strokes(Object &grease_pencil_object,
const bke::greasepencil::Drawing &drawing,
@ -363,4 +371,14 @@ Array<PointTransferData> compute_topology_change(
const Span<Vector<PointTransferData>> src_to_dst_points,
const bool keep_caps);
/** Returns a set of vertex group names that are deformed by a bone in an armature. */
Set<std::string> get_bone_deformed_vertex_group_names(const Object &object);
/** For a point in a stroke, normalize the weights of vertex groups deformed by bones so that the
* sum is 1.0f. */
void normalize_vertex_weights(MDeformVert &dvert,
int active_vertex_group,
Span<bool> vertex_group_is_locked,
Span<bool> vertex_group_is_bone_deformed);
} // namespace blender::ed::greasepencil

View File

@ -44,6 +44,10 @@ set(SRC
grease_pencil_erase.cc
grease_pencil_paint.cc
grease_pencil_tint.cc
grease_pencil_weight_average.cc
grease_pencil_weight_blur.cc
grease_pencil_weight_draw.cc
grease_pencil_weight_smear.cc
paint_canvas.cc
paint_cursor.cc
paint_curve.cc
@ -93,6 +97,7 @@ set(SRC
curves_sculpt_intern.hh
grease_pencil_intern.hh
grease_pencil_weight_paint.hh
paint_intern.hh
sculpt_intern.hh
)

View File

@ -3,7 +3,9 @@
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BKE_context.hh"
#include "BKE_deform.hh"
#include "BKE_grease_pencil.hh"
#include "BKE_object_deform.h"
#include "BKE_report.hh"
#include "DEG_depsgraph_query.hh"
@ -325,6 +327,135 @@ static void GREASE_PENCIL_OT_sculpt_paint(wmOperatorType *ot)
/** \} */
/* -------------------------------------------------------------------- */
/** \name Weight Brush Stroke Operator
SietseB marked this conversation as resolved Outdated

Flip the case and return early in the failure case

Flip the case and return early in the failure case
* \{ */
static bool weight_stroke_test_start(bContext *C, wmOperator *op, const float mouse[2])
SietseB marked this conversation as resolved Outdated

There should be a switch statement with the different tools here. Each tool creating a GreasePencilStrokeOperation e.g. new_weight_paint_blur_operation()
The implementation of each tool should be in it's own file.

There should be a switch statement with the different tools here. Each tool creating a `GreasePencilStrokeOperation` e.g. `new_weight_paint_blur_operation()` The implementation of each tool should be in it's own file.
{
InputSample start_sample;
start_sample.mouse_position = float2(mouse);
start_sample.pressure = 0.0f;
GreasePencilStrokeOperation *operation = nullptr;
Paint *paint = BKE_paint_get_active_from_context(C);
Brush *brush = BKE_paint_brush(paint);
const BrushStrokeMode brush_mode = BrushStrokeMode(RNA_enum_get(op->ptr, "mode"));
switch (eBrushGPWeightTool(brush->gpencil_weight_tool)) {
case GPWEIGHT_TOOL_DRAW:
operation = greasepencil::new_weight_paint_draw_operation(brush_mode).release();
break;
case GPWEIGHT_TOOL_BLUR:
operation = greasepencil::new_weight_paint_blur_operation().release();
break;
case GPWEIGHT_TOOL_AVERAGE:
operation = greasepencil::new_weight_paint_average_operation().release();
break;
case GPWEIGHT_TOOL_SMEAR:
operation = greasepencil::new_weight_paint_smear_operation().release();
break;
}
if (operation == nullptr) {
return false;
}
PaintStroke *paint_stroke = static_cast<PaintStroke *>(op->customdata);
paint_stroke_set_mode_data(paint_stroke, operation);
operation->on_stroke_begin(*C, start_sample);
return true;
}
static int grease_pencil_weight_brush_stroke_invoke(bContext *C,
wmOperator *op,
const wmEvent *event)
{
const Scene *scene = CTX_data_scene(C);
const Object *object = CTX_data_active_object(C);
if (!object || object->type != OB_GREASE_PENCIL) {
return OPERATOR_CANCELLED;
}
GreasePencil &grease_pencil = *static_cast<GreasePencil *>(object->data);
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;
}
const Vector<ed::greasepencil::MutableDrawingInfo> drawings =
ed::greasepencil::retrieve_editable_drawings(*scene, grease_pencil);
if (drawings.is_empty()) {
BKE_report(op->reports, RPT_ERROR, "No Grease Pencil frame to draw weight on");
return OPERATOR_CANCELLED;
}
const int active_defgroup_nr = BKE_object_defgroup_active_index_get(object) - 1;
if (active_defgroup_nr >= 0 && BKE_object_defgroup_active_is_locked(object)) {
BKE_report(op->reports, RPT_WARNING, "Active group is locked, aborting");
return OPERATOR_CANCELLED;
}
op->customdata = paint_stroke_new(C,
op,
stroke_get_location,
weight_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_weight_brush_stroke_modal(bContext *C,
wmOperator *op,
const wmEvent *event)
{
return paint_stroke_modal(C, op, event, reinterpret_cast<PaintStroke **>(&op->customdata));
}
static void grease_pencil_weight_brush_stroke_cancel(bContext *C, wmOperator *op)
{
paint_stroke_cancel(C, op, static_cast<PaintStroke *>(op->customdata));
}
static bool grease_pencil_weight_brush_stroke_poll(bContext *C)
{
if (!ed::greasepencil::grease_pencil_weight_painting_poll(C)) {
return false;
}
if (!WM_toolsystem_active_tool_is_brush(C)) {
return false;
}
return true;
}
static void GREASE_PENCIL_OT_weight_brush_stroke(wmOperatorType *ot)
{
ot->name = "Grease Pencil Paint Weight";
ot->idname = "GREASE_PENCIL_OT_weight_brush_stroke";
ot->description = "Draw weight on stroke points in the active Grease Pencil object";
ot->poll = grease_pencil_weight_brush_stroke_poll;
ot->invoke = grease_pencil_weight_brush_stroke_invoke;
ot->modal = grease_pencil_weight_brush_stroke_modal;
ot->cancel = grease_pencil_weight_brush_stroke_cancel;
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
paint_stroke_operator_properties(ot);
}
/** \} */
} // namespace blender::ed::sculpt_paint
/* -------------------------------------------------------------------- */
@ -336,6 +467,7 @@ void ED_operatortypes_grease_pencil_draw()
using namespace blender::ed::sculpt_paint;
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);
}
/** \} */

View File

@ -28,6 +28,11 @@ namespace greasepencil {
std::unique_ptr<GreasePencilStrokeOperation> new_paint_operation();
std::unique_ptr<GreasePencilStrokeOperation> new_erase_operation();
std::unique_ptr<GreasePencilStrokeOperation> new_tint_operation();
std::unique_ptr<GreasePencilStrokeOperation> new_weight_paint_draw_operation(
const BrushStrokeMode &brush_mode);
std::unique_ptr<GreasePencilStrokeOperation> new_weight_paint_blur_operation();
std::unique_ptr<GreasePencilStrokeOperation> new_weight_paint_average_operation();
std::unique_ptr<GreasePencilStrokeOperation> new_weight_paint_smear_operation();
} // namespace greasepencil

View File

@ -0,0 +1,116 @@
/* SPDX-FileCopyrightText: 2024 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "grease_pencil_weight_paint.hh"
namespace blender::ed::sculpt_paint::greasepencil {
class AverageWeightPaintOperation : public WeightPaintOperation {
/* Get the average weight of all points in the brush buffer. */
SietseB marked this conversation as resolved
Review

private is already the default for classes

`private` is already the default for classes
float get_average_weight_in_brush_buffer(const Span<DrawingWeightData> drawing_weights)
{
float average_sum = 0.0f;
float point_num = 0;
for (const DrawingWeightData &drawing_weight : drawing_weights) {
for (const BrushPoint &point : drawing_weight.points_in_brush) {
average_sum += drawing_weight.deform_weights[point.drawing_point_index];
point_num++;
}
}
if (point_num == 0) {
return 0.0f;
}
return math::clamp(average_sum / point_num, 0.0f, 1.0f);
}
public:
void on_stroke_begin(const bContext &C, const InputSample &start_sample) override
SietseB marked this conversation as resolved Outdated

Right, like @mod_moder said, these should have the override keyword. It's not strictly necessary in C++ (the compiler will do the right thing), but it's a nice indication that these are implementing a virtual method of a base class.

Right, like @mod_moder said, these should have the `override` keyword. It's not strictly necessary in C++ (the compiler will do the right thing), but it's a nice indication that these are implementing a virtual method of a base class.
https://projects.blender.org/blender/blender/commit/0673cd873dbec2cd2e565ae1a6e8a7b9f4242761

final doesn't really make sense. It just means that a class that is derived from e.g. AverageWeightPaintOperation can't override on_stroke_begin. But such a class wouldn't make sense in the first place.
override means that the program won't compile if the base class doesn't have a virtual function on_stroke_begin which is what we want.

`final` doesn't really make sense. It just means that a class that is derived from e.g. `AverageWeightPaintOperation` can't override `on_stroke_begin`. But such a class wouldn't make sense in the first place. `override` means that the program won't compile if the base class doesn't have a virtual function `on_stroke_begin` which is what we want.

Both final and override will ensure that there is base function to override and check if signature is the same. But yeah, not sure in the reason to choose final, i tried to point on the fact that blender had incorrect signature of virtual functions while a long time, so this rule should be more strict for us\

Both `final` and `override` will ensure that there is base function to override and check if signature is the same. But yeah, not sure in the reason to choose `final`, i tried to point on the fact that blender had incorrect signature of virtual functions while a long time, so this rule should be more strict for us\

No this is the point final will not check the base class. See https://en.cppreference.com/w/cpp/language/final. It will only give an error if there is a derived class that wants to override the method!

No this is the point `final` will **not** check the base class. See https://en.cppreference.com/w/cpp/language/final. It will only give an error if there is a derived class that wants to override the method!

Ah, so i just get final specifier ensures that the function is virtual in wrong way/

Ah, so i just get `final specifier ensures that the function is virtual` in wrong way/
{
using namespace blender::ed::greasepencil;
this->get_brush_settings(C, start_sample);
this->ensure_active_vertex_group_in_object();
this->get_locked_and_bone_deformed_vertex_groups();
/* Get editable drawings grouped per frame number. When multiframe editing is disabled, this
* is just one group for the current frame. When multiframe editing is enabled, the selected
* keyframes are grouped per frame number. This way we can use Average on multiple layers
* together instead of on every layer individually. */
const Scene *scene = CTX_data_scene(&C);
Array<Vector<MutableDrawingInfo>> drawings_per_frame =
retrieve_editable_drawings_grouped_per_frame(*scene, *this->grease_pencil);
this->drawing_weight_data = Array<Array<DrawingWeightData>>(drawings_per_frame.size());
/* Get weight data for all drawings in this frame group. */
for (const int frame_group : drawings_per_frame.index_range()) {
const Vector<MutableDrawingInfo> &drawings = drawings_per_frame[frame_group];
this->init_weight_data_for_drawings(C, drawings, frame_group);
}
}
void on_stroke_extended(const bContext &C, const InputSample &extension_sample) override
{
using namespace blender::ed::greasepencil;
this->get_mouse_input_sample(extension_sample);
/* Iterate over the drawings grouped per frame number. Collect all stroke points under the
* brush and average them. */
std::atomic<bool> changed = false;
threading::parallel_for_each(
this->drawing_weight_data.index_range(), [&](const int frame_group) {
Array<DrawingWeightData> &drawing_weights = this->drawing_weight_data[frame_group];
/* For all layers at this key frame, collect the stroke points under the brush in a
* buffer. */
threading::parallel_for_each(drawing_weights, [&](DrawingWeightData &drawing_weight) {
for (const int point_index : drawing_weight.point_positions.index_range()) {
const float2 &co = drawing_weight.point_positions[point_index];
SietseB marked this conversation as resolved Outdated

The grain_size should somewhat reflect how much work is being done in the loop in each iteration. It's a bit more complicated, but effectively:

  • The entire index range is split into grain_sized chunks
  • A thread is spawned for each chunk and executes it

But spawning threads is somewhat expensive! It can add up quickly if we do it too much.
So if each iteration is doing little work, a high (4096) grain size is best, because we will spawn less threads and they will run through the loop very quickly.
If each iteration is doing a ton of work, then we can think about lower grain sizes and trying to spawn more threads.

The `grain_size` should somewhat reflect how much work is being done in the loop in each iteration. It's a bit more complicated, but effectively: * The entire index range is split into `grain_sized` chunks * A thread is spawned for each chunk and executes it But spawning threads is somewhat expensive! It can add up quickly if we do it too much. So if each iteration is doing little work, a high (`4096`) grain size is best, because we will spawn less threads and they will run through the loop very quickly. If each iteration is doing a ton of work, then we can think about lower grain sizes and trying to spawn more threads.

The comment was probably a bit confusing, I changed it. And I changed the for loop into threading::parallel_for_each(drawing_weights, [&](DrawingWeightData &drawing_weight) {, reflecting the situation better.
This loop is iterating over the layers at a certain key frame. So a grain size of 1 seems appropriate to me?
Within the loop the stroke point are handled in serial: for (const int point_index : drawing_weight.point_positions.index_range()) {.

The comment was probably a bit confusing, I changed it. And I changed the for loop into `threading::parallel_for_each(drawing_weights, [&](DrawingWeightData &drawing_weight) {`, reflecting the situation better. This loop is iterating over the layers at a certain key frame. So a grain size of 1 seems appropriate to me? Within the loop the stroke point are handled in serial: `for (const int point_index : drawing_weight.point_positions.index_range()) {`.

@SietseB Inside we're not doing enough work to justify a grain size of 1. This should be more like 1024 I think.

@SietseB Inside we're not doing enough work to justify a grain size of 1. This should be more like 1024 I think.

For the case we have more than 1024 layers? ;-)
It's just like the threading::parallel_for_each(drawings, [&](const MutableDrawingInfo &info) { we do in many other operators, only now at a certain key frame.
Each thread handles all the stroke points at that layer, so there enough work to do, I would say.

For the case we have more than 1024 layers? ;-) It's just like the `threading::parallel_for_each(drawings, [&](const MutableDrawingInfo &info) {` we do in many other operators, only now at a certain key frame. Each thread handles all the stroke points at that layer, so there enough work to do, I would say.

Ah I see, so the outer loop is only for multi-frame editing essentially and the inner loop for the layers. I suppose it's ok to use parallel_for_each then. But all uses of parallel_for with a grain size of 1 should be replaced by that then.

Ah I see, so the outer loop is only for multi-frame editing essentially and the inner loop for the layers. I suppose it's ok to use `parallel_for_each` then. But all uses of `parallel_for` with a grain size of `1` should be replaced by that then.

For the outer loop that's not an option right now, unfortunately. We need the frame_group index inside the loop; with parallel_for_each we don't have that index.
I'll improve the comment above the outer loop, explaining a bit better what the setup is.

For the outer loop that's not an option right now, unfortunately. We need the `frame_group` index inside the loop; with `parallel_for_each` we don't have that index. I'll improve the comment above the outer loop, explaining a bit better what the setup is.

In that case you should be able to use an index range with parallel_for_each and loop over each index.

In that case you should be able to use an index range with `parallel_for_each` and loop over each index.
/* When the point is under the brush, add it to the brush point buffer. */
this->add_point_under_brush_to_brush_buffer(co, drawing_weight, point_index);
}
});
/* Get the average weight of the points in the brush buffer. */
const float average_weight = this->get_average_weight_in_brush_buffer(drawing_weights);
/* Apply the Average tool to all points in the brush buffer. */
threading::parallel_for_each(drawing_weights, [&](DrawingWeightData &drawing_weight) {
for (const BrushPoint &point : drawing_weight.points_in_brush) {
this->apply_weight_to_point(point, average_weight, drawing_weight);
/* Normalize weights of bone-deformed vertex groups to 1.0f. */
if (this->auto_normalize) {
normalize_vertex_weights(drawing_weight.deform_verts[point.drawing_point_index],
drawing_weight.active_vertex_group,
drawing_weight.locked_vgroups,
drawing_weight.bone_deformed_vgroups);
}
}
if (!drawing_weight.points_in_brush.is_empty()) {
changed = true;
drawing_weight.points_in_brush.clear();
}
});
});
if (changed) {
DEG_id_tag_update(&this->grease_pencil->id, ID_RECALC_GEOMETRY);
WM_event_add_notifier(&C, NC_GEOM | ND_DATA, &grease_pencil);
}
}
void on_stroke_done(const bContext & /*C*/) override {}
};
std::unique_ptr<GreasePencilStrokeOperation> new_weight_paint_average_operation()
{
return std::make_unique<AverageWeightPaintOperation>();
}
} // namespace blender::ed::sculpt_paint::greasepencil

View File

@ -0,0 +1,139 @@
/* SPDX-FileCopyrightText: 2024 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "grease_pencil_weight_paint.hh"
namespace blender::ed::sculpt_paint::greasepencil {
class BlurWeightPaintOperation : public WeightPaintOperation {
/* Apply the Blur tool to a point under the brush. */
void apply_blur_tool(const BrushPoint &point,
DrawingWeightData &drawing_weight,
PointsTouchedByBrush &touched_points)
{
/* Find the nearest neighbours of the to-be-blurred point. The point itself is included. */
KDTreeNearest_2d nearest_points[BLUR_NEIGHBOUR_NUM];
const int point_num = BLI_kdtree_2d_find_nearest_n(
touched_points.kdtree,
drawing_weight.point_positions[point.drawing_point_index],
nearest_points,
BLUR_NEIGHBOUR_NUM);
if (point_num <= 1) {
return;
}
/* Calculate the blurred weight for the point (A). For this we use a weighted average of the
* point weights, based on the distance of the neighbour point to A. So points closer to A
* contribute more to the average than points farther away from A. */
float distance_sum = 0.0f;
for (const int i : IndexRange(point_num)) {
distance_sum += nearest_points[i].dist;
SietseB marked this conversation as resolved Outdated

for (const int i : IndexRange(point_num))

`for (const int i : IndexRange(point_num))`
}
if (distance_sum == 0.0f) {
return;
}
float blur_weight_sum = 0.0f;
for (const int i : IndexRange(point_num)) {
blur_weight_sum += (1.0f - nearest_points[i].dist / distance_sum) *
SietseB marked this conversation as resolved Outdated

Same as above

Same as above
touched_points.weights[nearest_points[i].index];
}
const float blur_weight = blur_weight_sum / (point_num - 1);
apply_weight_to_point(point, blur_weight, drawing_weight);
}
public:
void on_stroke_begin(const bContext &C, const InputSample &start_sample) override
{
using namespace blender::ed::greasepencil;
this->get_brush_settings(C, start_sample);
this->ensure_active_vertex_group_in_object();
this->get_locked_and_bone_deformed_vertex_groups();
/* Get editable drawings grouped per frame number. When multiframe editing is disabled, this
* is just one group for the current frame. When multiframe editing is enabled, the selected
* keyframes are grouped per frame number. This way we can use Blur on multiple layers
* together instead of on every layer individually. */
const Scene *scene = CTX_data_scene(&C);
Array<Vector<MutableDrawingInfo>> drawings_per_frame =
retrieve_editable_drawings_grouped_per_frame(*scene, *this->grease_pencil);
this->drawing_weight_data = Array<Array<DrawingWeightData>>(drawings_per_frame.size());
/* Get weight data for all drawings in this frame group. */
for (const int frame_group : drawings_per_frame.index_range()) {
const Vector<MutableDrawingInfo> &drawings = drawings_per_frame[frame_group];
this->init_weight_data_for_drawings(C, drawings, frame_group);
}
}
void on_stroke_extended(const bContext &C, const InputSample &extension_sample) override
{
using namespace blender::ed::greasepencil;
this->get_mouse_input_sample(extension_sample, 1.3f);
/* Iterate over the drawings grouped per frame number. Collect all stroke points under the
* brush and blur them. */
std::atomic<bool> changed = false;
threading::parallel_for_each(
this->drawing_weight_data.index_range(), [&](const int frame_group) {
Array<DrawingWeightData> &drawing_weights = this->drawing_weight_data[frame_group];
/* For all layers at this key frame, collect the stroke points under the brush in a
* buffer. */
threading::parallel_for_each(drawing_weights, [&](DrawingWeightData &drawing_weight) {
for (const int point_index : drawing_weight.point_positions.index_range()) {
const float2 &co = drawing_weight.point_positions[point_index];
/* When the point is under the brush, add it to the brush point buffer. */
this->add_point_under_brush_to_brush_buffer(co, drawing_weight, point_index);
}
});
/* Create a KDTree with all stroke points touched by the brush during the weight paint
SietseB marked this conversation as resolved Outdated

There is not really a point in doing this in a parallel_for, when you can't do it in parallel and need a mutex.
Especially when the loop doesn't do much work. This is probably faster with a regular-old for loop.
I would also get rid of the mutex argument to add_point_to_stroke_buffer.

There is not really a point in doing this in a `parallel_for`, when you can't do it in parallel and need a mutex. Especially when the loop doesn't do much work. This is probably faster with a regular-old `for` loop. I would also get rid of the `mutex` argument to `add_point_to_stroke_buffer`.

Ha, when the mutex was used all the time, I would fully agree, of course. But adding stroke points to the brush buffer runs perfectly parallel, no mutex used there. The only rare moment the mutex is used, is when a point is added to the KDtree and the buffer size is exceeded. So 1 in 1024 times at max, with the current settings.
IMO, it would really be a missed opportunity to remove the parallel execution here, because that would mean that in the majority of use cases (when no multi frame editing is used) weight painting is running on a single thread, just like in GPv2.

Ha, when the mutex was used all the time, I would fully agree, of course. But adding stroke points to the brush buffer runs perfectly parallel, no mutex used there. The only rare moment the mutex is used, is when a point is added to the KDtree and the buffer size is exceeded. So 1 in 1024 times at max, with the current settings. IMO, it would really be a missed opportunity to remove the parallel execution here, because that would mean that in the majority of use cases (when no multi frame editing is used) weight painting is running on a single thread, just like in GPv2.

Why can add_point_under_brush_to_brush_buffer run in parallel?

Why can `add_point_under_brush_to_brush_buffer` run in parallel?

The layers (threads) are keeping their own record of points under the brush, so just for the points on that layer. That way the threads can operate independently, no shared memory, no need for atomic counters.

For the KDtree buffer that setup is not possible of course. We want to find nearest points like it is one big layer, so the KDtree has to be shared by all the layers (threads). Hence the atomic counter (how many points are added to the tree) and the mutex, in case the maximum size of the KDtree is reached.

The layers (threads) are keeping their own record of points under the brush, so just for the points on that layer. That way the threads can operate independently, no shared memory, no need for atomic counters. For the KDtree buffer that setup is not possible of course. We want to find nearest points like it is one big layer, so the KDtree _has_ to be shared by all the layers (threads). Hence the atomic counter (how many points are added to the tree) and the mutex, in case the maximum size of the KDtree is reached.
* operation. */
PointsTouchedByBrush touched_points = this->create_affected_points_kdtree(
drawing_weights);
/* Apply the Blur tool to all points in the brush buffer. */
threading::parallel_for_each(drawing_weights, [&](DrawingWeightData &drawing_weight) {
for (const BrushPoint &point : drawing_weight.points_in_brush) {
this->apply_blur_tool(point, drawing_weight, touched_points);
/* Normalize weights of bone-deformed vertex groups to 1.0f. */
if (this->auto_normalize) {
normalize_vertex_weights(drawing_weight.deform_verts[point.drawing_point_index],
drawing_weight.active_vertex_group,
drawing_weight.locked_vgroups,
drawing_weight.bone_deformed_vgroups);
}
}
if (!drawing_weight.points_in_brush.is_empty()) {
changed = true;
drawing_weight.points_in_brush.clear();
}
});
BLI_kdtree_2d_free(touched_points.kdtree);
});
if (changed) {
DEG_id_tag_update(&this->grease_pencil->id, ID_RECALC_GEOMETRY);
WM_event_add_notifier(&C, NC_GEOM | ND_DATA, &grease_pencil);
}
}
void on_stroke_done(const bContext & /*C*/) override {}
};
std::unique_ptr<GreasePencilStrokeOperation> new_weight_paint_blur_operation()
{
return std::make_unique<BlurWeightPaintOperation>();
}
} // namespace blender::ed::sculpt_paint::greasepencil

View File

@ -0,0 +1,106 @@
/* SPDX-FileCopyrightText: 2024 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "grease_pencil_weight_paint.hh"
namespace blender::ed::sculpt_paint::greasepencil {
class DrawWeightPaintOperation : public WeightPaintOperation {
public:
DrawWeightPaintOperation(const BrushStrokeMode &brush_mode)
{
this->brush_mode = brush_mode;
}
void on_stroke_begin(const bContext &C, const InputSample &start_sample) override
{
using namespace blender::ed::greasepencil;
this->get_brush_settings(C, start_sample);
this->ensure_active_vertex_group_in_object();
this->get_locked_and_bone_deformed_vertex_groups();
/* Get the add/subtract mode of the draw tool. */
this->invert_brush_weight = (this->brush->flag & BRUSH_DIR_IN) != 0;
if (this->brush_mode == BRUSH_STROKE_INVERT) {
this->invert_brush_weight = !this->invert_brush_weight;
}
/* Get editable drawings grouped per frame number. When multiframe editing is disabled, this
* is just one group for the current frame. When multiframe editing is enabled, the selected
* keyframes are grouped per frame number. */
const Scene *scene = CTX_data_scene(&C);
Array<Vector<MutableDrawingInfo>> drawings_per_frame =
retrieve_editable_drawings_grouped_per_frame(*scene, *this->grease_pencil);
this->drawing_weight_data = Array<Array<DrawingWeightData>>(drawings_per_frame.size());
/* Get weight data for all drawings in this frame group. */
for (const int frame_group : drawings_per_frame.index_range()) {
const Vector<MutableDrawingInfo> &drawings = drawings_per_frame[frame_group];
this->init_weight_data_for_drawings(C, drawings, frame_group);
}
}
void on_stroke_extended(const bContext &C, const InputSample &extension_sample) override
{
using namespace blender::ed::greasepencil;
this->get_mouse_input_sample(extension_sample);
/* Iterate over the drawings grouped per frame number. Collect all stroke points under the
* brush and draw weight on them. */
std::atomic<bool> changed = false;
threading::parallel_for_each(
this->drawing_weight_data.index_range(), [&](const int frame_group) {
Array<DrawingWeightData> &drawing_weights = this->drawing_weight_data[frame_group];
/* For all layers at this key frame, collect the stroke points under the brush in a
* buffer. */
threading::parallel_for_each(drawing_weights, [&](DrawingWeightData &drawing_weight) {
for (const int point_index : drawing_weight.point_positions.index_range()) {
const float2 &co = drawing_weight.point_positions[point_index];
/* When the point is under the brush, add it to the brush point buffer. */
this->add_point_under_brush_to_brush_buffer(co, drawing_weight, point_index);
}
});
/* Apply the Draw tool to all points in the brush buffer. */
threading::parallel_for_each(drawing_weights, [&](DrawingWeightData &drawing_weight) {
for (const BrushPoint &point : drawing_weight.points_in_brush) {
this->apply_weight_to_point(point, this->brush_weight, drawing_weight);
/* Normalize weights of bone-deformed vertex groups to 1.0f. */
if (this->auto_normalize) {
normalize_vertex_weights(drawing_weight.deform_verts[point.drawing_point_index],
drawing_weight.active_vertex_group,
drawing_weight.locked_vgroups,
drawing_weight.bone_deformed_vgroups);
}
}
if (!drawing_weight.points_in_brush.is_empty()) {
changed = true;
drawing_weight.points_in_brush.clear();
}
});
});
if (changed) {
DEG_id_tag_update(&this->grease_pencil->id, ID_RECALC_GEOMETRY);
WM_event_add_notifier(&C, NC_GEOM | ND_DATA, &grease_pencil);
}
}
void on_stroke_done(const bContext & /*C*/) override {}
};
std::unique_ptr<GreasePencilStrokeOperation> new_weight_paint_draw_operation(
const BrushStrokeMode &brush_mode)
{
return std::make_unique<DrawWeightPaintOperation>(brush_mode);
}
} // namespace blender::ed::sculpt_paint::greasepencil

View File

@ -0,0 +1,315 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
#include "BKE_brush.hh"
#include "BKE_colortools.hh"
#include "BKE_context.hh"
#include "BKE_crazyspace.hh"
#include "BKE_curves.hh"
#include "BKE_deform.hh"
#include "BKE_grease_pencil_vertex_groups.hh"
#include "BKE_modifier.hh"
#include "BKE_object_deform.h"
#include "BKE_scene.hh"
#include "DEG_depsgraph_query.hh"
#include "BLI_kdtree.h"
#include "DNA_meshdata_types.h"
#include "ED_grease_pencil.hh"
#include "ED_view3d.hh"
#include "grease_pencil_intern.hh"
namespace blender::ed::sculpt_paint::greasepencil {
static constexpr float FIND_NEAREST_POINT_EPSILON = 1e-6f;
static constexpr int BLUR_NEIGHBOUR_NUM = 5;
static constexpr int SMEAR_NEIGHBOUR_NUM = 8;
class WeightPaintOperation : public GreasePencilStrokeOperation {
public:
struct BrushPoint {
float influence;
int drawing_point_index;
};
struct DrawingWeightData {
int active_vertex_group;
MutableSpan<MDeformVert> deform_verts;
VMutableArray<float> deform_weights;
float multi_frame_falloff;
Vector<bool> locked_vgroups;
Vector<bool> bone_deformed_vgroups;
Array<float2> point_positions;
/* Flag for all stroke points in a drawing: true when the point was touched by the brush during
* a #GreasePencilStrokeOperation. */
Array<bool> points_touched_by_brush;
int points_touched_by_brush_num;
/* Collected points under the brush in one #on_stroke_extended action. */
Vector<BrushPoint> points_in_brush;
};
struct PointsTouchedByBrush {
KDTree_2d *kdtree;
Array<float> weights;
};
Object *object;
GreasePencil *grease_pencil;
Brush *brush;
float initial_brush_radius;
float brush_radius;
float brush_radius_wide;
float initial_brush_strength;
float brush_strength;
float brush_weight;
float2 mouse_position;
float2 mouse_position_previous;
rctf brush_bbox;
/* Flag for Auto-normalize weights of bone deformed vertex groups. */
bool auto_normalize;
/* Brush mode: normal, invert or smooth. */
BrushStrokeMode brush_mode;
/* Add or subtract weight? */
bool invert_brush_weight;
/* Active vertex group in GP object. */
bDeformGroup *object_defgroup;
/* Weight paint data per editable drawing. Stored per frame group. */
Array<Array<DrawingWeightData>> drawing_weight_data;
/* Set of bone-deformed vertex groups (object level). */
Set<std::string> object_bone_deformed_defgroups;
/* Set of locked vertex groups (object level). */
Set<std::string> object_locked_defgroups;
~WeightPaintOperation() override {}
/* Apply a weight to a point under the brush. */
void apply_weight_to_point(const BrushPoint &point,
const float target_weight,
DrawingWeightData &drawing_weight)
{
/* Blend the current point weight with the target weight. */
const float old_weight = drawing_weight.deform_weights[point.drawing_point_index];
const float weight_delta = (this->invert_brush_weight ? (1.0f - target_weight) :
target_weight) -
old_weight;
drawing_weight.deform_weights.set(
point.drawing_point_index,
math::clamp(
old_weight + math::interpolate(0.0f, weight_delta, point.influence), 0.0f, 1.0f));
}
/* Get brush settings (radius, strength etc.) */
void get_brush_settings(const bContext &C, const InputSample &start_sample)
{
using namespace blender::ed::greasepencil;
const Scene *scene = CTX_data_scene(&C);
this->object = CTX_data_active_object(&C);
this->grease_pencil = static_cast<GreasePencil *>(this->object->data);
Paint *paint = BKE_paint_get_active_from_context(&C);
Brush *brush = BKE_paint_brush(paint);
this->brush = brush;
this->initial_brush_radius = BKE_brush_size_get(scene, brush);
this->initial_brush_strength = BKE_brush_alpha_get(scene, brush);
this->brush_weight = BKE_brush_weight_get(scene, brush);
this->mouse_position_previous = start_sample.mouse_position;
this->invert_brush_weight = false;
BKE_curvemapping_init(brush->curve);
/* Auto-normalize weights is only applied when the object is deformed by an armature. */
const ToolSettings *ts = CTX_data_tool_settings(&C);
this->auto_normalize = ts->auto_normalize &&
(BKE_modifiers_is_deformed_by_armature(this->object) != nullptr);
}
/* Get or create active vertex group in GP object. */
void ensure_active_vertex_group_in_object()
{
int object_defgroup_nr = BKE_object_defgroup_active_index_get(this->object) - 1;
if (object_defgroup_nr == -1) {
BKE_object_defgroup_add(this->object);
object_defgroup_nr = 0;
}
this->object_defgroup = static_cast<bDeformGroup *>(
BLI_findlink(BKE_object_defgroup_list(this->object), object_defgroup_nr));
}
/* Get locked and bone-deformed vertex groups in GP object. */
void get_locked_and_bone_deformed_vertex_groups()
{
const ListBase *defgroups = BKE_object_defgroup_list(this->object);
LISTBASE_FOREACH (bDeformGroup *, dg, defgroups) {
if ((dg->flag & DG_LOCK_WEIGHT) != 0) {
this->object_locked_defgroups.add(dg->name);
}
}
this->object_bone_deformed_defgroups = ed::greasepencil::get_bone_deformed_vertex_group_names(
*this->object);
}
/* For each drawing, retrieve pointers to the vertex weight data of the active vertex group,
* so that we can read and write to them later. And create buffers for points under the brush
* during one #on_stroke_extended action. */
void init_weight_data_for_drawings(const bContext &C,
const Span<ed::greasepencil::MutableDrawingInfo> &drawings,
const int frame_group)
{
const Depsgraph *depsgraph = CTX_data_depsgraph_pointer(&C);
const Object *ob_eval = DEG_get_evaluated_object(depsgraph, this->object);
const RegionView3D *rv3d = CTX_wm_region_view3d(&C);
const ARegion *region = CTX_wm_region(&C);
this->drawing_weight_data[frame_group].reinitialize(drawings.size());
threading::parallel_for(drawings.index_range(), 1, [&](const IndexRange range) {
for (const int drawing_index : range) {
const ed::greasepencil::MutableDrawingInfo &drawing_info = drawings[drawing_index];
bke::CurvesGeometry &curves = drawing_info.drawing.strokes_for_write();
/* Find or create the active vertex group in the drawing. */
DrawingWeightData &drawing_weight_data =
this->drawing_weight_data[frame_group][drawing_index];
drawing_weight_data.active_vertex_group = bke::greasepencil::ensure_vertex_group(
this->object_defgroup->name, curves.vertex_group_names);
drawing_weight_data.multi_frame_falloff = drawing_info.multi_frame_falloff;
drawing_weight_data.deform_verts = curves.deform_verts_for_write();
drawing_weight_data.deform_weights = bke::varray_for_mutable_deform_verts(
drawing_weight_data.deform_verts, drawing_weight_data.active_vertex_group);
/* Create boolean arrays indicating whether a vertex group is locked/bone deformed
* or not. */
if (this->auto_normalize) {
LISTBASE_FOREACH (bDeformGroup *, dg, &curves.vertex_group_names) {
drawing_weight_data.locked_vgroups.append(
this->object_locked_defgroups.contains(dg->name));
drawing_weight_data.bone_deformed_vgroups.append(
this->object_bone_deformed_defgroups.contains(dg->name));
}
}
/* Convert stroke points to screen space positions. */
const bke::greasepencil::Layer &layer =
*this->grease_pencil->layers()[drawing_info.layer_index];
const float4x4 layer_to_world = layer.to_world_space(*ob_eval);
const float4x4 projection = ED_view3d_ob_project_mat_get_from_obmat(rv3d, layer_to_world);
SietseB marked this conversation as resolved Outdated

drawing_weight_data.point_positions.reinitialize(deformation.positions.size())

`drawing_weight_data.point_positions.reinitialize(deformation.positions.size())`
bke::crazyspace::GeometryDeformation deformation =
bke::crazyspace::get_evaluated_grease_pencil_drawing_deformation(
ob_eval, *this->object, drawing_info.layer_index, drawing_info.frame_number);
drawing_weight_data.point_positions.reinitialize(deformation.positions.size());
threading::parallel_for(curves.points_range(), 1024, [&](const IndexRange point_range) {
for (const int point : point_range) {
drawing_weight_data.point_positions[point] = ED_view3d_project_float_v2_m4(
region, deformation.positions[point], projection);
}
});
/* Initialize the flag for stroke points being touched by the brush. */
drawing_weight_data.points_touched_by_brush_num = 0;
drawing_weight_data.points_touched_by_brush = Array<bool>(deformation.positions.size(),
false);
}
});
}
/* Get mouse position and pressure. */
void get_mouse_input_sample(const InputSample &input_sample,
const float brush_widen_factor = 1.0f)
{
this->mouse_position = input_sample.mouse_position;
this->brush_radius = this->initial_brush_radius;
if (BKE_brush_use_size_pressure(this->brush)) {
this->brush_radius *= input_sample.pressure;
}
this->brush_strength = this->initial_brush_strength;
if (BKE_brush_use_alpha_pressure(this->brush)) {
this->brush_strength *= input_sample.pressure;
}
this->brush_radius_wide = this->brush_radius * brush_widen_factor;
BLI_rctf_init(&this->brush_bbox,
this->mouse_position.x - this->brush_radius_wide,
this->mouse_position.x + this->brush_radius_wide,
this->mouse_position.y - this->brush_radius_wide,
this->mouse_position.y + this->brush_radius_wide);
}
/* Add a point to the brush buffer when it is within the brush radius. */
void add_point_under_brush_to_brush_buffer(const float2 point_position,
DrawingWeightData &drawing_weight,
const int point_index)
{
if (!BLI_rctf_isect_pt_v(&this->brush_bbox, point_position)) {
return;
}
const float dist_point_to_brush_center = math::distance(point_position, this->mouse_position);
if (dist_point_to_brush_center > this->brush_radius_wide) {
return;
}
/* Point is touched by the (wide) brush, set flag for that. */
if (!drawing_weight.points_touched_by_brush[point_index]) {
drawing_weight.points_touched_by_brush_num++;
}
drawing_weight.points_touched_by_brush[point_index] = true;
if (dist_point_to_brush_center > this->brush_radius) {
return;
}
/* When the point is under the brush, add it to the brush buffer. */
const float influence = drawing_weight.multi_frame_falloff * this->brush_strength *
BKE_brush_curve_strength(
this->brush, dist_point_to_brush_center, this->brush_radius);
if (influence != 0.0f) {
drawing_weight.points_in_brush.append({influence, point_index});
}
SietseB marked this conversation as resolved Outdated

How about create_affected_points_kdtree? Much shorter and simpler?

How about `create_affected_points_kdtree`? Much shorter and simpler?
}
/* Create KDTree for all stroke points touched by the brush during a weight paint operation. */
PointsTouchedByBrush create_affected_points_kdtree(const Span<DrawingWeightData> drawing_weights)
{
/* Get number of stroke points touched by the brush. */
int point_num = 0;
for (const DrawingWeightData &drawing_weight : drawing_weights) {
point_num += drawing_weight.points_touched_by_brush_num;
}
/* Create KDTree of stroke points touched by the brush. */
KDTree_2d *touched_points = BLI_kdtree_2d_new(point_num);
Array<float> touched_points_weights(point_num);
int kdtree_index = 0;
for (const DrawingWeightData &drawing_weight : drawing_weights) {
for (const int point_index : drawing_weight.point_positions.index_range()) {
if (drawing_weight.points_touched_by_brush[point_index]) {
BLI_kdtree_2d_insert(
touched_points, kdtree_index, drawing_weight.point_positions[point_index]);
touched_points_weights[kdtree_index] = drawing_weight.deform_weights[point_index];
kdtree_index++;
}
}
}
BLI_kdtree_2d_balance(touched_points);
return {touched_points, touched_points_weights};
}
};
} // namespace blender::ed::sculpt_paint::greasepencil

View File

@ -0,0 +1,197 @@
/* SPDX-FileCopyrightText: 2024 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "grease_pencil_weight_paint.hh"
namespace blender::ed::sculpt_paint::greasepencil {
class SmearWeightPaintOperation : public WeightPaintOperation {
/* Brush direction (angle) during a stroke movement. */
float2 brush_direction;
bool brush_direction_is_set;
/** Get the direction of the brush while the mouse is moving. The direction is given as a
* normalized XY vector. */
bool get_brush_direction()
{
this->brush_direction = this->mouse_position - this->mouse_position_previous;
/* Skip tiny changes in direction, we want the bigger movements only. */
if (math::length_squared(this->brush_direction) < 9.0f) {
return this->brush_direction_is_set;
}
this->brush_direction = math::normalize(this->brush_direction);
this->brush_direction_is_set = true;
this->mouse_position_previous = this->mouse_position;
return true;
}
/** Apply the Smear tool to a point under the brush. */
void apply_smear_tool(const BrushPoint &point,
DrawingWeightData &drawing_weight,
PointsTouchedByBrush &touched_points)
{
/* Find the nearest neighbours of the to-be-smeared point. */
KDTreeNearest_2d nearest_points[SMEAR_NEIGHBOUR_NUM];
const int point_num = BLI_kdtree_2d_find_nearest_n(
touched_points.kdtree,
drawing_weight.point_positions[point.drawing_point_index],
nearest_points,
SMEAR_NEIGHBOUR_NUM);
/* For smearing a weight to point A, we look for a point B in the trail of the mouse
* movement, matching the last known brush angle best and with the shortest distance to A. */
float point_dot_product[SMEAR_NEIGHBOUR_NUM];
float min_distance = FLT_MAX, max_distance = -FLT_MAX;
int smear_point_num = 0;
for (const int i : IndexRange(point_num)) {
/* Skip the point we are about to smear. */
if (nearest_points[i].dist < FIND_NEAREST_POINT_EPSILON) {
continue;
}
const float2 direction_nearest_to_point = math::normalize(
drawing_weight.point_positions[point.drawing_point_index] -
float2(nearest_points[i].co));
/* Match point direction with brush direction. */
point_dot_product[i] = math::dot(direction_nearest_to_point, this->brush_direction);
if (point_dot_product[i] <= 0.0f) {
continue;
}
smear_point_num++;
min_distance = math::min(min_distance, nearest_points[i].dist);
max_distance = math::max(max_distance, nearest_points[i].dist);
}
if (smear_point_num == 0) {
return;
}
/* Find best match in angle and distance. */
int best_match = -1;
float max_score = 0.0f;
const float distance_normalizer = (min_distance == max_distance) ?
1.0f :
(0.95f / (max_distance - min_distance));
for (const int i : IndexRange(point_num)) {
if (point_dot_product[i] <= 0.0f) {
SietseB marked this conversation as resolved Outdated

`for (const int i : IndexRange(point_num))

`for (const int i : IndexRange(point_num))
continue;
}
const float score = point_dot_product[i] *
(1.0f - (nearest_points[i].dist - min_distance) * distance_normalizer);
if (score > max_score) {
max_score = score;
best_match = i;
}
}
if (best_match == -1) {
return;
}
const float smear_weight = touched_points.weights[nearest_points[best_match].index];
apply_weight_to_point(point, smear_weight, drawing_weight);
}
public:
void on_stroke_begin(const bContext &C, const InputSample &start_sample) override
{
using namespace blender::ed::greasepencil;
this->get_brush_settings(C, start_sample);
this->ensure_active_vertex_group_in_object();
this->get_locked_and_bone_deformed_vertex_groups();
/* Get editable drawings grouped per frame number. When multiframe editing is disabled, this
* is just one group for the current frame. When multiframe editing is enabled, the selected
* keyframes are grouped per frame number. This way we can use Smear on multiple layers
* together instead of on every layer individually. */
const Scene *scene = CTX_data_scene(&C);
Array<Vector<MutableDrawingInfo>> drawings_per_frame =
retrieve_editable_drawings_grouped_per_frame(*scene, *this->grease_pencil);
this->drawing_weight_data = Array<Array<DrawingWeightData>>(drawings_per_frame.size());
/* Get weight data for all drawings in this frame group. */
for (const int frame_group : drawings_per_frame.index_range()) {
const Vector<MutableDrawingInfo> &drawings = drawings_per_frame[frame_group];
this->init_weight_data_for_drawings(C, drawings, frame_group);
}
}
void on_stroke_extended(const bContext &C, const InputSample &extension_sample) override
{
using namespace blender::ed::greasepencil;
this->get_mouse_input_sample(extension_sample);
/* For the Smear tool, we use the direction of the brush during the stroke movement. The
* direction is derived from the current and previous mouse position. */
if (!this->get_brush_direction()) {
/* Abort when no direction is established yet. */
return;
}
/* Iterate over the drawings grouped per frame number. Collect all stroke points under the
* brush and smear them. */
std::atomic<bool> changed = false;
threading::parallel_for_each(
this->drawing_weight_data.index_range(), [&](const int frame_group) {
Array<DrawingWeightData> &drawing_weights = this->drawing_weight_data[frame_group];
std::atomic<bool> balance_kdtree = false;
/* For all layers at this key frame, collect the stroke points under the brush in a
* buffer. */
threading::parallel_for_each(drawing_weights, [&](DrawingWeightData &drawing_weight) {
for (const int point_index : drawing_weight.point_positions.index_range()) {
const float2 &co = drawing_weight.point_positions[point_index];
/* When the point is under the brush, add it to the brush point buffer. */
this->add_point_under_brush_to_brush_buffer(co, drawing_weight, point_index);
}
});
/* Create a KDTree with all stroke points touched by the brush during the weight paint
* operation. */
PointsTouchedByBrush touched_points = this->create_affected_points_kdtree(
drawing_weights);
/* Apply the Smear tool to all points in the brush buffer. */
threading::parallel_for_each(drawing_weights, [&](DrawingWeightData &drawing_weight) {
for (const BrushPoint &point : drawing_weight.points_in_brush) {
this->apply_smear_tool(point, drawing_weight, touched_points);
/* Normalize weights of bone-deformed vertex groups to 1.0f. */
if (this->auto_normalize) {
normalize_vertex_weights(drawing_weight.deform_verts[point.drawing_point_index],
drawing_weight.active_vertex_group,
drawing_weight.locked_vgroups,
drawing_weight.bone_deformed_vgroups);
}
}
if (!drawing_weight.points_in_brush.is_empty()) {
changed = true;
drawing_weight.points_in_brush.clear();
}
});
BLI_kdtree_2d_free(touched_points.kdtree);
});
if (changed) {
DEG_id_tag_update(&this->grease_pencil->id, ID_RECALC_GEOMETRY);
WM_event_add_notifier(&C, NC_GEOM | ND_DATA, &grease_pencil);
}
}
void on_stroke_done(const bContext & /*C*/) override {}
};
std::unique_ptr<GreasePencilStrokeOperation> new_weight_paint_smear_operation()
{
return std::make_unique<SmearWeightPaintOperation>();
}
} // namespace blender::ed::sculpt_paint::greasepencil

View File

@ -1516,35 +1516,31 @@ static void grease_pencil_brush_cursor_draw(PaintCursorContext *pcontext)
return;
}
Paint *paint = pcontext->paint;
Brush *brush = pcontext->brush;
if ((brush == nullptr) || (brush->gpencil_settings == nullptr)) {
return;
}
if ((paint->flags & PAINT_SHOW_BRUSH) == 0) {
return;
}
/* default radius and color */
float color[3] = {1.0f, 1.0f, 1.0f};
float darkcolor[3];
float radius = 2.0f;
float radius = BKE_brush_size_get(pcontext->scene, brush);
const int x = pcontext->x;
const int y = pcontext->y;
/* for paint use paint brush size and color */
if (pcontext->mode == PaintMode::GPencil) {
Paint *paint = pcontext->paint;
Brush *brush = pcontext->brush;
if ((brush == nullptr) || (brush->gpencil_settings == nullptr)) {
return;
}
if ((paint->flags & PAINT_SHOW_BRUSH) == 0) {
return;
}
/* Eraser has a special shape and use a different shader program. */
if (brush->gpencil_tool == GPAINT_TOOL_ERASE) {
grease_pencil_eraser_draw(pcontext);
return;
}
/* Note: For now, there is only as screen space sized cursor. */
radius = BKE_brush_size_get(pcontext->scene, brush);
/* Get current drawing material. */
Material *ma = BKE_grease_pencil_object_material_from_brush_get(object, brush);
if (ma) {
@ -1570,6 +1566,9 @@ static void grease_pencil_brush_cursor_draw(PaintCursorContext *pcontext)
}
}
}
else if (pcontext->mode == PaintMode::WeightGPencil) {
copy_v3_v3(color, brush->add_col);
}
GPU_line_width(1.0f);
/* Inner Ring: Color from UI panel */
@ -1577,6 +1576,7 @@ static void grease_pencil_brush_cursor_draw(PaintCursorContext *pcontext)
imm_draw_circle_wire_2d(pcontext->pos, x, y, radius, 32);
/* Outer Ring: Dark color for contrast on light backgrounds (e.g. gray on white) */
float darkcolor[3];
mul_v3_v3fl(darkcolor, color, 0.40f);
immUniformColor4f(darkcolor[0], darkcolor[1], darkcolor[2], 0.8f);
imm_draw_circle_wire_2d(pcontext->pos, x, y, radius + 1, 32);
@ -1604,7 +1604,8 @@ 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::WeightGPencil: {
grease_pencil_brush_cursor_draw(pcontext);
break;
}

View File

@ -414,6 +414,10 @@ static void view3d_main_region_init(wmWindowManager *wm, ARegion *region)
wm->defaultconf, "Grease Pencil Sculpt Mode", SPACE_EMPTY, RGN_TYPE_WINDOW);
WM_event_add_keymap_handler(&region->handlers, keymap);
keymap = WM_keymap_ensure(
wm->defaultconf, "Grease Pencil Weight Paint", SPACE_EMPTY, RGN_TYPE_WINDOW);
WM_event_add_keymap_handler(&region->handlers, keymap);
/* Edit-font key-map swallows almost all (because of text input). */
keymap = WM_keymap_ensure(wm->defaultconf, "Font", SPACE_EMPTY, RGN_TYPE_WINDOW);
WM_event_add_keymap_handler(&region->handlers, keymap);
@ -1716,7 +1720,7 @@ void ED_view3d_buttons_region_layout_ex(const bContext *C,
ARRAY_SET_ITEMS(contexts, ".paint_common", ".grease_pencil_sculpt");
break;
case CTX_MODE_WEIGHT_GREASE_PENCIL:
ARRAY_SET_ITEMS(contexts, ".grease_pencil_weight");
ARRAY_SET_ITEMS(contexts, ".greasepencil_weight");
break;
case CTX_MODE_EDIT_POINT_CLOUD:
ARRAY_SET_ITEMS(contexts, ".point_cloud_edit");