diff --git a/scripts/startup/bl_ui/space_view3d.py b/scripts/startup/bl_ui/space_view3d.py index 5b136a43353..54de1753fb9 100644 --- a/scripts/startup/bl_ui/space_view3d.py +++ b/scripts/startup/bl_ui/space_view3d.py @@ -2325,6 +2325,10 @@ class VIEW3D_MT_select_edit_pointcloud(Menu): layout.operator("pointcloud.select_all", text="None").action = 'DESELECT' layout.operator("pointcloud.select_all", text="Invert").action = 'INVERT' + layout.separator() + + layout.operator("pointcloud.select_random") + layout.template_node_operator_asset_menu_items(catalog_path=self.bl_label) diff --git a/source/blender/blenlib/BLI_index_mask.hh b/source/blender/blenlib/BLI_index_mask.hh index f2111a7ba09..6c3ee40fc77 100644 --- a/source/blender/blenlib/BLI_index_mask.hh +++ b/source/blender/blenlib/BLI_index_mask.hh @@ -1112,6 +1112,26 @@ inline void index_range_to_mask_segments(const IndexRange range, } } +/** + * Return a mask of random points or curves. + * + * \param mask: (optional) The elements that should be used in the resulting mask. + * \param universe_size: The size of the mask. + * \param random_seed: The seed for the \a RandomNumberGenerator. + * \param probability: Determines how likely a point/curve will be chosen. + * If set to 0.0, nothing will be in the mask, if set to 1.0 everything will be in the mask. + */ +IndexMask random_mask(const IndexMask &mask, + const int64_t universe_size, + const uint32_t random_seed, + const float probability, + IndexMaskMemory &memory); + +IndexMask random_mask(const int64_t universe_size, + const uint32_t random_seed, + const float probability, + IndexMaskMemory &memory); + } // namespace blender::index_mask namespace blender { diff --git a/source/blender/blenlib/intern/index_mask.cc b/source/blender/blenlib/intern/index_mask.cc index 6bc4b97cbbd..21ce39e8350 100644 --- a/source/blender/blenlib/intern/index_mask.cc +++ b/source/blender/blenlib/intern/index_mask.cc @@ -16,6 +16,7 @@ #include "BLI_index_mask_expression.hh" #include "BLI_index_ranges_builder.hh" #include "BLI_math_base.hh" +#include "BLI_rand.hh" #include "BLI_set.hh" #include "BLI_sort.hh" #include "BLI_task.hh" @@ -1227,4 +1228,29 @@ template IndexMask IndexMask::from_ranges(OffsetIndices, const IndexMask &, IndexMaskMemory &); +IndexMask random_mask(const IndexMask &mask, + const int64_t universe_size, + const uint32_t random_seed, + const float probability, + IndexMaskMemory &memory) +{ + RandomNumberGenerator rng{random_seed}; + const auto next_bool_random_value = [&]() { return rng.get_float() <= probability; }; + + Array random(universe_size, false); + mask.foreach_index_optimized( + [&](const int64_t i) { random[i] = next_bool_random_value(); }); + + return IndexMask::from_bools(IndexRange(universe_size), random, memory); +} + +IndexMask random_mask(const int64_t universe_size, + const uint32_t random_seed, + const float probability, + IndexMaskMemory &memory) +{ + const IndexRange selection(universe_size); + return random_mask(selection, universe_size, random_seed, probability, memory); +} + } // namespace blender::index_mask diff --git a/source/blender/editors/curves/intern/curves_masks.cc b/source/blender/editors/curves/intern/curves_masks.cc index a9222ba1166..e0ac255f533 100644 --- a/source/blender/editors/curves/intern/curves_masks.cc +++ b/source/blender/editors/curves/intern/curves_masks.cc @@ -7,7 +7,6 @@ */ #include "BLI_offset_indices.hh" -#include "BLI_rand.hh" #include "BKE_attribute.hh" #include "BKE_curves.hh" @@ -65,33 +64,4 @@ IndexMask end_points(const bke::CurvesGeometry &curves, return end_points(curves, curves.curves_range(), amount_start, amount_end, inverted, memory); } -IndexMask random_mask(const bke::CurvesGeometry &curves, - const IndexMask &mask, - const bke::AttrDomain selection_domain, - const uint32_t random_seed, - const float probability, - IndexMaskMemory &memory) -{ - RandomNumberGenerator rng{random_seed}; - const auto next_bool_random_value = [&]() { return rng.get_float() <= probability; }; - - const int64_t domain_size = curves.attributes().domain_size(selection_domain); - - Array random(domain_size, false); - mask.foreach_index_optimized( - [&](const int64_t i) { random[i] = next_bool_random_value(); }); - - return IndexMask::from_bools(IndexRange(domain_size), random, memory); -} - -IndexMask random_mask(const bke::CurvesGeometry &curves, - const bke::AttrDomain selection_domain, - const uint32_t random_seed, - const float probability, - IndexMaskMemory &memory) -{ - const IndexRange selection(curves.attributes().domain_size(selection_domain)); - return random_mask(curves, selection, selection_domain, random_seed, probability, memory); -} - } // namespace blender::ed::curves diff --git a/source/blender/editors/curves/intern/curves_ops.cc b/source/blender/editors/curves/intern/curves_ops.cc index 7d4a5f8c9ff..18ec15c5e68 100644 --- a/source/blender/editors/curves/intern/curves_ops.cc +++ b/source/blender/editors/curves/intern/curves_ops.cc @@ -914,8 +914,7 @@ static int select_random_exec(bContext *C, wmOperator *op) const int domain_size = curves.attributes().domain_size(selection_domain); IndexMaskMemory memory; - const IndexMask inv_random_elements = random_mask( - curves, selection_domain, seed, probability, memory) + const IndexMask inv_random_elements = random_mask(domain_size, seed, probability, memory) .complement(IndexRange(domain_size), memory); const bool was_anything_selected = has_anything_selected(curves); diff --git a/source/blender/editors/grease_pencil/intern/grease_pencil_select.cc b/source/blender/editors/grease_pencil/intern/grease_pencil_select.cc index ff138ae9922..110112a26ce 100644 --- a/source/blender/editors/grease_pencil/intern/grease_pencil_select.cc +++ b/source/blender/editors/grease_pencil/intern/grease_pencil_select.cc @@ -13,6 +13,7 @@ #include "BKE_object.hh" #include "BLI_enumerable_thread_specific.hh" +#include "BLI_index_mask.hh" #include "BLI_offset_indices.hh" #include "BLI_task.hh" @@ -501,12 +502,11 @@ static int select_random_exec(bContext *C, wmOperator *op) if (selectable_elements.is_empty()) { return {}; } - return ed::curves::random_mask(info.drawing.strokes(), - selectable_elements, - selection_domain, - blender::get_default_hash(seed, info.layer_index), - ratio, - memory); + return random_mask(selectable_elements, + info.drawing.strokes().points_num(), + blender::get_default_hash(seed, info.layer_index), + ratio, + memory); }); /* Use #ID_RECALC_GEOMETRY instead of #ID_RECALC_SELECT because it is handled as a generic diff --git a/source/blender/editors/include/ED_curves.hh b/source/blender/editors/include/ED_curves.hh index 13afe48d9a8..1979f44f57c 100644 --- a/source/blender/editors/include/ED_curves.hh +++ b/source/blender/editors/include/ED_curves.hh @@ -176,26 +176,6 @@ IndexMask end_points(const bke::CurvesGeometry &curves, bool inverted, IndexMaskMemory &memory); -/** - * Return a mask of random points or curves. - * - * \param mask: (optional) The elements that should be used in the resulting mask. This mask should - * be in the same domain as the \a selection_domain. \param random_seed: The seed for the \a - * RandomNumberGenerator. \param probability: Determines how likely a point/curve will be chosen. - * If set to 0.0, nothing will be in the mask, if set to 1.0 everything will be in the mask. - */ -IndexMask random_mask(const bke::CurvesGeometry &curves, - bke::AttrDomain selection_domain, - uint32_t random_seed, - float probability, - IndexMaskMemory &memory); -IndexMask random_mask(const bke::CurvesGeometry &curves, - const IndexMask &mask, - bke::AttrDomain selection_domain, - uint32_t random_seed, - float probability, - IndexMaskMemory &memory); - /** \} */ /* -------------------------------------------------------------------- */ diff --git a/source/blender/editors/include/ED_pointcloud.hh b/source/blender/editors/include/ED_pointcloud.hh index 9e5c58cbecb..17294c0b6da 100644 --- a/source/blender/editors/include/ED_pointcloud.hh +++ b/source/blender/editors/include/ED_pointcloud.hh @@ -56,6 +56,7 @@ VectorSet get_unique_editable_pointclouds(const bContext &C); * helpful utilities on top of that. * \{ */ +void fill_selection_true(GMutableSpan span); void fill_selection_false(GMutableSpan selection, const IndexMask &mask); void fill_selection_true(GMutableSpan selection, const IndexMask &mask); diff --git a/source/blender/editors/pointcloud/intern/operators.cc b/source/blender/editors/pointcloud/intern/operators.cc index 968b6140d44..1fcea6b725f 100644 --- a/source/blender/editors/pointcloud/intern/operators.cc +++ b/source/blender/editors/pointcloud/intern/operators.cc @@ -139,6 +139,74 @@ static void POINTCLOUD_OT_select_all(wmOperatorType *ot) WM_operator_properties_select_all(ot); } +static int select_random_exec(bContext *C, wmOperator *op) +{ + const int seed = RNA_int_get(op->ptr, "seed"); + const float probability = RNA_float_get(op->ptr, "probability"); + + for (PointCloud *pointcloud : get_unique_editable_pointclouds(*C)) { + IndexMaskMemory memory; + const IndexMask inv_random_elements = random_mask( + pointcloud->totpoint, seed, probability, memory) + .complement(IndexRange(pointcloud->totpoint), + memory); + const bool was_anything_selected = has_anything_selected(*pointcloud); + bke::GSpanAttributeWriter selection = ensure_selection_attribute(*pointcloud, CD_PROP_BOOL); + if (!was_anything_selected) { + pointcloud::fill_selection_true(selection.span); + } + + pointcloud::fill_selection_false(selection.span, inv_random_elements); + 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(&pointcloud->id, ID_RECALC_GEOMETRY); + WM_event_add_notifier(C, NC_GEOM | ND_DATA, pointcloud); + } + return OPERATOR_FINISHED; +} + +static void select_random_ui(bContext * /*C*/, wmOperator *op) +{ + uiLayout *layout = op->layout; + + uiItemR(layout, op->ptr, "seed", UI_ITEM_NONE, std::nullopt, ICON_NONE); + uiItemR(layout, op->ptr, "probability", UI_ITEM_R_SLIDER, std::nullopt, ICON_NONE); +} + +static void POINTCLOUD_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 = editable_pointcloud_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 being included in the selection", + 0.0f, + 1.0f); +} + namespace pointcloud_delete { static int delete_exec(bContext *C, wmOperator * /*op*/) @@ -173,6 +241,7 @@ void operatortypes_pointcloud() WM_operatortype_append(POINTCLOUD_OT_delete); WM_operatortype_append(POINTCLOUD_OT_duplicate); WM_operatortype_append(POINTCLOUD_OT_select_all); + WM_operatortype_append(POINTCLOUD_OT_select_random); WM_operatortype_append(POINTCLOUD_OT_separate); } diff --git a/source/blender/editors/pointcloud/intern/selection.cc b/source/blender/editors/pointcloud/intern/selection.cc index 96d7c39cbed..0a0742d620e 100644 --- a/source/blender/editors/pointcloud/intern/selection.cc +++ b/source/blender/editors/pointcloud/intern/selection.cc @@ -133,6 +133,11 @@ void fill_selection_false(GMutableSpan selection, const IndexMask &mask) } } +void fill_selection_true(GMutableSpan selection) +{ + fill_selection_true(selection, IndexMask(selection.size())); +} + void fill_selection_true(GMutableSpan selection, const IndexMask &mask) { if (selection.type().is()) {