Changeset View
Changeset View
Standalone View
Standalone View
source/blender/editors/curves/intern/curves_ops.cc
| /* SPDX-License-Identifier: GPL-2.0-or-later */ | /* SPDX-License-Identifier: GPL-2.0-or-later */ | ||||
| /** \file | /** \file | ||||
| * \ingroup edcurves | * \ingroup edcurves | ||||
| */ | */ | ||||
| #include <atomic> | #include <atomic> | ||||
| #include "BLI_array_utils.hh" | #include "BLI_array_utils.hh" | ||||
| #include "BLI_devirtualize_parameters.hh" | |||||
| #include "BLI_index_mask_ops.hh" | #include "BLI_index_mask_ops.hh" | ||||
| #include "BLI_kdtree.h" | |||||
HooglyBoogly: Not sure all these new includes are needed | |||||
| #include "BLI_rand.hh" | |||||
| #include "BLI_utildefines.h" | #include "BLI_utildefines.h" | ||||
| #include "BLI_vector_set.hh" | #include "BLI_vector_set.hh" | ||||
| #include "ED_curves.h" | #include "ED_curves.h" | ||||
| #include "ED_object.h" | #include "ED_object.h" | ||||
| #include "ED_screen.h" | #include "ED_screen.h" | ||||
| #include "ED_select_utils.h" | #include "ED_select_utils.h" | ||||
| #include "ED_view3d.h" | |||||
| #include "WM_api.h" | #include "WM_api.h" | ||||
| #include "BKE_attribute_math.hh" | #include "BKE_attribute_math.hh" | ||||
| #include "BKE_bvhutils.h" | #include "BKE_bvhutils.h" | ||||
| #include "BKE_context.h" | #include "BKE_context.h" | ||||
| #include "BKE_curves.hh" | #include "BKE_curves.hh" | ||||
| #include "BKE_geometry_set.hh" | #include "BKE_geometry_set.hh" | ||||
| Show All 17 Lines | |||||
| #include "DEG_depsgraph.h" | #include "DEG_depsgraph.h" | ||||
| #include "DEG_depsgraph_query.h" | #include "DEG_depsgraph_query.h" | ||||
| #include "RNA_access.h" | #include "RNA_access.h" | ||||
| #include "RNA_define.h" | #include "RNA_define.h" | ||||
| #include "RNA_enum_types.h" | #include "RNA_enum_types.h" | ||||
| #include "RNA_prototypes.h" | #include "RNA_prototypes.h" | ||||
| #include "UI_interface.h" | |||||
| #include "UI_resources.h" | |||||
| #include "GEO_reverse_uv_sampler.hh" | #include "GEO_reverse_uv_sampler.hh" | ||||
| /** | /** | ||||
| * The code below uses a suffix naming convention to indicate the coordinate space: | * The code below uses a suffix naming convention to indicate the coordinate space: | ||||
| * `cu`: Local space of the curves object that is being edited. | * `cu`: Local space of the curves object that is being edited. | ||||
| * `su`: Local space of the surface object. | * `su`: Local space of the surface object. | ||||
| * `wo`: World space. | * `wo`: World space. | ||||
| * `ha`: Local space of an individual hair in the legacy hair system. | * `ha`: Local space of an individual hair in the legacy hair system. | ||||
| ▲ Show 20 Lines • Show All 756 Lines • ▼ Show 20 Lines | static void CURVES_OT_set_selection_domain(wmOperatorType *ot) | ||||
| ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; | ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; | ||||
| ot->prop = prop = RNA_def_enum( | ot->prop = prop = RNA_def_enum( | ||||
| ot->srna, "domain", rna_enum_attribute_curves_domain_items, 0, "Domain", ""); | ot->srna, "domain", rna_enum_attribute_curves_domain_items, 0, "Domain", ""); | ||||
| RNA_def_property_flag(prop, (PropertyFlag)(PROP_HIDDEN | PROP_SKIP_SAVE)); | RNA_def_property_flag(prop, (PropertyFlag)(PROP_HIDDEN | PROP_SKIP_SAVE)); | ||||
| } | } | ||||
| static bool contains(const VArray<bool> &varray, const bool value) | static bool has_anything_selected(const Span<Curves *> curves_ids) | ||||
| { | { | ||||
| const CommonVArrayInfo info = varray.common_info(); | return std::any_of(curves_ids.begin(), curves_ids.end(), [](const Curves *curves_id) { | ||||
| if (info.type == CommonVArrayInfo::Type::Single) { | return has_anything_selected(CurvesGeometry::wrap(curves_id->geometry)); | ||||
| return *static_cast<const bool *>(info.data) == value; | }); | ||||
| } | |||||
| if (info.type == CommonVArrayInfo::Type::Span) { | |||||
| const Span<bool> span(static_cast<const bool *>(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) | static int select_all_exec(bContext *C, wmOperator *op) | ||||
| { | { | ||||
| const CurvesGeometry &curves = CurvesGeometry::wrap(curves_id.geometry); | int action = RNA_enum_get(op->ptr, "action"); | ||||
| const VArray<bool> selection = curves.attributes().lookup<bool>(".selection"); | |||||
| return !selection || contains(selection, true); | VectorSet<Curves *> unique_curves = get_unique_editable_curves(*C); | ||||
| if (action == SEL_TOGGLE) { | |||||
| action = has_anything_selected(unique_curves) ? SEL_DESELECT : SEL_SELECT; | |||||
| } | } | ||||
| static bool has_anything_selected(const Span<Curves *> curves_ids) | for (Curves *curves_id : unique_curves) { | ||||
| { | /* (De)select all the curves. */ | ||||
| return std::any_of(curves_ids.begin(), curves_ids.end(), [](const Curves *curves_id) { | select_all(CurvesGeometry::wrap(curves_id->geometry), | ||||
| return has_anything_selected(*curves_id); | 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. */ | |||||
| DEG_id_tag_update(&curves_id->id, ID_RECALC_GEOMETRY); | |||||
| WM_event_add_notifier(C, NC_GEOM | ND_DATA, curves_id); | |||||
| } | } | ||||
| namespace select_all { | return OPERATOR_FINISHED; | ||||
| } | |||||
| static void invert_selection(MutableSpan<float> selection) | static void CURVES_OT_select_all(wmOperatorType *ot) | ||||
| { | { | ||||
| threading::parallel_for(selection.index_range(), 2048, [&](IndexRange range) { | ot->name = "(De)select All"; | ||||
| for (const int i : range) { | ot->idname = "CURVES_OT_select_all"; | ||||
| selection[i] = 1.0f - selection[i]; | ot->description = "(De)select all control points"; | ||||
| } | |||||
| }); | ot->exec = select_all_exec; | ||||
| ot->poll = editable_curves_poll; | |||||
| ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; | |||||
| WM_operator_properties_select_all(ot); | |||||
| } | } | ||||
| static void invert_selection(GMutableSpan selection) | static int select_random_exec(bContext *C, wmOperator *op) | ||||
| { | { | ||||
| if (selection.type().is<bool>()) { | VectorSet<Curves *> unique_curves = curves::get_unique_editable_curves(*C); | ||||
| array_utils::invert_booleans(selection.typed<bool>()); | |||||
| 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); | |||||
| } | } | ||||
| else if (selection.type().is<float>()) { | return OPERATOR_FINISHED; | ||||
| invert_selection(selection.typed<float>()); | |||||
| } | } | ||||
| 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 int select_all_exec(bContext *C, wmOperator *op) | static void CURVES_OT_select_random(wmOperatorType *ot) | ||||
| { | { | ||||
| int action = RNA_enum_get(op->ptr, "action"); | ot->name = "Select Random"; | ||||
| ot->idname = __func__; | |||||
| ot->description = "Randomizes existing selection or create new random selection"; | |||||
| VectorSet<Curves *> unique_curves = get_unique_editable_curves(*C); | ot->exec = select_random_exec; | ||||
| ot->poll = curves::editable_curves_poll; | |||||
| ot->ui = select_random_ui; | |||||
| if (action == SEL_TOGGLE) { | ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; | ||||
| action = has_anything_selected(unique_curves) ? SEL_DESELECT : SEL_SELECT; | |||||
| } | |||||
| for (Curves *curves_id : unique_curves) { | RNA_def_int(ot->srna, | ||||
| CurvesGeometry &curves = CurvesGeometry::wrap(curves_id->geometry); | "seed", | ||||
| bke::MutableAttributeAccessor attributes = curves.attributes_for_write(); | 0, | ||||
| if (action == SEL_SELECT) { | INT32_MIN, | ||||
| /* As an optimization, just remove the selection attributes when everything is selected. */ | INT32_MAX, | ||||
| attributes.remove(".selection"); | "Seed", | ||||
| } | "Source of randomness", | ||||
| else if (!attributes.contains(".selection")) { | INT32_MIN, | ||||
| BLI_assert(ELEM(action, SEL_INVERT, SEL_DESELECT)); | INT32_MAX); | ||||
| /* If the attribute doesn't exist and it's either deleted or inverted, create | RNA_def_float(ot->srna, | ||||
| * it with nothing selected, since that means everything was selected before. */ | "probability", | ||||
| attributes.add(".selection", | 0.5f, | ||||
| eAttrDomain(curves_id->selection_domain), | 0.0f, | ||||
| CD_PROP_BOOL, | 1.0f, | ||||
| bke::AttributeInitDefaultValue()); | "Probability", | ||||
| "Chance of every point or curve being included in the selection", | |||||
| 0.0f, | |||||
| 1.0f); | |||||
| } | } | ||||
| else { | |||||
| bke::GSpanAttributeWriter selection = attributes.lookup_for_write_span(".selection"); | static bool select_end_poll(bContext *C) | ||||
| if (action == SEL_DESELECT) { | { | ||||
| fill_selection_false(selection.span); | if (!curves::editable_curves_poll(C)) { | ||||
| return false; | |||||
| } | } | ||||
| else if (action == SEL_INVERT) { | const Curves *curves_id = static_cast<const Curves *>(CTX_data_active_object(C)->data); | ||||
| invert_selection(selection.span); | if (curves_id->selection_domain != ATTR_DOMAIN_POINT) { | ||||
| CTX_wm_operator_poll_msg_set(C, "Only available in point selection mode"); | |||||
| return false; | |||||
| } | } | ||||
| selection.finish(); | return true; | ||||
| } | } | ||||
| static int select_end_exec(bContext *C, wmOperator *op) | |||||
| { | |||||
| VectorSet<Curves *> 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 | /* Use #ID_RECALC_GEOMETRY instead of #ID_RECALC_SELECT because it is handled as a generic | ||||
| * attribute for now. */ | * attribute for now. */ | ||||
| DEG_id_tag_update(&curves_id->id, ID_RECALC_GEOMETRY); | DEG_id_tag_update(&curves_id->id, ID_RECALC_GEOMETRY); | ||||
| WM_event_add_notifier(C, NC_GEOM | ND_DATA, curves_id); | WM_event_add_notifier(C, NC_GEOM | ND_DATA, curves_id); | ||||
| } | } | ||||
| return OPERATOR_FINISHED; | return OPERATOR_FINISHED; | ||||
| } | } | ||||
| } // namespace select_all | static void CURVES_OT_select_end(wmOperatorType *ot) | ||||
| static void CURVES_OT_select_all(wmOperatorType *ot) | |||||
| { | { | ||||
| ot->name = "(De)select All"; | ot->name = "Select End"; | ||||
| ot->idname = "CURVES_OT_select_all"; | ot->idname = __func__; | ||||
| ot->description = "(De)select all control points"; | ot->description = "Select end points of curves"; | ||||
| ot->exec = select_all::select_all_exec; | ot->exec = select_end_exec; | ||||
| ot->poll = editable_curves_poll; | ot->poll = select_end_poll; | ||||
| ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; | ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; | ||||
| WM_operator_properties_select_all(ot); | 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 { | namespace surface_set { | ||||
| static bool surface_set_poll(bContext *C) | static bool surface_set_poll(bContext *C) | ||||
| { | { | ||||
| const Object *object = CTX_data_active_object(C); | const Object *object = CTX_data_active_object(C); | ||||
| if (object == nullptr) { | if (object == nullptr) { | ||||
| ▲ Show 20 Lines • Show All 79 Lines • ▼ Show 20 Lines | |||||
| void ED_operatortypes_curves() | void ED_operatortypes_curves() | ||||
| { | { | ||||
| using namespace blender::ed::curves; | using namespace blender::ed::curves; | ||||
| WM_operatortype_append(CURVES_OT_convert_to_particle_system); | WM_operatortype_append(CURVES_OT_convert_to_particle_system); | ||||
| WM_operatortype_append(CURVES_OT_convert_from_particle_system); | WM_operatortype_append(CURVES_OT_convert_from_particle_system); | ||||
| WM_operatortype_append(CURVES_OT_snap_curves_to_surface); | WM_operatortype_append(CURVES_OT_snap_curves_to_surface); | ||||
| WM_operatortype_append(CURVES_OT_set_selection_domain); | WM_operatortype_append(CURVES_OT_set_selection_domain); | ||||
| WM_operatortype_append(CURVES_OT_select_all); | 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); | 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. */ | |||||
Not Done Inline ActionsGuessing this is copied from somewhere, but comment formatting still :) HooglyBoogly: Guessing this is copied from somewhere, but comment formatting still :) | |||||
| wmKeyMap *keymap = WM_keymap_ensure(keyconf, "Curves", 0, 0); | |||||
| keymap->poll = editable_curves_poll; | |||||
| } | |||||
Not sure all these new includes are needed