Point Cloud: Implement separate operator

Similar to existing operators, moves the selected
geometry to a new object.
This commit is contained in:
Hans Goudey
2025-02-17 21:21:05 -05:00
parent f1514b45e3
commit 4cd3540579
8 changed files with 139 additions and 14 deletions

View File

@@ -5814,6 +5814,7 @@ def km_edit_point_cloud(params):
*_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),
("point_cloud.separate", {"type": 'P', "value": 'PRESS'}, None),
("transform.transform", {"type": 'S', "value": 'PRESS', "alt": True},
{"properties": [("mode", 'CURVE_SHRINKFATTEN')]}),
])

View File

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

View File

@@ -123,6 +123,7 @@ IndexMask retrieve_selected_points(const PointCloud &pointcloud, IndexMaskMemory
* \returns true if any point was removed.
*/
bool remove_selection(PointCloud &point_cloud);
PointCloud *copy_selection(const PointCloud &src, const IndexMask &mask);
/** \} */
@@ -140,6 +141,7 @@ bool editable_point_cloud_in_edit_mode_poll(bContext *C);
void POINT_CLOUD_OT_attribute_set(wmOperatorType *ot);
void POINT_CLOUD_OT_duplicate(wmOperatorType *ot);
void POINT_CLOUD_OT_separate(wmOperatorType *ot);
/** \} */

View File

@@ -20,6 +20,7 @@ set(SRC
intern/point_cloud_ops.cc
intern/point_cloud_selection.cc
intern/point_cloud_undo.cc
intern/separate.cc
)
set(LIB

View File

@@ -67,8 +67,6 @@ void POINT_CLOUD_OT_duplicate(wmOperatorType *ot)
ot->poll = editable_point_cloud_in_edit_mode_poll;
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
WM_operator_properties_select_all(ot);
}
} // namespace blender::ed::point_cloud

View File

@@ -9,31 +9,40 @@
#include "BKE_attribute.hh"
#include "BKE_pointcloud.hh"
#include "DNA_node_types.h"
#include "ED_point_cloud.hh"
namespace blender::ed::point_cloud {
PointCloud *copy_selection(const PointCloud &src, const IndexMask &mask)
{
if (mask.size() == src.totpoint) {
return BKE_pointcloud_copy_for_eval(&src);
}
PointCloud *dst = BKE_pointcloud_new_nomain(mask.size());
bke::gather_attributes(src.attributes(),
bke::AttrDomain::Point,
bke::AttrDomain::Point,
{},
mask,
dst->attributes_for_write());
pointcloud_copy_parameters(src, *dst);
return dst;
}
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);
if (mask.size() == point_cloud.totpoint) {
return false;
}
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);
PointCloud *point_cloud_new = copy_selection(point_cloud, mask);
BKE_pointcloud_nomain_to_pointcloud(point_cloud_new, &point_cloud);
return point_cloud.totpoint != domain_size_orig;
return true;
}
} // namespace blender::ed::point_cloud

View File

@@ -174,6 +174,7 @@ void operatortypes_point_cloud()
WM_operatortype_append(POINT_CLOUD_OT_delete);
WM_operatortype_append(POINT_CLOUD_OT_duplicate);
WM_operatortype_append(POINT_CLOUD_OT_select_all);
WM_operatortype_append(POINT_CLOUD_OT_separate);
}
void operatormacros_point_cloud()

View File

@@ -0,0 +1,112 @@
/* SPDX-FileCopyrightText: 2025 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BLI_index_mask.hh"
#include "BLI_task.hh"
#include "BLI_vector_set.hh"
#include "BKE_context.hh"
#include "BKE_layer.hh"
#include "BKE_lib_id.hh"
#include "BKE_pointcloud.hh"
#include "DEG_depsgraph_build.hh"
#include "ED_object.hh"
#include "ED_point_cloud.hh"
#include "DNA_layer_types.h"
#include "DNA_pointcloud_types.h"
#include "DEG_depsgraph.hh"
#include "WM_api.hh"
namespace blender::ed::point_cloud {
static int separate_exec(bContext *C, wmOperator * /*op*/)
{
Main *bmain = CTX_data_main(C);
Scene *scene = CTX_data_scene(C);
ViewLayer *view_layer = CTX_data_view_layer(C);
Vector<Base *> bases = BKE_view_layer_array_from_bases_in_edit_mode(
scene, view_layer, CTX_wm_view3d(C));
VectorSet<PointCloud *> src_pointclouds;
for (Base *base_src : bases) {
src_pointclouds.add(static_cast<PointCloud *>(base_src->object->data));
}
/* Modify new point clouds and generate new point clouds in parallel. */
Array<PointCloud *> dst_pointclouds(src_pointclouds.size());
threading::parallel_for(dst_pointclouds.index_range(), 1, [&](const IndexRange range) {
for (const int i : range) {
IndexMaskMemory memory;
const IndexMask selection = retrieve_selected_points(*src_pointclouds[i], memory);
if (selection.is_empty()) {
dst_pointclouds[i] = nullptr;
continue;
}
dst_pointclouds[i] = copy_selection(*src_pointclouds[i], selection);
const IndexMask inverse = selection.complement(IndexRange(src_pointclouds[i]->totpoint),
memory);
BKE_pointcloud_nomain_to_pointcloud(copy_selection(*src_pointclouds[i], inverse),
src_pointclouds[i]);
}
});
/* Move new point clouds into main data-base. */
for (const int i : dst_pointclouds.index_range()) {
if (PointCloud *dst = dst_pointclouds[i]) {
dst_pointclouds[i] = BKE_pointcloud_add(bmain, BKE_id_name(src_pointclouds[i]->id));
pointcloud_copy_parameters(*src_pointclouds[i], *dst_pointclouds[i]);
BKE_pointcloud_nomain_to_pointcloud(dst, dst_pointclouds[i]);
}
}
/* Skip processing objects with no selected elements. */
bases.remove_if([&](Base *base) {
PointCloud *pointcloud = static_cast<PointCloud *>(base->object->data);
return dst_pointclouds[src_pointclouds.index_of(pointcloud)] == nullptr;
});
if (bases.is_empty()) {
return OPERATOR_CANCELLED;
}
/* Add new objects for the new point clouds. */
for (Base *base_src : bases) {
PointCloud *src = static_cast<PointCloud *>(base_src->object->data);
PointCloud *dst = dst_pointclouds[src_pointclouds.index_of(src)];
Base *base_dst = object::add_duplicate(
bmain, scene, view_layer, base_src, eDupli_ID_Flags(U.dupflag) & USER_DUP_ACT);
Object *object_dst = base_dst->object;
object_dst->mode = OB_MODE_OBJECT;
object_dst->data = dst;
DEG_id_tag_update(&src->id, ID_RECALC_GEOMETRY);
DEG_id_tag_update(&dst->id, ID_RECALC_GEOMETRY);
WM_event_add_notifier(C, NC_OBJECT | ND_DRAW, base_src->object);
WM_event_add_notifier(C, NC_OBJECT | ND_DRAW, object_dst);
}
DEG_relations_tag_update(bmain);
return OPERATOR_FINISHED;
}
void POINT_CLOUD_OT_separate(wmOperatorType *ot)
{
ot->name = "Separate";
ot->idname = "POINT_CLOUD_OT_separate";
ot->description = "Separate selected geometry into a new point cloud";
ot->exec = separate_exec;
ot->poll = editable_point_cloud_in_edit_mode_poll;
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
}
} // namespace blender::ed::point_cloud