diff --git a/release/scripts/modules/bl_keymap_utils/keymap_hierarchy.py b/release/scripts/modules/bl_keymap_utils/keymap_hierarchy.py index 7172d7809f2..f183877749c 100644 --- a/release/scripts/modules/bl_keymap_utils/keymap_hierarchy.py +++ b/release/scripts/modules/bl_keymap_utils/keymap_hierarchy.py @@ -55,6 +55,7 @@ _km_hierarchy = [ ('Curve', 'EMPTY', 'WINDOW', [ _km_expand_from_toolsystem('VIEW_3D', 'EDIT_CURVE'), ]), + ('Curves', 'EMPTY', 'WINDOW', []), ('Armature', 'EMPTY', 'WINDOW', [ _km_expand_from_toolsystem('VIEW_3D', 'EDIT_ARMATURE'), ]), diff --git a/release/scripts/presets/keyconfig/keymap_data/blender_default.py b/release/scripts/presets/keyconfig/keymap_data/blender_default.py index 19093e51ec5..4149377581c 100644 --- a/release/scripts/presets/keyconfig/keymap_data/blender_default.py +++ b/release/scripts/presets/keyconfig/keymap_data/blender_default.py @@ -5615,6 +5615,14 @@ def km_curves(params): {"items": items}, ) + items.extend([ + ("curves.set_selection_domain", {"type": 'ONE', "value": 'PRESS'}, {"properties": [("domain", 'POINT')]}), + ("curves.set_selection_domain", {"type": 'TWO', "value": 'PRESS'}, {"properties": [("domain", 'CURVE')]}), + ("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"), + ]) + return keymap diff --git a/release/scripts/startup/bl_ui/space_view3d.py b/release/scripts/startup/bl_ui/space_view3d.py index 3bb009dd2ad..938399485d3 100644 --- a/release/scripts/startup/bl_ui/space_view3d.py +++ b/release/scripts/startup/bl_ui/space_view3d.py @@ -718,7 +718,7 @@ class VIEW3D_HT_header(Header): if object_mode == 'PARTICLE_EDIT': row = layout.row() row.prop(tool_settings.particle_edit, "select_mode", text="", expand=True) - elif object_mode == 'SCULPT_CURVES' and obj.type == 'CURVES': + elif object_mode in {'EDIT', 'SCULPT_CURVES'} and obj.type == 'CURVES': curves = obj.data row = layout.row(align=True) @@ -2044,7 +2044,13 @@ class VIEW3D_MT_select_edit_curves(Menu): bl_label = "Select" def draw(self, _context): - pass + layout = self.layout + + layout.operator("curves.select_all", text="All").action = 'SELECT' + layout.operator("curves.select_all", text="None").action = 'DESELECT' + layout.operator("curves.select_all", text="Invert").action = 'INVERT' + layout.operator("curves.select_random", text="Random") + layout.operator("curves.select_end", text="Endpoints") class VIEW3D_MT_select_sculpt_curves(Menu): @@ -2057,7 +2063,7 @@ class VIEW3D_MT_select_sculpt_curves(Menu): layout.operator("curves.select_all", text="None").action = 'DESELECT' layout.operator("curves.select_all", text="Invert").action = 'INVERT' layout.operator("sculpt_curves.select_random", text="Random") - layout.operator("sculpt_curves.select_end", text="Endpoints") + layout.operator("curves.select_end", text="Endpoints") layout.operator("sculpt_curves.select_grow", text="Grow") diff --git a/source/blender/editors/curves/intern/curves_ops.cc b/source/blender/editors/curves/intern/curves_ops.cc index 24aa362b973..d0a68774c71 100644 --- a/source/blender/editors/curves/intern/curves_ops.cc +++ b/source/blender/editors/curves/intern/curves_ops.cc @@ -7,7 +7,10 @@ #include #include "BLI_array_utils.hh" +#include "BLI_devirtualize_parameters.hh" #include "BLI_index_mask_ops.hh" +#include "BLI_kdtree.h" +#include "BLI_rand.hh" #include "BLI_utildefines.h" #include "BLI_vector_set.hh" @@ -15,6 +18,7 @@ #include "ED_object.h" #include "ED_screen.h" #include "ED_select_utils.h" +#include "ED_view3d.h" #include "WM_api.h" @@ -48,6 +52,9 @@ #include "RNA_enum_types.h" #include "RNA_prototypes.h" +#include "UI_interface.h" +#include "UI_resources.h" + #include "GEO_reverse_uv_sampler.hh" /** @@ -820,77 +827,13 @@ static void CURVES_OT_set_selection_domain(wmOperatorType *ot) RNA_def_property_flag(prop, (PropertyFlag)(PROP_HIDDEN | PROP_SKIP_SAVE)); } -static bool contains(const VArray &varray, const bool value) -{ - const CommonVArrayInfo info = varray.common_info(); - if (info.type == CommonVArrayInfo::Type::Single) { - return *static_cast(info.data) == value; - } - if (info.type == CommonVArrayInfo::Type::Span) { - const Span span(static_cast(info.data), varray.size()); - return threading::parallel_reduce( - span.index_range(), - 4096, - false, - [&](const IndexRange range, const bool init) { - return init || span.slice(range).contains(value); - }, - [&](const bool a, const bool b) { return a || b; }); - } - return threading::parallel_reduce( - varray.index_range(), - 2048, - false, - [&](const IndexRange range, const bool init) { - if (init) { - return init; - } - /* Alternatively, this could use #materialize to retrieve many values at once. */ - for (const int64_t i : range) { - if (varray[i] == value) { - return true; - } - } - return false; - }, - [&](const bool a, const bool b) { return a || b; }); -} - -bool has_anything_selected(const Curves &curves_id) -{ - const CurvesGeometry &curves = CurvesGeometry::wrap(curves_id.geometry); - const VArray selection = curves.attributes().lookup(".selection"); - return !selection || contains(selection, true); -} - static bool has_anything_selected(const Span curves_ids) { return std::any_of(curves_ids.begin(), curves_ids.end(), [](const Curves *curves_id) { - return has_anything_selected(*curves_id); + return has_anything_selected(CurvesGeometry::wrap(curves_id->geometry)); }); } -namespace select_all { - -static void invert_selection(MutableSpan selection) -{ - threading::parallel_for(selection.index_range(), 2048, [&](IndexRange range) { - for (const int i : range) { - selection[i] = 1.0f - selection[i]; - } - }); -} - -static void invert_selection(GMutableSpan selection) -{ - if (selection.type().is()) { - array_utils::invert_booleans(selection.typed()); - } - else if (selection.type().is()) { - invert_selection(selection.typed()); - } -} - static int select_all_exec(bContext *C, wmOperator *op) { int action = RNA_enum_get(op->ptr, "action"); @@ -902,31 +845,10 @@ static int select_all_exec(bContext *C, wmOperator *op) } for (Curves *curves_id : unique_curves) { - CurvesGeometry &curves = CurvesGeometry::wrap(curves_id->geometry); - bke::MutableAttributeAccessor attributes = curves.attributes_for_write(); - if (action == SEL_SELECT) { - /* As an optimization, just remove the selection attributes when everything is selected. */ - attributes.remove(".selection"); - } - else if (!attributes.contains(".selection")) { - BLI_assert(ELEM(action, SEL_INVERT, SEL_DESELECT)); - /* If the attribute doesn't exist and it's either deleted or inverted, create - * it with nothing selected, since that means everything was selected before. */ - attributes.add(".selection", - eAttrDomain(curves_id->selection_domain), - CD_PROP_BOOL, - bke::AttributeInitDefaultValue()); - } - else { - bke::GSpanAttributeWriter selection = attributes.lookup_for_write_span(".selection"); - if (action == SEL_DESELECT) { - fill_selection_false(selection.span); - } - else if (action == SEL_INVERT) { - invert_selection(selection.span); - } - selection.finish(); - } + /* (De)select all the curves. */ + select_all(CurvesGeometry::wrap(curves_id->geometry), + eAttrDomain(curves_id->selection_domain), + action); /* Use #ID_RECALC_GEOMETRY instead of #ID_RECALC_SELECT because it is handled as a generic * attribute for now. */ @@ -937,15 +859,13 @@ static int select_all_exec(bContext *C, wmOperator *op) return OPERATOR_FINISHED; } -} // namespace select_all - static void CURVES_OT_select_all(wmOperatorType *ot) { ot->name = "(De)select All"; ot->idname = "CURVES_OT_select_all"; ot->description = "(De)select all control points"; - ot->exec = select_all::select_all_exec; + ot->exec = select_all_exec; ot->poll = editable_curves_poll; ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; @@ -953,6 +873,117 @@ static void CURVES_OT_select_all(wmOperatorType *ot) WM_operator_properties_select_all(ot); } +static int select_random_exec(bContext *C, wmOperator *op) +{ + VectorSet unique_curves = curves::get_unique_editable_curves(*C); + + const int seed = RNA_int_get(op->ptr, "seed"); + const float probability = RNA_float_get(op->ptr, "probability"); + + for (Curves *curves_id : unique_curves) { + CurvesGeometry &curves = CurvesGeometry::wrap(curves_id->geometry); + select_random(curves, eAttrDomain(curves_id->selection_domain), uint32_t(seed), probability); + + /* 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 select_random_ui(bContext * /*C*/, wmOperator *op) +{ + uiLayout *layout = op->layout; + + uiItemR(layout, op->ptr, "seed", 0, nullptr, ICON_NONE); + uiItemR(layout, op->ptr, "probability", UI_ITEM_R_SLIDER, "Probability", ICON_NONE); +} + +static void CURVES_OT_select_random(wmOperatorType *ot) +{ + ot->name = "Select Random"; + ot->idname = __func__; + ot->description = "Randomizes existing selection or create new random selection"; + + ot->exec = select_random_exec; + ot->poll = curves::editable_curves_poll; + ot->ui = select_random_ui; + + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + RNA_def_int(ot->srna, + "seed", + 0, + INT32_MIN, + INT32_MAX, + "Seed", + "Source of randomness", + INT32_MIN, + INT32_MAX); + RNA_def_float(ot->srna, + "probability", + 0.5f, + 0.0f, + 1.0f, + "Probability", + "Chance of every point or curve being included in the selection", + 0.0f, + 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); + const bool end_points = RNA_boolean_get(op->ptr, "end_points"); + const int amount = RNA_int_get(op->ptr, "amount"); + + for (Curves *curves_id : unique_curves) { + CurvesGeometry &curves = CurvesGeometry::wrap(curves_id->geometry); + select_ends(curves, eAttrDomain(curves_id->selection_domain), amount, end_points); + + /* 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_end(wmOperatorType *ot) +{ + ot->name = "Select End"; + ot->idname = __func__; + ot->description = "Select end points of curves"; + + ot->exec = select_end_exec; + ot->poll = select_end_poll; + + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + RNA_def_boolean(ot->srna, + "end_points", + true, + "End Points", + "Select points at the end of the curve as opposed to the beginning"); + RNA_def_int( + ot->srna, "amount", 1, 0, INT32_MAX, "Amount", "Number of points to select", 0, INT32_MAX); +} + namespace surface_set { static bool surface_set_poll(bContext *C) @@ -1046,5 +1077,15 @@ void ED_operatortypes_curves() WM_operatortype_append(CURVES_OT_snap_curves_to_surface); WM_operatortype_append(CURVES_OT_set_selection_domain); 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_surface_set); } + +void ED_keymap_curves(wmKeyConfig *keyconf) +{ + using namespace blender::ed::curves; + /* Only set in editmode curves, by space_view3d listener. */ + wmKeyMap *keymap = WM_keymap_ensure(keyconf, "Curves", 0, 0); + keymap->poll = editable_curves_poll; +} diff --git a/source/blender/editors/curves/intern/curves_selection.cc b/source/blender/editors/curves/intern/curves_selection.cc index b9b3be976c5..9ed25473a0c 100644 --- a/source/blender/editors/curves/intern/curves_selection.cc +++ b/source/blender/editors/curves/intern/curves_selection.cc @@ -4,13 +4,16 @@ * \ingroup edcurves */ +#include "BLI_array_utils.hh" #include "BLI_index_mask_ops.hh" +#include "BLI_rand.hh" #include "BKE_attribute.hh" #include "BKE_curves.hh" #include "ED_curves.h" #include "ED_object.h" +#include "ED_select_utils.h" namespace blender::ed::curves { @@ -69,31 +72,32 @@ IndexMask retrieve_selected_points(const Curves &curves_id, Vector &r_i return retrieve_selected_points(curves, r_indices); } -void ensure_selection_attribute(Curves &curves_id, const eCustomDataType create_type) +bke::GSpanAttributeWriter ensure_selection_attribute(bke::CurvesGeometry &curves, + const eAttrDomain selection_domain, + const eCustomDataType create_type) { - bke::CurvesGeometry &curves = bke::CurvesGeometry::wrap(curves_id.geometry); bke::MutableAttributeAccessor attributes = curves.attributes_for_write(); if (attributes.contains(".selection")) { - return; + return attributes.lookup_for_write_span(".selection"); } - const eAttrDomain domain = eAttrDomain(curves_id.selection_domain); - const int domain_size = attributes.domain_size(domain); + const int domain_size = attributes.domain_size(selection_domain); switch (create_type) { case CD_PROP_BOOL: attributes.add(".selection", - domain, + selection_domain, CD_PROP_BOOL, bke::AttributeInitVArray(VArray::ForSingle(true, domain_size))); break; case CD_PROP_FLOAT: attributes.add(".selection", - domain, + selection_domain, CD_PROP_FLOAT, bke::AttributeInitVArray(VArray::ForSingle(1.0f, domain_size))); break; default: BLI_assert_unreachable(); } + return attributes.lookup_for_write_span(".selection"); } void fill_selection_false(GMutableSpan selection) @@ -105,6 +109,7 @@ void fill_selection_false(GMutableSpan selection) selection.typed().fill(0.0f); } } + void fill_selection_true(GMutableSpan selection) { if (selection.type().is()) { @@ -115,4 +120,168 @@ void fill_selection_true(GMutableSpan selection) } } +static bool contains(const VArray &varray, const bool value) +{ + const CommonVArrayInfo info = varray.common_info(); + if (info.type == CommonVArrayInfo::Type::Single) { + return *static_cast(info.data) == value; + } + if (info.type == CommonVArrayInfo::Type::Span) { + const Span span(static_cast(info.data), varray.size()); + return threading::parallel_reduce( + span.index_range(), + 4096, + false, + [&](const IndexRange range, const bool init) { + return init || span.slice(range).contains(value); + }, + [&](const bool a, const bool b) { return a || b; }); + } + return threading::parallel_reduce( + varray.index_range(), + 2048, + false, + [&](const IndexRange range, const bool init) { + if (init) { + return init; + } + /* Alternatively, this could use #materialize to retrieve many values at once. */ + for (const int64_t i : range) { + if (varray[i] == value) { + return true; + } + } + return false; + }, + [&](const bool a, const bool b) { return a || b; }); +} + +bool has_anything_selected(const bke::CurvesGeometry &curves) +{ + const VArray selection = curves.attributes().lookup(".selection"); + return !selection || contains(selection, true); +} + +static void invert_selection(MutableSpan selection) +{ + threading::parallel_for(selection.index_range(), 2048, [&](IndexRange range) { + for (const int i : range) { + selection[i] = 1.0f - selection[i]; + } + }); +} + +static void invert_selection(GMutableSpan selection) +{ + if (selection.type().is()) { + array_utils::invert_booleans(selection.typed()); + } + else if (selection.type().is()) { + invert_selection(selection.typed()); + } +} + +void select_all(bke::CurvesGeometry &curves, const eAttrDomain selection_domain, int action) +{ + bke::MutableAttributeAccessor attributes = curves.attributes_for_write(); + if (action == SEL_SELECT) { + /* As an optimization, just remove the selection attributes when everything is selected. */ + attributes.remove(".selection"); + } + else { + bke::GSpanAttributeWriter selection = ensure_selection_attribute( + curves, selection_domain, CD_PROP_BOOL); + if (action == SEL_DESELECT) { + fill_selection_false(selection.span); + } + else if (action == SEL_INVERT) { + invert_selection(selection.span); + } + selection.finish(); + } +} + +void select_ends(bke::CurvesGeometry &curves, + const eAttrDomain selection_domain, + int amount, + bool end_points) +{ + const bool was_anything_selected = has_anything_selected(curves); + bke::GSpanAttributeWriter selection = ensure_selection_attribute( + curves, selection_domain, CD_PROP_BOOL); + if (!was_anything_selected) { + fill_selection_true(selection.span); + } + selection.span.type().to_static_type_tag([&](auto type_tag) { + using T = typename decltype(type_tag)::type; + if constexpr (std::is_void_v) { + BLI_assert_unreachable(); + } + else { + 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)); + } + else { + selection_typed.slice(points_by_curve[curve_i].drop_front(amount)).fill(T(0)); + } + } + }); + } + }); + selection.finish(); +} + +void select_random(bke::CurvesGeometry &curves, + const eAttrDomain selection_domain, + uint32_t random_seed, + float probability) +{ + RandomNumberGenerator rng{random_seed}; + const auto next_bool_random_value = [&]() { return rng.get_float() <= probability; }; + + const bool was_anything_selected = has_anything_selected(curves); + bke::GSpanAttributeWriter selection = ensure_selection_attribute( + curves, selection_domain, CD_PROP_BOOL); + if (!was_anything_selected) { + curves::fill_selection_true(selection.span); + } + selection.span.type().to_static_type_tag([&](auto type_tag) { + using T = typename decltype(type_tag)::type; + if constexpr (std::is_void_v) { + BLI_assert_unreachable(); + } + else { + MutableSpan selection_typed = selection.span.typed(); + switch (selection_domain) { + case ATTR_DOMAIN_POINT: { + for (const int point_i : selection_typed.index_range()) { + const bool random_value = next_bool_random_value(); + if (!random_value) { + selection_typed[point_i] = T(0); + } + } + + break; + } + case ATTR_DOMAIN_CURVE: { + for (const int curve_i : curves.curves_range()) { + const bool random_value = next_bool_random_value(); + if (!random_value) { + selection_typed[curve_i] = T(0); + } + } + break; + } + default: + BLI_assert_unreachable(); + } + } + }); + selection.finish(); +} + } // namespace blender::ed::curves diff --git a/source/blender/editors/include/ED_curves.h b/source/blender/editors/include/ED_curves.h index 74eb290e98a..9cb673ff9e4 100644 --- a/source/blender/editors/include/ED_curves.h +++ b/source/blender/editors/include/ED_curves.h @@ -19,6 +19,7 @@ extern "C" { void ED_operatortypes_curves(void); void ED_curves_undosys_type(struct UndoType *ut); +void ED_keymap_curves(struct wmKeyConfig *keyconf); /** * Return an owning pointer to an array of point normals the same size as the number of control @@ -80,7 +81,7 @@ void fill_selection_true(GMutableSpan span); /** * Return true if any element is selected, on either domain with either type. */ -bool has_anything_selected(const Curves &curves_id); +bool has_anything_selected(const bke::CurvesGeometry &curves); /** * Find curves that have any point selected (a selection factor greater than zero), @@ -97,8 +98,40 @@ IndexMask retrieve_selected_points(const Curves &curves_id, Vector &r_i /** * If the ".selection" attribute doesn't exist, create it with the requested type (bool or float). */ -void ensure_selection_attribute(Curves &curves_id, const eCustomDataType create_type); +bke::GSpanAttributeWriter ensure_selection_attribute(bke::CurvesGeometry &curves, + const eAttrDomain selection_domain, + const eCustomDataType create_type); +/** + * (De)select all the curves. + * + * \param action: One of SEL_TOGGLE, SEL_SELECT, SEL_DESELECT, or SEL_INVERT. See + * "ED_select_utils.h". + */ +void select_all(bke::CurvesGeometry &curves, const eAttrDomain selection_domain, int action); + +/** + * Select the ends (front or back) of all the curves. + * + * \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); + +/** + * Select random points or curves. + * + * \param random_seed: The seed for the \a RandomNumberGenerator. + * \param probability: Determins how likely a point/curve will be selected. If set to 0.0, nothing + * will be selected, if set to 1.0 everything will be selected. + */ +void select_random(bke::CurvesGeometry &curves, + const eAttrDomain selection_domain, + uint32_t random_seed, + float probability); /** \} */ } // namespace blender::ed::curves diff --git a/source/blender/editors/sculpt_paint/curves_sculpt_ops.cc b/source/blender/editors/sculpt_paint/curves_sculpt_ops.cc index 444ab821cf1..39575015381 100644 --- a/source/blender/editors/sculpt_paint/curves_sculpt_ops.cc +++ b/source/blender/editors/sculpt_paint/curves_sculpt_ops.cc @@ -363,7 +363,7 @@ static int select_random_exec(bContext *C, wmOperator *op) for (Curves *curves_id : unique_curves) { CurvesGeometry &curves = CurvesGeometry::wrap(curves_id->geometry); - const bool was_anything_selected = curves::has_anything_selected(*curves_id); + const bool was_anything_selected = curves::has_anything_selected(curves); bke::SpanAttributeWriter attribute = float_selection_ensure(*curves_id); MutableSpan selection = attribute.span; @@ -517,90 +517,6 @@ static void SCULPT_CURVES_OT_select_random(wmOperatorType *ot) "Constant per Curve", "The generated random number is the same for every control point of a curve"); } - -namespace select_end { -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); - const bool end_points = RNA_boolean_get(op->ptr, "end_points"); - const int amount = RNA_int_get(op->ptr, "amount"); - - for (Curves *curves_id : unique_curves) { - CurvesGeometry &curves = CurvesGeometry::wrap(curves_id->geometry); - bke::MutableAttributeAccessor attributes = curves.attributes_for_write(); - - const bool was_anything_selected = curves::has_anything_selected(*curves_id); - curves::ensure_selection_attribute(*curves_id, CD_PROP_BOOL); - bke::GSpanAttributeWriter selection = attributes.lookup_for_write_span(".selection"); - if (!was_anything_selected) { - curves::fill_selection_true(selection.span); - } - const OffsetIndices points_by_curve = curves.points_by_curve(); - selection.span.type().to_static_type_tag([&](auto type_tag) { - using T = typename decltype(type_tag)::type; - if constexpr (std::is_void_v) { - BLI_assert_unreachable(); - } - else { - MutableSpan selection_typed = selection.span.typed(); - threading::parallel_for(curves.curves_range(), 256, [&](const IndexRange range) { - for (const int curve_i : range) { - const IndexRange points = points_by_curve[curve_i]; - if (end_points) { - selection_typed.slice(points.drop_back(amount)).fill(T(0)); - } - else { - selection_typed.slice(points.drop_front(amount)).fill(T(0)); - } - } - }); - } - }); - 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(&curves_id->id, ID_RECALC_GEOMETRY); - WM_event_add_notifier(C, NC_GEOM | ND_DATA, curves_id); - } - - return OPERATOR_FINISHED; -} -} // namespace select_end - -static void SCULPT_CURVES_OT_select_end(wmOperatorType *ot) -{ - ot->name = "Select End"; - ot->idname = __func__; - ot->description = "Select end points of curves"; - - ot->exec = select_end::select_end_exec; - ot->poll = select_end::select_end_poll; - - ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; - - RNA_def_boolean(ot->srna, - "end_points", - true, - "End Points", - "Select points at the end of the curve as opposed to the beginning"); - RNA_def_int( - ot->srna, "amount", 1, 0, INT32_MAX, "Amount", "Number of points to select", 0, INT32_MAX); -} - namespace select_grow { struct GrowOperatorDataPerCurve : NonCopyable, NonMovable { @@ -1263,7 +1179,6 @@ void ED_operatortypes_sculpt_curves() WM_operatortype_append(SCULPT_CURVES_OT_brush_stroke); WM_operatortype_append(CURVES_OT_sculptmode_toggle); WM_operatortype_append(SCULPT_CURVES_OT_select_random); - WM_operatortype_append(SCULPT_CURVES_OT_select_end); WM_operatortype_append(SCULPT_CURVES_OT_select_grow); WM_operatortype_append(SCULPT_CURVES_OT_min_distance_edit); } diff --git a/source/blender/editors/space_api/spacetypes.c b/source/blender/editors/space_api/spacetypes.c index 3d964a95bc0..9690d8bd860 100644 --- a/source/blender/editors/space_api/spacetypes.c +++ b/source/blender/editors/space_api/spacetypes.c @@ -186,6 +186,7 @@ void ED_spacetypes_keymap(wmKeyConfig *keyconf) ED_keymap_mesh(keyconf); ED_keymap_uvedit(keyconf); ED_keymap_curve(keyconf); + ED_keymap_curves(keyconf); ED_keymap_armature(keyconf); ED_keymap_physics(keyconf); ED_keymap_metaball(keyconf); diff --git a/source/blender/editors/space_view3d/space_view3d.cc b/source/blender/editors/space_view3d/space_view3d.cc index 05fb0c6a720..396e75f6b4e 100644 --- a/source/blender/editors/space_view3d/space_view3d.cc +++ b/source/blender/editors/space_view3d/space_view3d.cc @@ -407,6 +407,9 @@ static void view3d_main_region_init(wmWindowManager *wm, ARegion *region) keymap = WM_keymap_ensure(wm->defaultconf, "Curve", 0, 0); WM_event_add_keymap_handler(®ion->handlers, keymap); + keymap = WM_keymap_ensure(wm->defaultconf, "Curves", 0, 0); + WM_event_add_keymap_handler(®ion->handlers, keymap); + keymap = WM_keymap_ensure(wm->defaultconf, "Image Paint", 0, 0); WM_event_add_keymap_handler(®ion->handlers, keymap);