GPv3: Weight Paint tools (Draw, Blur, Average, Smear, Sample weight)
This PR implements the Weight Paint tools for GPv3. Tools: - Draw, for assigning weight to stroke points - Blur, smooths out weight using adjacent stroke point weights - Average, smooths weight using the average weight under the brush - Smear, like finger painting, drags weights in the direction of the brush - Sample weight, sets the brush weight to the weight under the cursor The weights are assigned to the active vertex group. When there is no active vertex group, a group is automatically created. When the Auto Normalize option is enabled, it is ensured that all bone-deforming vertex groups add up to the weight of 1.0. When a vertex group is locked, it's weights will not be altered by Auto Normalize. The PR already supports multi frame editing, including the use of a falloff (defined by a curve). The implementation is in accordance with the Weight Paint tools in GPv2. Pull Request: https://projects.blender.org/blender/blender/pulls/118347
This commit is contained in:
parent
07f2a0ee9f
commit
5220caeabb
@ -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),
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
)
|
||||
|
@ -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",
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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):
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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()) {
|
||||
|
@ -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) {
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -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 */
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
/* 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) {
|
||||
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;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
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;
|
||||
|
||||
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? */
|
||||
if (sum_weights_total == 1.0f) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Any unlocked vertex group to normalize? */
|
||||
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) {
|
||||
/* Zero out the weights we are allowed to touch and return false, indicating a second pass is
|
||||
* needed. */
|
||||
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. */
|
||||
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,
|
||||
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. */
|
||||
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);
|
||||
|
||||
/* 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;
|
||||
}
|
||||
|
||||
/* 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 =
|
||||
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;
|
||||
|
||||
/* 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);
|
||||
}
|
@ -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(
|
||||
const Scene &scene, GreasePencil &grease_pencil);
|
||||
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
|
||||
|
@ -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
|
||||
)
|
||||
|
@ -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
|
||||
* \{ */
|
||||
|
||||
static bool weight_stroke_test_start(bContext *C, wmOperator *op, const float mouse[2])
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
@ -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
|
||||
|
||||
|
@ -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. */
|
||||
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
|
||||
{
|
||||
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];
|
||||
|
||||
/* 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
|
139
source/blender/editors/sculpt_paint/grease_pencil_weight_blur.cc
Normal file
139
source/blender/editors/sculpt_paint/grease_pencil_weight_blur.cc
Normal 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;
|
||||
}
|
||||
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) *
|
||||
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
|
||||
* 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
|
106
source/blender/editors/sculpt_paint/grease_pencil_weight_draw.cc
Normal file
106
source/blender/editors/sculpt_paint/grease_pencil_weight_draw.cc
Normal 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
|
@ -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);
|
||||
|
||||
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});
|
||||
}
|
||||
}
|
||||
|
||||
/* 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
|
@ -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) {
|
||||
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
|
@ -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;
|
||||
}
|
||||
|
@ -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(®ion->handlers, keymap);
|
||||
|
||||
keymap = WM_keymap_ensure(
|
||||
wm->defaultconf, "Grease Pencil Weight Paint", SPACE_EMPTY, RGN_TYPE_WINDOW);
|
||||
WM_event_add_keymap_handler(®ion->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(®ion->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");
|
||||
|
Loading…
Reference in New Issue
Block a user