diff --git a/scripts/presets/keyconfig/keymap_data/blender_default.py b/scripts/presets/keyconfig/keymap_data/blender_default.py index fd6cfd61d22..95d169d9062 100644 --- a/scripts/presets/keyconfig/keymap_data/blender_default.py +++ b/scripts/presets/keyconfig/keymap_data/blender_default.py @@ -5707,6 +5707,9 @@ def km_edit_curves(params): *_template_items_select_actions(params, "curves.select_all"), ("curves.extrude_move", {"type": 'E', "value": 'PRESS'}, None), ("curves.select_linked", {"type": 'L', "value": 'PRESS', "ctrl": True}, None), + ("curves.select_linked_pick", {"type": 'L', "value": 'PRESS'}, {"properties": [("deselect", False)]}), + ("curves.select_linked_pick", {"type": 'L', "value": 'PRESS', + "shift": True}, {"properties": [("deselect", True)]}), ("curves.delete", {"type": 'X', "value": 'PRESS'}, None), ("curves.delete", {"type": 'DEL', "value": 'PRESS'}, None), ("curves.select_more", {"type": 'NUMPAD_PLUS', "value": 'PRESS', "ctrl": True, "repeat": True}, None), diff --git a/source/blender/editors/curves/CMakeLists.txt b/source/blender/editors/curves/CMakeLists.txt index a8a2e0b95b5..8b46dc9d6c3 100644 --- a/source/blender/editors/curves/CMakeLists.txt +++ b/source/blender/editors/curves/CMakeLists.txt @@ -31,6 +31,7 @@ set(SRC intern/curves_ops.cc intern/curves_selection.cc intern/curves_undo.cc + intern/select_linked_pick.cc ) set(LIB diff --git a/source/blender/editors/curves/intern/curves_ops.cc b/source/blender/editors/curves/intern/curves_ops.cc index f1d31fb6766..679baca3987 100644 --- a/source/blender/editors/curves/intern/curves_ops.cc +++ b/source/blender/editors/curves/intern/curves_ops.cc @@ -1777,6 +1777,7 @@ void operatortypes_curves() WM_operatortype_append(CURVES_OT_select_random); WM_operatortype_append(CURVES_OT_select_ends); WM_operatortype_append(CURVES_OT_select_linked); + WM_operatortype_append(CURVES_OT_select_linked_pick); WM_operatortype_append(CURVES_OT_select_more); WM_operatortype_append(CURVES_OT_select_less); WM_operatortype_append(CURVES_OT_surface_set); diff --git a/source/blender/editors/curves/intern/select_linked_pick.cc b/source/blender/editors/curves/intern/select_linked_pick.cc new file mode 100644 index 00000000000..47ed0c1541c --- /dev/null +++ b/source/blender/editors/curves/intern/select_linked_pick.cc @@ -0,0 +1,143 @@ +/* SPDX-FileCopyrightText: 2024 Blender Authors + * + * SPDX-License-Identifier: GPL-2.0-or-later */ + +#include "BKE_context.hh" +#include "BKE_curves.hh" +#include "BKE_layer.hh" + +#include "RNA_access.hh" +#include "RNA_define.hh" + +#include "DEG_depsgraph.hh" + +#include "WM_api.hh" +#include "WM_types.hh" + +#include "ED_curves.hh" +#include "ED_view3d.hh" + +namespace blender::ed::curves { + +struct ClosestCurveDataBlock { + Curves *curves_id = nullptr; + FindClosestData elem = {}; +}; +static ClosestCurveDataBlock find_closest_curve(const Depsgraph &depsgraph, + const ViewContext &vc, + const Span bases, + const int2 &mval) +{ + return threading::parallel_reduce( + bases.index_range(), + 1L, + ClosestCurveDataBlock(), + [&](const IndexRange range, const ClosestCurveDataBlock &init) { + ClosestCurveDataBlock new_closest = init; + for (Base *base : bases.slice(range)) { + Object &curves_ob = *base->object; + Curves &curves_id = *static_cast(curves_ob.data); + bke::crazyspace::GeometryDeformation deformation = + bke::crazyspace::get_evaluated_curves_deformation(depsgraph, curves_ob); + const bke::CurvesGeometry &curves = curves_id.geometry.wrap(); + const float4x4 projection = ED_view3d_ob_project_mat_get(vc.rv3d, &curves_ob); + foreach_selectable_curve_range( + curves, + deformation, + eHandleDisplay(vc.v3d->overlay.handle_display), + [&](IndexRange range, Span positions, StringRef /*selection_name*/) { + std::optional new_closest_elem = closest_elem_find_screen_space( + vc, + curves.points_by_curve(), + positions, + curves.cyclic(), + projection, + range, + bke::AttrDomain::Curve, + mval, + new_closest.elem); + if (new_closest_elem) { + new_closest.elem = *new_closest_elem; + new_closest.curves_id = &curves_id; + } + }); + } + return new_closest; + }, + [](const ClosestCurveDataBlock &a, const ClosestCurveDataBlock &b) { + return (a.elem.distance < b.elem.distance) ? a : b; + }); +} + +static bool select_linked_pick(bContext &C, const int2 &mval, const SelectPick_Params ¶ms) +{ + Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(&C); + const ViewContext vc = ED_view3d_viewcontext_init(&C, depsgraph); + const Vector bases = BKE_view_layer_array_from_bases_in_edit_mode_unique_data( + vc.scene, vc.view_layer, vc.v3d); + + const ClosestCurveDataBlock closest = find_closest_curve(*depsgraph, vc, bases, mval); + if (!closest.curves_id) { + return false; + } + + bke::CurvesGeometry &closest_curves = closest.curves_id->geometry.wrap(); + const bke::AttrDomain selection_domain = bke::AttrDomain(closest.curves_id->selection_domain); + + if (selection_domain == bke::AttrDomain::Point) { + const OffsetIndices points_by_curve = closest_curves.points_by_curve(); + foreach_selection_attribute_writer( + closest_curves, bke::AttrDomain::Point, [&](bke::GSpanAttributeWriter &selection) { + for (const int point : points_by_curve[closest.elem.index]) { + apply_selection_operation_at_index(selection.span, point, params.sel_op); + } + }); + } + else if (selection_domain == bke::AttrDomain::Curve) { + bke::GSpanAttributeWriter selection = ensure_selection_attribute( + closest_curves, bke::AttrDomain::Curve, CD_PROP_BOOL); + apply_selection_operation_at_index(selection.span, closest.elem.index, params.sel_op); + selection.finish(); + } + + /* Use #ID_RECALC_GEOMETRY instead of #ID_RECALC_SELECT because it is handled as a + * generic attribute for now. */ + DEG_id_tag_update(&closest.curves_id->id, ID_RECALC_GEOMETRY); + WM_event_add_notifier(&C, NC_GEOM | ND_DATA, closest.curves_id); + + return true; +} + +static int select_linked_pick_invoke(bContext *C, wmOperator *op, const wmEvent *event) +{ + SelectPick_Params params{}; + params.sel_op = RNA_boolean_get(op->ptr, "deselect") ? SEL_OP_SUB : SEL_OP_ADD; + params.deselect_all = false; + params.select_passthrough = false; + + if (!select_linked_pick(*C, event->mval, params)) { + return OPERATOR_CANCELLED; + } + + return OPERATOR_FINISHED; +} + +void CURVES_OT_select_linked_pick(wmOperatorType *ot) +{ + ot->name = "Select Linked"; + ot->idname = "CURVES_OT_select_linked_pick"; + ot->description = "Select all points in the curve under the cursor"; + + ot->invoke = select_linked_pick_invoke; + ot->poll = editable_curves_poll; + + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + RNA_def_boolean(ot->srna, + "deselect", + false, + "Deselect", + "Deselect linked control points rather than selecting them"); +} + +} // namespace blender::ed::curves diff --git a/source/blender/editors/include/ED_curves.hh b/source/blender/editors/include/ED_curves.hh index bb785572eb7..21ba843659a 100644 --- a/source/blender/editors/include/ED_curves.hh +++ b/source/blender/editors/include/ED_curves.hh @@ -134,6 +134,7 @@ bool curves_poll(bContext *C); void CURVES_OT_attribute_set(wmOperatorType *ot); void CURVES_OT_draw(wmOperatorType *ot); void CURVES_OT_extrude(wmOperatorType *ot); +void CURVES_OT_select_linked_pick(wmOperatorType *ot); /** \} */