Sculpt: Polyline hide operator

This PR adds a polyline hide operator for sculpt mode as well as the
necessary generic callback code to allow using this gesture in other
selection tools.

Added features include:
* *Polyline Hide* operator
* `WM_gesture_polyline_*` callback functions for other operators
* Status bar text while using the polyline modal
* Common *Gesture Polyline* keymap for usage with the modal

Unlike the *Box Hide* and *Lasso Hide* operators, the *Polyline Hide*
operator does not provide a simple shortcut to click and show all
hidden elements in a mesh. This is because the existing operators
operate on a click-drag action while the new operator is invoked by
just a click.

Design Task: #119353

Pull Request: https://projects.blender.org/blender/blender/pulls/119483
This commit is contained in:
Sean Kim 2024-04-29 14:04:16 +02:00 committed by Hans Goudey
parent 3d2a2a9bd2
commit 55fc1066ac
15 changed files with 440 additions and 3 deletions

@ -6480,6 +6480,25 @@ def km_gesture_lasso(_params):
return keymap
def km_gesture_polyline(_params):
items = []
keymap = (
"Gesture Polyline",
{"space_type": 'EMPTY', "region_type": 'WINDOW', "modal": True},
{"items": items},
)
items.extend([
("CONFIRM", {"type": 'RET', "value": 'PRESS', "any": True}, None),
("CANCEL", {"type": 'ESC', "value": 'PRESS', "any": True}, None),
("CANCEL", {"type": 'RIGHTMOUSE', "value": 'ANY', "any": True}, None),
("SELECT", {"type": 'LEFTMOUSE', "value": 'PRESS', "any": True}, None),
("MOVE", {"type": 'SPACE', "value": 'ANY', "any": True}, None),
])
return keymap
def km_standard_modal_map(_params):
items = []
keymap = (
@ -8033,6 +8052,21 @@ def km_3d_view_tool_sculpt_line_hide(params):
)
def km_3d_view_tool_sculpt_polyline_hide(params):
# autopep8:off
return (
"3D View Tool: Sculpt, Polyline Hide",
{"space_type": 'VIEW_3D', "region_type": 'WINDOW'},
{"items": [
("paint.hide_show_polyline_gesture", {"type": params.tool_mouse, "value": "PRESS"},
{"properties": [("action", 'HIDE')]}),
("paint.hide_show_polyline_gesture", {"type": params.tool_mouse, "value": "PRESS", "ctrl": True},
{"properties": [("action", 'SHOW')]}),
]},
)
# autopep8: on
def km_3d_view_tool_sculpt_box_mask(params):
return (
"3D View Tool: Sculpt, Box Mask",
@ -8911,6 +8945,7 @@ def generate_keymaps(params=None):
km_gesture_zoom_border(params),
km_gesture_straight_line(params),
km_gesture_lasso(params),
km_gesture_polyline(params),
km_standard_modal_map(params),
km_knife_tool_modal_map(params),
km_custom_normals_modal_map(params),
@ -9019,6 +9054,7 @@ def generate_keymaps(params=None):
km_3d_view_tool_sculpt_box_hide(params),
km_3d_view_tool_sculpt_lasso_hide(params),
km_3d_view_tool_sculpt_line_hide(params),
km_3d_view_tool_sculpt_polyline_hide(params),
km_3d_view_tool_sculpt_box_mask(params),
km_3d_view_tool_sculpt_lasso_mask(params),
km_3d_view_tool_sculpt_box_face_set(params),

@ -1452,6 +1452,21 @@ class _defs_sculpt:
draw_settings=draw_settings,
)
@ToolDef.from_fn
def hide_polyline():
def draw_settings(_context, layout, tool):
props = tool.operator_properties("paint.hide_show_polyline_gesture")
layout.prop(props, "area", expand=False)
return dict(
idname="builtin.polyline_hide",
label="Polyline Hide",
icon="ops.sculpt.polyline_hide",
widget=None,
keymap=(),
draw_settings=draw_settings,
)
@ToolDef.from_fn
def mask_border():
def draw_settings(_context, layout, tool):
@ -3386,6 +3401,7 @@ class VIEW3D_PT_tools_active(ToolSelectPanelHelper, Panel):
_defs_sculpt.hide_border,
_defs_sculpt.hide_lasso,
_defs_sculpt.hide_line,
_defs_sculpt.hide_polyline,
),
(
_defs_sculpt.face_set_box,

@ -3722,6 +3722,12 @@ class VIEW3D_MT_sculpt(Menu):
props = layout.operator("paint.hide_show_line_gesture", text="Line Show")
props.action = 'SHOW'
props = layout.operator("paint.hide_show_polyline_gesture", text="Polyline Hide")
props.action = 'HIDE'
props = layout.operator("paint.hide_show_polyline_gesture", text="Polyline Show")
props.action = 'SHOW'
layout.separator()
props = layout.operator("sculpt.face_set_change_visibility", text="Toggle Visibility")

@ -880,6 +880,17 @@ static int hide_show_gesture_line_exec(bContext *C, wmOperator *op)
return OPERATOR_FINISHED;
}
static int hide_show_gesture_polyline_exec(bContext *C, wmOperator *op)
{
std::unique_ptr<gesture::GestureData> gesture_data = gesture::init_from_polyline(C, op);
if (!gesture_data) {
return OPERATOR_CANCELLED;
}
hide_show_init_properties(*C, *gesture_data, *op);
gesture::apply(*C, *gesture_data, *op);
return OPERATOR_FINISHED;
}
static void hide_show_operator_gesture_properties(wmOperatorType *ot)
{
static const EnumPropertyItem area_items[] = {
@ -964,6 +975,26 @@ void PAINT_OT_hide_show_line_gesture(wmOperatorType *ot)
gesture::operator_properties(ot, gesture::ShapeType::Line);
}
void PAINT_OT_hide_show_polyline_gesture(wmOperatorType *ot)
{
ot->name = "Hide/Show Polyline";
ot->idname = "PAINT_OT_hide_show_polyline_gesture";
ot->description = "Hide/show some vertices";
ot->invoke = WM_gesture_polyline_invoke;
ot->modal = WM_gesture_polyline_modal;
ot->exec = hide_show_gesture_polyline_exec;
/* Sculpt-only for now. */
ot->poll = SCULPT_mode_poll_view3d;
ot->flag = OPTYPE_REGISTER | OPTYPE_DEPENDS_ON_CURSOR;
WM_operator_properties_gesture_polyline(ot);
hide_show_operator_properties(ot);
hide_show_operator_gesture_properties(ot);
gesture::operator_properties(ot, gesture::ShapeType::Lasso);
}
/** \} */
} // namespace blender::ed::sculpt_paint::hide

