Point Cloud: Select Random

Move the mask_random function to BLI_index_mask.hh, so it can be shared between curves, grease pencil and point cloud.

Copied/inspired by the curves select code.

Pull Request: https://projects.blender.org/blender/blender/pulls/134624
This commit is contained in:
Dalai Felinto
2025-02-19 17:23:37 +01:00
parent 1584cd9aa5
commit 9d06f32761
10 changed files with 132 additions and 58 deletions

View File

@@ -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)

View File

@@ -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 {

View File

@@ -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<int64_t>,
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<bool> random(universe_size, false);
mask.foreach_index_optimized<int64_t>(
[&](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

View File

@@ -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<bool> random(domain_size, false);
mask.foreach_index_optimized<int64_t>(
[&](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

View File

@@ -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);

View File

@@ -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<int>(seed, info.layer_index),
ratio,
memory);
return random_mask(selectable_elements,
info.drawing.strokes().points_num(),
blender::get_default_hash<int>(seed, info.layer_index),
ratio,
memory);
});
/* Use #ID_RECALC_GEOMETRY instead of #ID_RECALC_SELECT because it is handled as a generic

View File

@@ -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);
/** \} */
/* -------------------------------------------------------------------- */

View File

@@ -56,6 +56,7 @@ VectorSet<PointCloud *> 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);

View File

@@ -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);
}

View File

@@ -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<bool>()) {