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:
@@ -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')]}),
|
||||
])
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
* \{ */
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user