@ -474,6 +474,7 @@ void PAINT_OT_hide_show_all(wmOperatorType *ot);
void PAINT_OT_hide_show(wmOperatorType *ot);
void PAINT_OT_hide_show_lasso_gesture(wmOperatorType *ot);
void PAINT_OT_hide_show_line_gesture(wmOperatorType *ot);
void PAINT_OT_hide_show_polyline_gesture(wmOperatorType *ot);
void PAINT_OT_visibility_invert(wmOperatorType *ot);
} // namespace blender::ed::sculpt_paint::hide

@ -1559,6 +1559,7 @@ void ED_operatortypes_paint()
WM_operatortype_append(hide::PAINT_OT_hide_show);
WM_operatortype_append(hide::PAINT_OT_hide_show_lasso_gesture);
WM_operatortype_append(hide::PAINT_OT_hide_show_line_gesture);
WM_operatortype_append(hide::PAINT_OT_hide_show_polyline_gesture);
WM_operatortype_append(hide::PAINT_OT_visibility_invert);
/* paint masking */

@ -96,6 +96,11 @@ static void lasso_px_cb(int x, int x_end, int y, void *user_data)
} while (++index != index_end);
}
std::unique_ptr<GestureData> init_from_polyline(bContext *C, wmOperator *op)
{
return init_from_lasso(C, op);
}
std::unique_ptr<GestureData> init_from_lasso(bContext *C, wmOperator *op)
{
const Array<int2> mcoords = WM_gesture_lasso_path_to_array(C, op);

@ -1661,6 +1661,9 @@ void modal_keymap(wmKeyConfig *keyconf);
namespace blender::ed::sculpt_paint::gesture {
enum ShapeType {
Box = 0,
/* In the context of a sculpt gesture, both lasso and polyline modal
* operators are handled as the same general shape. */
Lasso = 1,
Line = 2,
};
@ -1670,13 +1673,14 @@ enum class SelectionType {
Outside = 1,
};
/* Common data structure for both lasso and polyline. */
struct LassoData {
float4x4 projviewobjmat;
rcti boundbox;
int width;
/* 2D bitmap to test if a vertex is affected by the lasso shape. */
/* 2D bitmap to test if a vertex is affected by the surrounding shape. */
blender::BitVector<> mask_px;
};
@ -1734,7 +1738,7 @@ struct GestureData {
float3 world_space_view_origin;
float3 world_space_view_normal;
/* Lasso Gesture. */
/* Lasso & Polyline Gesture. */
LassoData lasso;
/* Line Gesture. */
@ -1765,6 +1769,7 @@ bool is_affected(GestureData &gesture_data, const float3 &co, const float3 &vert
/* Initialization functions. */
std::unique_ptr<GestureData> init_from_box(bContext *C, wmOperator *op);
std::unique_ptr<GestureData> init_from_lasso(bContext *C, wmOperator *op);
std::unique_ptr<GestureData> init_from_polyline(bContext *C, wmOperator *op);
std::unique_ptr<GestureData> init_from_line(bContext *C, wmOperator *op);
/* Common gesture operator properties. */

@ -991,6 +991,10 @@ void WM_operator_properties_gesture_box_zoom(wmOperatorType *ot);
* Use with #WM_gesture_lasso_invoke
*/
void WM_operator_properties_gesture_lasso(wmOperatorType *ot);
/**
* Use with #WM_gesture_polyline_invoke
*/
void WM_operator_properties_gesture_polyline(wmOperatorType *ot);
/**
* Use with #WM_gesture_straightline_invoke
*/
@ -1285,6 +1289,9 @@ void WM_gesture_lines_cancel(bContext *C, wmOperator *op);
int WM_gesture_lasso_invoke(bContext *C, wmOperator *op, const wmEvent *event);
int WM_gesture_lasso_modal(bContext *C, wmOperator *op, const wmEvent *event);
void WM_gesture_lasso_cancel(bContext *C, wmOperator *op);
int WM_gesture_polyline_invoke(bContext *C, wmOperator *op, const wmEvent *event);
int WM_gesture_polyline_modal(bContext *C, wmOperator *op, const wmEvent *event);
void WM_gesture_polyline_cancel(bContext *C, wmOperator *op);
/**
* helper function, we may want to add options for conversion to view space
*/

@ -556,6 +556,10 @@ struct wmNotifier {
/* ************** Gesture Manager data ************** */
namespace blender::wm::gesture {
constexpr float POLYLINE_CLICK_RADIUS = 15.0f;
}
/** #wmGesture::type */
#define WM_GESTURE_LINES 1
#define WM_GESTURE_RECT 2
@ -563,6 +567,7 @@ struct wmNotifier {
#define WM_GESTURE_LASSO 4
#define WM_GESTURE_CIRCLE 5
#define WM_GESTURE_STRAIGHTLINE 6
#define WM_GESTURE_POLYLINE 7
/**
* wmGesture is registered to #wmWindow.gesture, handled by operator callbacks.

@ -80,6 +80,17 @@ wmGesture *WM_gesture_new(wmWindow *window, const ARegion *region, const wmEvent
lasso[1] = xy[1] - gesture->winrct.ymin;
gesture->points = 1;
}
else if (ELEM(type, WM_GESTURE_POLYLINE)) {
gesture->points_alloc = 64;
short *border = static_cast<short int *>(
MEM_mallocN(sizeof(short[2]) * gesture->points_alloc, "polyline points"));
gesture->customdata = border;
border[0] = xy[0] - gesture->winrct.xmin;
border[1] = xy[1] - gesture->winrct.ymin;
border[2] = border[0];
border[3] = border[1];
gesture->points = 2;
}
return gesture;
}
@ -382,6 +393,68 @@ static void wm_gesture_draw_lasso(wmGesture *gt, bool filled)
immUnbindProgram();
}
static void draw_start_vertex_circle(const wmGesture &gt, const uint shdr_pos)
{
const int numverts = gt.points;
/* Draw the circle around the starting vertex. */
const short(*border)[2] = static_cast<short int(*)[2]>(gt.customdata);
const float start_pos[2] = {float(border[0][0]), float(border[0][1])};
const float current_pos[2] = {float(border[numverts - 1][0]), float(border[numverts - 1][1])};
const float dist = len_v2v2(start_pos, current_pos);
const float limit = pow2f(blender::wm::gesture::POLYLINE_CLICK_RADIUS * UI_SCALE_FAC);
if (dist < limit && numverts > 2) {
const float u = smoothstep(0.0f, limit, dist);
const float radius = interpf(
1.0f * UI_SCALE_FAC, blender::wm::gesture::POLYLINE_CLICK_RADIUS * UI_SCALE_FAC, u);
immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR);
imm_draw_circle_wire_2d(shdr_pos, start_pos[0], start_pos[1], radius, 15.0f);
immUnbindProgram();
}
}
static void wm_gesture_draw_polyline(wmGesture *gt)
{
draw_filled_lasso(gt);
const int numverts = gt->points;
if (numverts < 2) {
return;
}
const uint shdr_pos = GPU_vertformat_attr_add(
immVertexFormat(), "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT);
immBindBuiltinProgram(GPU_SHADER_3D_LINE_DASHED_UNIFORM_COLOR);
float viewport_size[4];
GPU_viewport_size_get_f(viewport_size);
immUniform2f("viewport_size", viewport_size[2], viewport_size[3]);
immUniform1i("colors_len", 2); /* "advanced" mode */
immUniform4f("color", 0.4f, 0.4f, 0.4f, 1.0f);
immUniform4f("color2", 1.0f, 1.0f, 1.0f, 1.0f);
immUniform1f("dash_width", 2.0f);
immUniform1f("udash_factor", 0.5f);
immBegin(GPU_PRIM_LINE_LOOP, numverts);
const short *border = (short *)gt->customdata;
for (int i = 0; i < numverts; i++, border += 2) {
immVertex2f(shdr_pos, float(border[0]), float(border[1]));
}
immEnd();
immUnbindProgram();
draw_start_vertex_circle(*gt, shdr_pos);
}
static void wm_gesture_draw_cross(wmWindow *win, wmGesture *gt)
{
const rcti *rect = static_cast<const rcti *>(gt->customdata);
@ -460,6 +533,9 @@ void wm_gesture_draw(wmWindow *win)
else if (gt->type == WM_GESTURE_STRAIGHTLINE) {
wm_gesture_draw_line(gt);
}
else if (gt->type == WM_GESTURE_POLYLINE) {
wm_gesture_draw_polyline(gt);
}
}
}

@ -11,8 +11,8 @@
* - Keymaps are in `wm_operators.cc`.
* - Property definitions are in `wm_operator_props.cc`.
*/
#include "MEM_guardedalloc.h"
#include <fmt/format.h>
#include "DNA_windowmanager_types.h"
@ -21,6 +21,8 @@
#include "BLI_math_vector_types.hh"
#include "BLI_rect.h"
#include "BLT_translation.hh"
#include "BKE_context.hh"
#include "WM_api.hh"
@ -35,6 +37,7 @@
#include "RNA_access.hh"
using blender::Array;
using blender::float2;
using blender::int2;
/* -------------------------------------------------------------------- */
@ -694,6 +697,219 @@ void WM_OT_lasso_gesture(wmOperatorType *ot)
/** \} */
/* -------------------------------------------------------------------- */
/** \name Polyline Gesture
* Gesture defined by three or more points in a viewport enclosing a particular area.
*
* Like the Lasso Gesture, the data passed onto other operators via the 'path' property is a
* sequential array of mouse positions.
* \{ */
int WM_gesture_polyline_invoke(bContext *C, wmOperator *op, const wmEvent *event)
{
wmWindow *win = CTX_wm_window(C);
PropertyRNA *prop;
op->customdata = WM_gesture_new(win, CTX_wm_region(C), event, WM_GESTURE_POLYLINE);
/* add modal handler */
WM_event_add_modal_handler(C, op);
wm_gesture_tag_redraw(win);
if ((prop = RNA_struct_find_property(op->ptr, "cursor"))) {
WM_cursor_modal_set(win, RNA_property_int_get(op->ptr, prop));
}
return OPERATOR_RUNNING_MODAL;
}
/* Calculates the number of valid points in a polyline gesture where
* a duplicated end point is invalid for submission */
static int gesture_polyline_valid_points(const wmGesture &wmGesture)
{
BLI_assert(wmGesture.points > 2);
const int num_points = wmGesture.points;
short(*points)[2] = static_cast<short int(*)[2]>(wmGesture.customdata);
const short last_x = points[num_points - 1][0];
const short last_y = points[num_points - 1][1];
const short prev_x = points[num_points - 2][0];
const short prev_y = points[num_points - 2][1];
return (last_x == prev_x && last_y == prev_y) ? num_points - 1 : num_points;
}
/* Evaluates whether the polyline has at least three points and represents
* a shape and can be submitted for other gesture operators to act on. */
static bool gesture_polyline_can_apply(const wmGesture &wmGesture)
{
if (wmGesture.points <= 2) {
return false;
}
const int valid_points = gesture_polyline_valid_points(wmGesture);
if (valid_points <= 2) {
return false;
}
return true;
}
static int gesture_polyline_apply(bContext *C, wmOperator *op)
{
wmGesture *gesture = static_cast<wmGesture *>(op->customdata);
BLI_assert(gesture_polyline_can_apply(*gesture));
const int valid_points = gesture_polyline_valid_points(*gesture);
gesture->points = valid_points;
const short *border = static_cast<const short int *>(gesture->customdata);
PointerRNA itemptr;
float loc[2];
RNA_collection_clear(op->ptr, "path");
for (int i = 0; i < gesture->points; i++, border += 2) {
loc[0] = border[0];
loc[1] = border[1];
RNA_collection_add(op->ptr, "path", &itemptr);
RNA_float_set_array(&itemptr, "loc", loc);
}
gesture_modal_end(C, op);
int retval = OPERATOR_FINISHED;
if (op->type->exec) {
retval = op->type->exec(C, op);
OPERATOR_RETVAL_CHECK(retval);
}
return retval;
}
int WM_gesture_polyline_modal(bContext *C, wmOperator *op, const wmEvent *event)
{
wmGesture *gesture = static_cast<wmGesture *>(op->customdata);
if (event->type == EVT_MODAL_MAP) {
switch (event->val) {
case GESTURE_MODAL_MOVE:
gesture->move = !gesture->move;
break;
case GESTURE_MODAL_SELECT: {
wm_gesture_tag_redraw(CTX_wm_window(C));
short(*border)[2] = static_cast<short int(*)[2]>(gesture->customdata);
const short cur_x = border[gesture->points - 1][0];
const short cur_y = border[gesture->points - 1][1];
const short prev_x = border[gesture->points - 2][0];
const short prev_y = border[gesture->points - 2][1];
if (cur_x == prev_x && cur_y == prev_y) {
break;
}
const float2 cur(cur_x, cur_y);
const float2 orig(border[0][0], border[0][1]);
const float dist = len_v2v2(cur, orig);
if (dist < blender::wm::gesture::POLYLINE_CLICK_RADIUS * UI_SCALE_FAC &&
gesture_polyline_can_apply(*gesture))
{
return gesture_polyline_apply(C, op);
}
gesture->points++;
border[gesture->points - 1][0] = cur_x;
border[gesture->points - 1][1] = cur_y;
break;
}
case GESTURE_MODAL_CONFIRM:
if (gesture_polyline_can_apply(*gesture)) {
return gesture_polyline_apply(C, op);
}
break;
case GESTURE_MODAL_CANCEL:
gesture_modal_end(C, op);
return OPERATOR_CANCELLED;
}
}
else {
switch (event->type) {
case MOUSEMOVE:
case INBETWEEN_MOUSEMOVE: {
wm_gesture_tag_redraw(CTX_wm_window(C));
if (gesture->points == gesture->points_alloc) {
gesture->points_alloc *= 2;
gesture->customdata = MEM_reallocN(gesture->customdata,
sizeof(short[2]) * gesture->points_alloc);
}
short(*border)[2] = static_cast<short int(*)[2]>(gesture->customdata);
/* move the lasso */
if (gesture->move) {
const int x = ((event->xy[0] - gesture->winrct.xmin) - border[gesture->points - 1][0]);
const int y = ((event->xy[1] - gesture->winrct.ymin) - border[gesture->points - 1][1]);
for (int i = 0; i < gesture->points; i++) {
border[i][0] += x;
border[i][1] += y;
}
}
border[gesture->points - 1][0] = event->xy[0] - gesture->winrct.xmin;
border[gesture->points - 1][1] = event->xy[1] - gesture->winrct.ymin;
break;
}
}
}
gesture->is_active_prev = gesture->is_active;
return OPERATOR_RUNNING_MODAL;
}
void WM_gesture_polyline_cancel(bContext *C, wmOperator *op)
{
gesture_modal_end(C, op);
}
/* template to copy from */
#if 0
static int gesture_polyline_exec(bContext *C, wmOperator *op)
{
RNA_BEGIN (op->ptr, itemptr, "path") {
float loc[2];
RNA_float_get_array(&itemptr, "loc", loc);
printf("Location: %f %f\n", loc[0], loc[1]);
}
RNA_END;
return OPERATOR_FINISHED;
}
void WM_OT_polyline_gesture(wmOperatorType *ot)
{
PropertyRNA *prop;
ot->name = "Polyline Gesture";
ot->idname = "WM_OT_polyline_gesture";
ot->description = "Outline a selection area with each mouse click";
ot->invoke = WM_gesture_polyline_invoke;
ot->modal = WM_gesture_polyline_modal;
ot->exec = gesture_polyline_exec;
ot->poll = WM_operator_winactive;
WM_operator_properties_gesture_polyline(ot);
}
#endif
/** \} */
/* -------------------------------------------------------------------- */
/** \name Straight Line Gesture
*

@ -528,6 +528,13 @@ void WM_operator_properties_gesture_lasso(wmOperatorType *ot)
RNA_def_property_flag(prop, PROP_HIDDEN | PROP_SKIP_SAVE);
}
void WM_operator_properties_gesture_polyline(wmOperatorType *ot)
{
PropertyRNA *prop;
prop = RNA_def_collection_runtime(ot->srna, "path", &RNA_OperatorMousePath, "Path", "");
RNA_def_property_flag(prop, PROP_HIDDEN | PROP_SKIP_SAVE);
}
void WM_operator_properties_gesture_straightline(wmOperatorType *ot, int cursor)
{
PropertyRNA *prop;

@ -4229,6 +4229,30 @@ static void gesture_lasso_modal_keymap(wmKeyConfig *keyconf)
WM_modalkeymap_assign(keymap, "PAINT_OT_hide_show_lasso_gesture");
}
/* Polyline modal operators */
static void gesture_polyline_modal_keymap(wmKeyConfig *keyconf)
{
static const EnumPropertyItem modal_items[] = {
{GESTURE_MODAL_CONFIRM, "CONFIRM", 0, "Confirm", ""},
{GESTURE_MODAL_CANCEL, "CANCEL", 0, "Cancel", ""},
{GESTURE_MODAL_SELECT, "SELECT", 0, "Select", ""},
{GESTURE_MODAL_MOVE, "MOVE", 0, "Move", ""},
{0, nullptr, 0, nullptr, nullptr},
};
wmKeyMap *keymap = WM_modalkeymap_find(keyconf, "Gesture Polyline");
/* This function is called for each space-type, only needs to add map once. */
if (keymap && keymap->modal_items) {
return;
}
keymap = WM_modalkeymap_ensure(keyconf, "Gesture Polyline", modal_items);
/* assign map to operators */
WM_modalkeymap_assign(keymap, "PAINT_OT_hide_show_polyline_gesture");
}
/* Zoom to border modal operators. */
static void gesture_zoom_border_modal_keymap(wmKeyConfig *keyconf)
{
@ -4265,6 +4289,7 @@ void wm_window_keymap(wmKeyConfig *keyconf)
gesture_zoom_border_modal_keymap(keyconf);
gesture_straightline_modal_keymap(keyconf);
gesture_lasso_modal_keymap(keyconf);
gesture_polyline_modal_keymap(keyconf);
WM_keymap_fix_linking();
}