VSE: Implement Select Circle
This feature works like the select circle in any other modes. The user can press "C" in preview or timeline and then select or deselect strips by pressing the left or middle mouse button. It’s an enhancement for the VSE preview because: 1. It makes it more similar to other editors in Blender 2. This behavior makes it easier to select specific overlapping strips in preview, that is because the select circle only checks for the origin of the strip. Pull Request: https://projects.blender.org/blender/blender/pulls/141422
This commit is contained in:
committed by
Richard Antalik
parent
ede8e3af91
commit
098be390ca
@@ -3031,6 +3031,7 @@ def km_sequencer(params):
|
||||
("sequencer.select_box", {"type": 'B', "value": 'PRESS'}, None),
|
||||
("sequencer.select_box", {"type": 'B', "value": 'PRESS', "ctrl": True},
|
||||
{"properties": [("include_handles", True)]}),
|
||||
("sequencer.select_circle", {"type": 'C', "value": 'PRESS'}, None),
|
||||
("sequencer.select_grouped", {"type": 'G', "value": 'PRESS', "shift": True}, None),
|
||||
*_template_items_select_actions(params, "sequencer.select_all"),
|
||||
("sequencer.split", {"type": 'K', "value": 'PRESS'},
|
||||
@@ -3099,7 +3100,7 @@ def km_sequencer(params):
|
||||
)
|
||||
),
|
||||
op_menu("SEQUENCER_MT_add", {"type": 'A', "value": 'PRESS', "shift": True}),
|
||||
op_menu("SEQUENCER_MT_change", {"type": 'C', "value": 'PRESS'}),
|
||||
op_menu("SEQUENCER_MT_change", {"type": 'C', "value": 'PRESS', "shift": True}),
|
||||
op_menu_pie("SEQUENCER_MT_view_pie", {"type": 'ACCENT_GRAVE', "value": 'PRESS'}),
|
||||
("sequencer.slip", {"type": 'S', "value": 'PRESS'}, {"properties": [("use_cursor_position", False)]}),
|
||||
("wm.context_set_int", {"type": 'O', "value": 'PRESS'},
|
||||
@@ -3199,6 +3200,7 @@ def km_sequencer_preview(params):
|
||||
),
|
||||
*_template_items_select_actions(params, "sequencer.select_all"),
|
||||
("sequencer.select_box", {"type": 'B', "value": 'PRESS'}, None),
|
||||
("sequencer.select_circle", {"type": 'C', "value": 'PRESS'}, None),
|
||||
|
||||
# View.
|
||||
("sequencer.view_selected", {"type": 'NUMPAD_PERIOD', "value": 'PRESS'}, None),
|
||||
@@ -8448,6 +8450,18 @@ def km_sequencer_tool_generic_select_box(params, *, fallback):
|
||||
]},
|
||||
)
|
||||
|
||||
def km_sequencer_tool_generic_select_circle(params, *, fallback):
|
||||
return (
|
||||
_fallback_id("Sequencer Tool: Select Circle", fallback),
|
||||
{"space_type": 'SEQUENCE_EDITOR', "region_type": 'WINDOW'},
|
||||
{"items": [
|
||||
*([] if (fallback and not params.use_fallback_tool) else _template_items_tool_select_actions_simple(
|
||||
"sequencer.select_circle",
|
||||
**(params.select_tweak_event if (fallback and params.use_fallback_tool_select_mouse) else
|
||||
{"type": params.tool_mouse, "value": 'PRESS'}),
|
||||
properties=[("wait_for_input", False)])),
|
||||
]},
|
||||
)
|
||||
|
||||
def km_sequencer_preview_tool_generic_select(params, *, fallback):
|
||||
return (
|
||||
@@ -8482,6 +8496,20 @@ def km_sequencer_preview_tool_generic_select_box(params, *, fallback):
|
||||
)
|
||||
|
||||
|
||||
def km_sequencer_preview_tool_generic_select_circle(params, *, fallback):
|
||||
return (
|
||||
_fallback_id("Preview Tool: Select Circle", fallback),
|
||||
{"space_type": 'SEQUENCE_EDITOR', "region_type": 'WINDOW'},
|
||||
{"items": [
|
||||
*([] if (fallback and not params.use_fallback_tool) else _template_items_tool_select_actions_simple(
|
||||
"sequencer.select_circle",
|
||||
**(params.select_tweak_event if (fallback and params.use_fallback_tool_select_mouse) else
|
||||
{"type": params.tool_mouse, "value": 'PRESS'}),
|
||||
properties=[("wait_for_input", False)])),
|
||||
]},
|
||||
)
|
||||
|
||||
|
||||
def km_sequencer_preview_tool_generic_cursor(params):
|
||||
return (
|
||||
"Preview Tool: Cursor",
|
||||
@@ -8867,6 +8895,10 @@ def generate_keymaps(params=None):
|
||||
for fallback in (False, True)),
|
||||
*(km_sequencer_preview_tool_generic_select_box(params, fallback=fallback)
|
||||
for fallback in (False, True)),
|
||||
*(km_sequencer_preview_tool_generic_select_circle(params, fallback=fallback)
|
||||
for fallback in (False, True)),
|
||||
*(km_sequencer_tool_generic_select_circle(params, fallback=fallback)
|
||||
for fallback in (False, True)),
|
||||
km_3d_view_tool_paint_grease_pencil_trim(params),
|
||||
km_3d_view_tool_edit_grease_pencil_texture_gradient(params),
|
||||
km_sequencer_tool_blade(params),
|
||||
|
||||
@@ -1073,14 +1073,19 @@ def _activate_by_item(context, space_type, item, index, *, as_fallback=False):
|
||||
WindowManager = bpy.types.WindowManager
|
||||
|
||||
handle_map = _activate_by_item._cursor_draw_handle
|
||||
handle = handle_map.pop(space_type, None)
|
||||
# view_type used when in VSE, check if view_type exists because not every space_data has it.
|
||||
view_type = getattr(context.space_data, "view_type", None)
|
||||
handle = handle_map.pop((space_type, view_type), None)
|
||||
if handle is not None:
|
||||
WindowManager.draw_cursor_remove(handle)
|
||||
if item.draw_cursor is not None:
|
||||
def handle_fn(context, item, tool, xy):
|
||||
item.draw_cursor(context, tool, xy)
|
||||
handle = WindowManager.draw_cursor_add(handle_fn, (context, item, tool), space_type, 'WINDOW')
|
||||
handle_map[space_type] = handle
|
||||
if view_type == 'PREVIEW':
|
||||
handle = WindowManager.draw_cursor_add(handle_fn, (context, item, tool), space_type, 'PREVIEW')
|
||||
else:
|
||||
handle = WindowManager.draw_cursor_add(handle_fn, (context, item, tool), space_type, 'WINDOW')
|
||||
handle_map[(space_type, view_type)] = handle
|
||||
|
||||
|
||||
_activate_by_item._cursor_draw_handle = {}
|
||||
|
||||
@@ -3272,6 +3272,54 @@ class _defs_sequencer_select:
|
||||
draw_settings=draw_settings,
|
||||
)
|
||||
|
||||
@ToolDef.from_fn
|
||||
def circle_timeline():
|
||||
def draw_settings(_context, layout, tool):
|
||||
props = tool.operator_properties("sequencer.select_circle")
|
||||
row = layout.row()
|
||||
row.use_property_split = False
|
||||
row.prop(props, "mode", text="", expand=True, icon_only=True)
|
||||
layout.prop(props, "radius")
|
||||
|
||||
def draw_cursor(_context, tool, xy):
|
||||
from gpu_extras.presets import draw_circle_2d
|
||||
props = tool.operator_properties("sequencer.select_circle")
|
||||
radius = props.radius
|
||||
draw_circle_2d(xy, (1.0,) * 4, radius, segments=32)
|
||||
return dict(
|
||||
idname="sequencer.select_circle",
|
||||
label="Select Circle",
|
||||
icon="ops.generic.select_circle",
|
||||
widget=None,
|
||||
keymap="Sequencer Tool: Select Circle",
|
||||
draw_settings=draw_settings,
|
||||
draw_cursor=draw_cursor,
|
||||
)
|
||||
|
||||
@ToolDef.from_fn
|
||||
def circle_preview():
|
||||
def draw_settings(_context, layout, tool):
|
||||
props = tool.operator_properties("sequencer.select_circle")
|
||||
row = layout.row()
|
||||
row.use_property_split = False
|
||||
row.prop(props, "mode", text="", expand=True, icon_only=True)
|
||||
layout.prop(props, "radius")
|
||||
|
||||
def draw_cursor(_context, tool, xy):
|
||||
from gpu_extras.presets import draw_circle_2d
|
||||
props = tool.operator_properties("sequencer.select_circle")
|
||||
radius = props.radius
|
||||
draw_circle_2d(xy, (1.0,) * 4, radius, segments=32)
|
||||
return dict(
|
||||
idname="sequencer.select_circle",
|
||||
label="Select Circle",
|
||||
icon="ops.generic.select_circle",
|
||||
widget=None,
|
||||
keymap="Preview Tool: Select Circle",
|
||||
draw_settings=draw_settings,
|
||||
draw_cursor=draw_cursor,
|
||||
)
|
||||
|
||||
|
||||
class IMAGE_PT_tools_active(ToolSelectPanelHelper, Panel):
|
||||
bl_space_type = 'IMAGE_EDITOR'
|
||||
@@ -3958,6 +4006,7 @@ class SEQUENCER_PT_tools_active(ToolSelectPanelHelper, Panel):
|
||||
(
|
||||
_defs_sequencer_select.select_preview,
|
||||
_defs_sequencer_select.box_preview,
|
||||
_defs_sequencer_select.circle_preview,
|
||||
),
|
||||
_defs_sequencer_generic.cursor,
|
||||
None,
|
||||
@@ -3970,7 +4019,10 @@ class SEQUENCER_PT_tools_active(ToolSelectPanelHelper, Panel):
|
||||
*_tools_annotate,
|
||||
],
|
||||
'SEQUENCER': [
|
||||
_defs_sequencer_select.box_timeline,
|
||||
(
|
||||
_defs_sequencer_select.box_timeline,
|
||||
_defs_sequencer_select.circle_timeline,
|
||||
),
|
||||
_defs_sequencer_generic.blade,
|
||||
_defs_sequencer_generic.slip
|
||||
],
|
||||
|
||||
@@ -274,6 +274,7 @@ void SEQUENCER_OT_select_linked_pick(wmOperatorType *ot);
|
||||
void SEQUENCER_OT_select_handles(wmOperatorType *ot);
|
||||
void SEQUENCER_OT_select_side(wmOperatorType *ot);
|
||||
void SEQUENCER_OT_select_box(wmOperatorType *ot);
|
||||
void SEQUENCER_OT_select_circle(wmOperatorType *ot);
|
||||
void SEQUENCER_OT_select_inverse(wmOperatorType *ot);
|
||||
void SEQUENCER_OT_select_grouped(wmOperatorType *ot);
|
||||
|
||||
|
||||
@@ -105,6 +105,7 @@ void sequencer_operatortypes()
|
||||
WM_operatortype_append(SEQUENCER_OT_select_side);
|
||||
WM_operatortype_append(SEQUENCER_OT_select_side_of_frame);
|
||||
WM_operatortype_append(SEQUENCER_OT_select_box);
|
||||
WM_operatortype_append(SEQUENCER_OT_select_circle);
|
||||
WM_operatortype_append(SEQUENCER_OT_select_grouped);
|
||||
|
||||
/* `sequencer_add.cc` */
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
|
||||
#include "BLI_rect.h"
|
||||
#include "MEM_guardedalloc.h"
|
||||
|
||||
#include "BLI_ghash.h"
|
||||
@@ -2325,6 +2326,167 @@ void SEQUENCER_OT_select_box(wmOperatorType *ot)
|
||||
RNA_def_property_flag(prop, PROP_SKIP_SAVE);
|
||||
}
|
||||
|
||||
static bool strip_circle_select_radius_image_isect(const Scene *scene,
|
||||
const Strip *strip,
|
||||
const int *radius,
|
||||
const float2 mval)
|
||||
{
|
||||
float2 origin = seq::image_transform_origin_offset_pixelspace_get(scene, strip);
|
||||
|
||||
float dx = origin.x - float(mval[0]);
|
||||
float dy = origin.y - float(mval[1]);
|
||||
float dist_sq = sqrt(dx * dx + dy * dy);
|
||||
|
||||
return dist_sq <= *radius;
|
||||
}
|
||||
|
||||
static void seq_circle_select_strip_from_preview(bContext *C,
|
||||
int radius,
|
||||
const float2 mval,
|
||||
const eSelectOp mode)
|
||||
{
|
||||
Scene *scene = CTX_data_scene(C);
|
||||
Editing *ed = seq::editing_get(scene);
|
||||
ListBase *seqbase = seq::active_seqbase_get(ed);
|
||||
ListBase *channels = seq::channels_displayed_get(ed);
|
||||
SpaceSeq *sseq = CTX_wm_space_seq(C);
|
||||
|
||||
VectorSet strips = seq::query_rendered_strips(
|
||||
scene, channels, seqbase, scene->r.cfra, sseq->chanshown);
|
||||
for (Strip *strip : strips) {
|
||||
if (!strip_circle_select_radius_image_isect(scene, strip, &radius, mval)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ELEM(mode, SEL_OP_ADD, SEL_OP_SET)) {
|
||||
strip->flag |= SELECT;
|
||||
}
|
||||
else {
|
||||
BLI_assert(mode == SEL_OP_SUB);
|
||||
strip->flag &= ~SELECT;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static bool check_circle_intersection_in_timeline(const rctf *rect,
|
||||
const float xy[2],
|
||||
const float x_radius,
|
||||
const float y_radius)
|
||||
{
|
||||
float dx, dy;
|
||||
|
||||
if (xy[0] >= rect->xmin && xy[0] <= rect->xmax) {
|
||||
dx = 0;
|
||||
}
|
||||
else {
|
||||
dx = (xy[0] < rect->xmin) ? (rect->xmin - xy[0]) : (xy[0] - rect->xmax);
|
||||
}
|
||||
|
||||
if (xy[1] >= rect->ymin && xy[1] <= rect->ymax) {
|
||||
dy = 0;
|
||||
}
|
||||
else {
|
||||
dy = (xy[1] < rect->ymin) ? (rect->ymin - xy[1]) : (xy[1] - rect->ymax);
|
||||
}
|
||||
|
||||
return ((dx * dx) / (x_radius * x_radius) + (dy * dy) / (y_radius * y_radius) <= 1.0f);
|
||||
}
|
||||
static wmOperatorStatus vse_circle_select_exec(bContext *C, wmOperator *op)
|
||||
{
|
||||
const int radius = RNA_int_get(op->ptr, "radius");
|
||||
const int mval[2] = {RNA_int_get(op->ptr, "x"), RNA_int_get(op->ptr, "y")};
|
||||
wmGesture *gesture = static_cast<wmGesture *>(op->customdata);
|
||||
const eSelectOp sel_op = eSelectOp(RNA_enum_get(op->ptr, "mode"));
|
||||
|
||||
Scene *scene = CTX_data_scene(C);
|
||||
View2D *v2d = UI_view2d_fromcontext(C);
|
||||
Editing *ed = seq::editing_get(scene);
|
||||
ARegion *region = CTX_wm_region(C);
|
||||
|
||||
const bool use_pre_deselect = SEL_OP_USE_PRE_DESELECT(sel_op);
|
||||
|
||||
if (use_pre_deselect && WM_gesture_is_modal_first(gesture)) {
|
||||
deselect_all_strips(scene);
|
||||
sequencer_select_do_updates(C, scene);
|
||||
}
|
||||
|
||||
if (ed == nullptr) {
|
||||
return OPERATOR_CANCELLED;
|
||||
}
|
||||
|
||||
float2 view_mval;
|
||||
UI_view2d_region_to_view(v2d, mval[0], mval[1], &view_mval[0], &view_mval[1]);
|
||||
float pixel_radius = radius / UI_view2d_scale_get_x(v2d);
|
||||
|
||||
if (region->regiontype == RGN_TYPE_PREVIEW) {
|
||||
seq_circle_select_strip_from_preview(C, pixel_radius, view_mval, sel_op);
|
||||
sequencer_select_do_updates(C, scene);
|
||||
return OPERATOR_FINISHED;
|
||||
}
|
||||
|
||||
float x_radius = radius / UI_view2d_scale_get_x(v2d);
|
||||
float y_radius = radius / UI_view2d_scale_get_y(v2d);
|
||||
bool changed = false;
|
||||
LISTBASE_FOREACH (Strip *, strip, ed->seqbasep) {
|
||||
rctf rq;
|
||||
strip_rectf(scene, strip, &rq);
|
||||
/* Use custom function to check the distance because in timeline the circle is a ellipse. */
|
||||
if (check_circle_intersection_in_timeline(&rq, view_mval, x_radius, y_radius)) {
|
||||
if (ELEM(sel_op, SEL_OP_ADD, SEL_OP_SET)) {
|
||||
strip->flag |= SELECT;
|
||||
}
|
||||
else {
|
||||
BLI_assert(sel_op == SEL_OP_SUB);
|
||||
strip->flag &= ~SELECT;
|
||||
}
|
||||
changed = true;
|
||||
|
||||
const bool ignore_connections = RNA_boolean_get(op->ptr, "ignore_connections");
|
||||
if (!ignore_connections) {
|
||||
/* Propagate selection to connected strips. */
|
||||
StripSelection selection;
|
||||
selection.strip1 = strip;
|
||||
sequencer_select_connected_strips(selection);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (changed) {
|
||||
sequencer_select_do_updates(C, scene);
|
||||
}
|
||||
return OPERATOR_FINISHED;
|
||||
}
|
||||
|
||||
void SEQUENCER_OT_select_circle(wmOperatorType *ot)
|
||||
{
|
||||
PropertyRNA *prop;
|
||||
|
||||
ot->name = "Circle Select";
|
||||
ot->description = "Select strips using circle selection";
|
||||
ot->idname = "SEQUENCER_OT_select_circle";
|
||||
|
||||
ot->invoke = WM_gesture_circle_invoke;
|
||||
ot->modal = WM_gesture_circle_modal;
|
||||
ot->exec = vse_circle_select_exec;
|
||||
|
||||
ot->poll = ED_operator_sequencer_active;
|
||||
|
||||
ot->get_name = ED_select_circle_get_name;
|
||||
|
||||
/* flags */
|
||||
ot->flag = OPTYPE_UNDO;
|
||||
|
||||
/* properties */
|
||||
WM_operator_properties_gesture_circle(ot);
|
||||
WM_operator_properties_select_operation_simple(ot);
|
||||
|
||||
prop = RNA_def_boolean(ot->srna,
|
||||
"ignore_connections",
|
||||
false,
|
||||
"Ignore Connections",
|
||||
"Select strips individually whether or not they are connected");
|
||||
RNA_def_property_flag(prop, PROP_SKIP_SAVE);
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
|
||||
@@ -4276,6 +4276,7 @@ static void gesture_circle_modal_keymap(wmKeyConfig *keyconf)
|
||||
/* Assign map to operators. */
|
||||
WM_modalkeymap_assign(keymap, "VIEW3D_OT_select_circle");
|
||||
WM_modalkeymap_assign(keymap, "UV_OT_select_circle");
|
||||
WM_modalkeymap_assign(keymap, "SEQUENCER_OT_select_circle");
|
||||
WM_modalkeymap_assign(keymap, "CLIP_OT_select_circle");
|
||||
WM_modalkeymap_assign(keymap, "MASK_OT_select_circle");
|
||||
WM_modalkeymap_assign(keymap, "NODE_OT_select_circle");
|
||||
|
||||
Reference in New Issue
Block a user