Point Cloud: Delete operator

Note: The point cloud bounding box is not being updated once the points are deleted.

This is a known issue with BKE_pointcloud_nomain_to_pointcloud and Hans is looking into it.

Code inspired/built based on the Delete Geometry node.

Pull Request: https://projects.blender.org/blender/blender/pulls/134622
This commit is contained in:
Dalai Felinto
2025-02-18 00:28:14 +01:00
parent 9057e528c7
commit ec6383de96
11 changed files with 127 additions and 0 deletions

View File

@@ -5812,6 +5812,8 @@ def km_edit_point_cloud(params):
("point_cloud.duplicate_move", {"type": 'D', "value": 'PRESS', "shift": True}, None),
*_template_items_select_actions(params, "point_cloud.select_all"),
("point_cloud.delete", {"type": 'X', "value": 'PRESS'}, None),
("point_cloud.delete", {"type": 'DEL', "value": 'PRESS'}, None),
("transform.transform", {"type": 'S', "value": 'PRESS', "alt": True},
{"properties": [("mode", 'CURVE_SHRINKFATTEN')]}),
])

View File

@@ -3434,6 +3434,8 @@ def km_point_cloud(params):
"ctrl": True}, {"properties": [("action", 'DESELECT')]}),
("point_cloud.select_all", {"type": 'I', "value": 'PRESS',
"ctrl": True}, {"properties": [("action", 'INVERT')]}),
# Delete
("point_cloud.delete", {"type": 'DEL', "value": 'PRESS'}, None),
])
return keymap

View File

@@ -5904,6 +5904,7 @@ class VIEW3D_MT_edit_pointcloud(Menu):
layout.operator("point_cloud.duplicate_move")
layout.separator()
layout.operator("point_cloud.attribute_set")
layout.operator("point_cloud.delete")
layout.template_node_operator_asset_menu_items(catalog_path=self.bl_label)

View File

