diff --git a/release/scripts/presets/keyconfig/keymap_data/blender_default.py b/release/scripts/presets/keyconfig/keymap_data/blender_default.py index 637580364dc..8a3eb28c5ae 100644 --- a/release/scripts/presets/keyconfig/keymap_data/blender_default.py +++ b/release/scripts/presets/keyconfig/keymap_data/blender_default.py @@ -5627,6 +5627,7 @@ def km_curves(params): ("curves.disable_selection", {"type": 'ONE', "value": 'PRESS', "alt": True}, None), ("curves.disable_selection", {"type": 'TWO', "value": 'PRESS', "alt": True}, None), *_template_items_select_actions(params, "curves.select_all"), + ("curves.select_linked", {"type": 'L', "value": 'PRESS', "ctrl": True}, None), ]) return keymap diff --git a/release/scripts/startup/bl_ui/space_view3d.py b/release/scripts/startup/bl_ui/space_view3d.py index 1ce457480e5..6e8e4155c0b 100644 --- a/release/scripts/startup/bl_ui/space_view3d.py +++ b/release/scripts/startup/bl_ui/space_view3d.py @@ -2052,6 +2052,7 @@ class VIEW3D_MT_select_edit_curves(Menu): layout.operator("curves.select_all", text="Invert").action = 'INVERT' layout.operator("curves.select_random", text="Random") layout.operator("curves.select_end", text="Endpoints") + layout.operator("curves.select_linked", text="Linked") class VIEW3D_MT_select_sculpt_curves(Menu): diff --git a/source/blender/editors/curves/intern/curves_ops.cc b/source/blender/editors/curves/intern/curves_ops.cc index 59af21f8702..77641d7a916 100644 --- a/source/blender/editors/curves/intern/curves_ops.cc +++ b/source/blender/editors/curves/intern/curves_ops.cc @@ -157,6 +157,19 @@ bool curves_poll(bContext *C) return curves_poll_impl(C, false, false, false); } +static bool editable_curves_point_domain_poll(bContext *C) +{ + if (!curves::editable_curves_poll(C)) { + return false; + } + const Curves *curves_id = static_cast(CTX_data_active_object(C)->data); + if (curves_id->selection_domain != ATTR_DOMAIN_POINT) { + CTX_wm_operator_poll_msg_set(C, "Only available in point selection mode"); + return false; + } + return true; +} + using bke::CurvesGeometry; namespace convert_to_particle_system { @@ -931,19 +944,6 @@ static void CURVES_OT_select_random(wmOperatorType *ot) 1.0f); } -static bool select_end_poll(bContext *C) -{ - if (!curves::editable_curves_poll(C)) { - return false; - } - const Curves *curves_id = static_cast(CTX_data_active_object(C)->data); - if (curves_id->selection_domain != ATTR_DOMAIN_POINT) { - CTX_wm_operator_poll_msg_set(C, "Only available in point selection mode"); - return false; - } - return true; -} - static int select_end_exec(bContext *C, wmOperator *op) { VectorSet unique_curves = curves::get_unique_editable_curves(*C); @@ -952,7 +952,7 @@ static int select_end_exec(bContext *C, wmOperator *op) for (Curves *curves_id : unique_curves) { CurvesGeometry &curves = curves_id->geometry.wrap(); - select_ends(curves, eAttrDomain(curves_id->selection_domain), amount, end_points); + select_ends(curves, amount, end_points); /* Use #ID_RECALC_GEOMETRY instead of #ID_RECALC_SELECT because it is handled as a generic * attribute for now. */ @@ -970,7 +970,7 @@ static void CURVES_OT_select_end(wmOperatorType *ot) ot->description = "Select end points of curves"; ot->exec = select_end_exec; - ot->poll = select_end_poll; + ot->poll = editable_curves_point_domain_poll; ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; @@ -983,6 +983,33 @@ static void CURVES_OT_select_end(wmOperatorType *ot) ot->srna, "amount", 1, 0, INT32_MAX, "Amount", "Number of points to select", 0, INT32_MAX); } +static int select_linked_exec(bContext *C, wmOperator * /*op*/) +{ + VectorSet unique_curves = get_unique_editable_curves(*C); + for (Curves *curves_id : unique_curves) { + CurvesGeometry &curves = curves_id->geometry.wrap(); + select_linked(curves); + /* Use #ID_RECALC_GEOMETRY instead of #ID_RECALC_SELECT because it is handled as a generic + * attribute for now. */ + DEG_id_tag_update(&curves_id->id, ID_RECALC_GEOMETRY); + WM_event_add_notifier(C, NC_GEOM | ND_DATA, curves_id); + } + + return OPERATOR_FINISHED; +} + +static void CURVES_OT_select_linked(wmOperatorType *ot) +{ + ot->name = "Select Linked"; + ot->idname = __func__; + ot->description = "Select all points in curves with any point selection"; + + ot->exec = select_linked_exec; + ot->poll = editable_curves_point_domain_poll; + + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; +} + namespace surface_set { static bool surface_set_poll(bContext *C) @@ -1078,6 +1105,7 @@ void ED_operatortypes_curves() WM_operatortype_append(CURVES_OT_select_all); WM_operatortype_append(CURVES_OT_select_random); WM_operatortype_append(CURVES_OT_select_end); + WM_operatortype_append(CURVES_OT_select_linked); WM_operatortype_append(CURVES_OT_surface_set); } diff --git a/source/blender/editors/curves/intern/curves_selection.cc b/source/blender/editors/curves/intern/curves_selection.cc index 875ee2ae9ec..b285c96b584 100644 --- a/source/blender/editors/curves/intern/curves_selection.cc +++ b/source/blender/editors/curves/intern/curves_selection.cc @@ -164,6 +164,21 @@ bool has_anything_selected(const bke::CurvesGeometry &curves) return !selection || contains(selection, true); } +bool has_anything_selected(const GSpan selection) +{ + if (selection.type().is()) { + return selection.typed().contains(true); + } + else if (selection.type().is()) { + for (const float elem : selection.typed()) { + if (elem > 0.0f) { + return true; + } + } + } + return false; +} + static void invert_selection(MutableSpan selection) { threading::parallel_for(selection.index_range(), 2048, [&](IndexRange range) { @@ -203,14 +218,12 @@ void select_all(bke::CurvesGeometry &curves, const eAttrDomain selection_domain, } } -void select_ends(bke::CurvesGeometry &curves, - const eAttrDomain selection_domain, - int amount, - bool end_points) +void select_ends(bke::CurvesGeometry &curves, int amount, bool end_points) { const bool was_anything_selected = has_anything_selected(curves); + const OffsetIndices points_by_curve = curves.points_by_curve(); bke::GSpanAttributeWriter selection = ensure_selection_attribute( - curves, selection_domain, CD_PROP_BOOL); + curves, ATTR_DOMAIN_POINT, CD_PROP_BOOL); if (!was_anything_selected) { fill_selection_true(selection.span); } @@ -223,7 +236,6 @@ void select_ends(bke::CurvesGeometry &curves, MutableSpan selection_typed = selection.span.typed(); threading::parallel_for(curves.curves_range(), 256, [&](const IndexRange range) { for (const int curve_i : range) { - const OffsetIndices points_by_curve = curves.points_by_curve(); if (end_points) { selection_typed.slice(points_by_curve[curve_i].drop_back(amount)).fill(T(0)); } @@ -237,6 +249,23 @@ void select_ends(bke::CurvesGeometry &curves, selection.finish(); } +void select_linked(bke::CurvesGeometry &curves) +{ + const OffsetIndices points_by_curve = curves.points_by_curve(); + bke::GSpanAttributeWriter selection = ensure_selection_attribute( + curves, ATTR_DOMAIN_POINT, CD_PROP_BOOL); + + threading::parallel_for(curves.curves_range(), 256, [&](const IndexRange range) { + for (const int curve_i : range) { + GMutableSpan selection_curve = selection.span.slice(points_by_curve[curve_i]); + if (has_anything_selected(selection_curve)) { + fill_selection_true(selection_curve); + } + } + }); + selection.finish(); +} + void select_random(bke::CurvesGeometry &curves, const eAttrDomain selection_domain, uint32_t random_seed, diff --git a/source/blender/editors/include/ED_curves.h b/source/blender/editors/include/ED_curves.h index 734c1118dfc..ccbc69d55a6 100644 --- a/source/blender/editors/include/ED_curves.h +++ b/source/blender/editors/include/ED_curves.h @@ -89,6 +89,11 @@ void fill_selection_true(GMutableSpan span); */ bool has_anything_selected(const bke::CurvesGeometry &curves); +/** + * Return true if any element in the span is selected, on either domain with either type. + */ +bool has_anything_selected(GSpan selection); + /** * Find curves that have any point selected (a selection factor greater than zero), * or curves that have their own selection factor greater than zero. @@ -123,10 +128,12 @@ void select_all(bke::CurvesGeometry &curves, const eAttrDomain selection_domain, * \param amount: The amount of points to select from the front or back. * \param end_points: If true, select the last point(s), if false, select the first point(s). */ -void select_ends(bke::CurvesGeometry &curves, - const eAttrDomain selection_domain, - int amount, - bool end_points); +void select_ends(bke::CurvesGeometry &curves, int amount, bool end_points); + +/** + * Select the points of all curves that have at least one point selected. + */ +void select_linked(bke::CurvesGeometry &curves); /** * Select random points or curves.