@@ -59,6 +59,13 @@ void BKE_pointcloud_nomain_to_pointcloud(PointCloud *pointcloud_src, PointCloud
bool BKE_pointcloud_attribute_required(const PointCloud *pointcloud, blender::StringRef name);
/**
* Copy data from #src to #dst, except the geometry and attributes. Typically used to
* copy high-level parameters when a geometry-altering operation creates a new point cloud
* data-block.
*/
void pointcloud_copy_parameters(const PointCloud &src, PointCloud &dst);
/* Dependency Graph */
PointCloud *BKE_pointcloud_copy_for_eval(const PointCloud *pointcloud_src);

View File

@@ -369,6 +369,15 @@ bool BKE_pointcloud_attribute_required(const PointCloud * /*pointcloud*/,
return name == POINTCLOUD_ATTR_POSITION;
}
void pointcloud_copy_parameters(const PointCloud &src, PointCloud &dst)
{
dst.flag = src.flag;
MEM_SAFE_FREE(dst.mat);
dst.mat = static_cast<Material **>(MEM_malloc_arrayN(src.totcol, sizeof(Material *), __func__));
dst.totcol = src.totcol;
MutableSpan(dst.mat, dst.totcol).copy_from(Span(src.mat, src.totcol));
}
/* Dependency Graph */
PointCloud *BKE_pointcloud_copy_for_eval(const PointCloud *pointcloud_src)

View File

@@ -199,6 +199,7 @@ class IndexMask : private IndexMaskData {
/** Construct a mask from the true indices. */
static IndexMask from_bools(Span<bool> bools, IndexMaskMemory &memory);
static IndexMask from_bools(const VArray<bool> &bools, IndexMaskMemory &memory);
static IndexMask from_bools_inverse(const VArray<bool> &bools, IndexMaskMemory &memory);
/** Construct a mask from the true indices, but limited by the indices in #universe. */
static IndexMask from_bools(const IndexMask &universe,
Span<bool> bools,
@@ -209,6 +210,9 @@ class IndexMask : private IndexMaskData {
static IndexMask from_bools(const IndexMask &universe,
const VArray<bool> &bools,
IndexMaskMemory &memory);
static IndexMask from_bools_inverse(const IndexMask &universe,
const VArray<bool> &bools,
IndexMaskMemory &memory);
/** Construct a mask from the ranges referenced by the offset indices. */
template<typename T>
static IndexMask from_ranges(OffsetIndices<T> offsets,

View File

@@ -586,6 +586,11 @@ IndexMask IndexMask::from_bools(const VArray<bool> &bools, IndexMaskMemory &memo
return IndexMask::from_bools(bools.index_range(), bools, memory);
}
IndexMask IndexMask::from_bools_inverse(const VArray<bool> &bools, IndexMaskMemory &memory)
{
return IndexMask::from_bools_inverse(bools.index_range(), bools, memory);
}
IndexMask IndexMask::from_bools(const IndexMask &universe,
Span<bool> bools,
IndexMaskMemory &memory)
@@ -640,6 +645,22 @@ IndexMask IndexMask::from_bools(const IndexMask &universe,
universe, GrainSize(512), memory, [&](const int64_t index) { return bools[index]; });
}
IndexMask IndexMask::from_bools_inverse(const IndexMask &universe,
const VArray<bool> &bools,
IndexMaskMemory &memory)
{
const CommonVArrayInfo info = bools.common_info();
if (info.type == CommonVArrayInfo::Type::Single) {
return *static_cast<const bool *>(info.data) ? IndexMask() : universe;
}
if (info.type == CommonVArrayInfo::Type::Span) {
const Span<bool> span(static_cast<const bool *>(info.data), bools.size());
return IndexMask::from_bools_inverse(universe, span, memory);
}
return IndexMask::from_predicate(
universe, GrainSize(512), memory, [&](const int64_t index) { return !bools[index]; });
}
template<typename T>
IndexMask IndexMask::from_ranges(OffsetIndices<T> offsets,
const IndexMask &mask,

View File

@@ -114,6 +114,18 @@ IndexMask retrieve_selected_points(const PointCloud &pointcloud, IndexMaskMemory
/** \} */
/* -------------------------------------------------------------------- */
/** \name Editing
* \{ */
/**
* Remove selected points based on the ".selection" attribute.
* \returns true if any point was removed.
*/
bool remove_selection(PointCloud &point_cloud);
/** \} */
/* -------------------------------------------------------------------- */
/** \name Poll Functions
* \{ */

View File

@@ -16,6 +16,7 @@ set(INC_SYS
set(SRC
intern/attribute_set.cc
intern/duplicate.cc
intern/point_cloud_edit.cc
intern/point_cloud_ops.cc
intern/point_cloud_selection.cc
intern/point_cloud_undo.cc

View File

@@ -0,0 +1,39 @@
/* SPDX-FileCopyrightText: 2025 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup edpointcloud
*/
#include "BKE_attribute.hh"
#include "BKE_pointcloud.hh"
#include "DNA_node_types.h"
#include "ED_point_cloud.hh"
namespace blender::ed::point_cloud {
bool remove_selection(PointCloud &point_cloud)
{
const bke::AttributeAccessor attributes = point_cloud.attributes();
const VArray<bool> selection = *attributes.lookup_or_default<bool>(
".selection", bke::AttrDomain::Point, true);
const int domain_size_orig = point_cloud.totpoint;
IndexMaskMemory memory;
const IndexMask mask = IndexMask::from_bools_inverse(selection, memory);
PointCloud *point_cloud_new = BKE_pointcloud_new_nomain(mask.size());
bke::gather_attributes(attributes,
bke::AttrDomain::Point,
bke::AttrDomain::Point,
{},
mask,
point_cloud_new->attributes_for_write());
pointcloud_copy_parameters(point_cloud, *point_cloud_new);
BKE_pointcloud_nomain_to_pointcloud(point_cloud_new, &point_cloud);
return point_cloud.totpoint != domain_size_orig;
}
} // namespace blender::ed::point_cloud

View File

@@ -140,9 +140,38 @@ static void POINT_CLOUD_OT_select_all(wmOperatorType *ot)
WM_operator_properties_select_all(ot);
}
namespace point_cloud_delete {
static int delete_exec(bContext *C, wmOperator * /*op*/)
{
for (PointCloud *point_cloud : get_unique_editable_point_clouds(*C)) {
if (remove_selection(*point_cloud)) {
DEG_id_tag_update(&point_cloud->id, ID_RECALC_GEOMETRY);
WM_event_add_notifier(C, NC_GEOM | ND_DATA, &point_cloud);
}
}
return OPERATOR_FINISHED;
}
} // namespace point_cloud_delete
static void POINT_CLOUD_OT_delete(wmOperatorType *ot)
{
ot->name = "Delete";
ot->idname = __func__;
ot->description = "Remove selected points";
ot->exec = point_cloud_delete::delete_exec;
ot->poll = editable_point_cloud_in_edit_mode_poll;
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
}
void operatortypes_point_cloud()
{
WM_operatortype_append(POINT_CLOUD_OT_attribute_set);
WM_operatortype_append(POINT_CLOUD_OT_delete);
WM_operatortype_append(POINT_CLOUD_OT_duplicate);
WM_operatortype_append(POINT_CLOUD_OT_select_all);
}