Add Chromium-only Blender WebEngine parity work

This commit is contained in:
mes123456
2026-08-12 04:47:48 -04:00
commit 9fd26010f6
18225 changed files with 11622124 additions and 0 deletions

View File

@@ -0,0 +1,40 @@
# SPDX-FileCopyrightText: 2025 Blender Authors
#
# SPDX-License-Identifier: GPL-2.0-or-later
set(INC
../include
../../makesrna
# RNA_prototypes.hh
${CMAKE_BINARY_DIR}/source/blender/makesrna
)
set(INC_SYS
)
set(SRC
intern/attribute_set.cc
intern/duplicate.cc
intern/edit.cc
intern/join.cc
intern/operators.cc
intern/selection.cc
intern/separate.cc
intern/undo.cc
)
set(LIB
PRIVATE bf::blenkernel
PRIVATE bf::blenlib
PRIVATE bf::depsgraph
PRIVATE bf::dna
PRIVATE bf::geometry
PRIVATE bf::functions
PRIVATE bf::intern::clog
PRIVATE bf::intern::guardedalloc
PRIVATE bf::windowmanager
)
blender_add_lib(bf_editor_pointcloud "${SRC}" "${INC}" "${INC_SYS}" "${LIB}")
add_dependencies(bf_editor_pointcloud bf_rna)

View File

@@ -0,0 +1,214 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup edmesh
*/
#include "BLI_generic_pointer.hh"
#include "BKE_attribute.h"
#include "BKE_attribute.hh"
#include "BKE_attribute_math.hh"
#include "BKE_context.hh"
#include "BKE_pointcloud.hh"
#include "BKE_type_conversions.hh"
#include "WM_api.hh"
#include "WM_types.hh"
#include "ED_geometry.hh"
#include "ED_object.hh"
#include "ED_pointcloud.hh"
#include "ED_screen.hh"
#include "ED_transform.hh"
#include "ED_view3d.hh"
#include "RNA_access.hh"
#include "UI_interface_layout.hh"
#include "UI_resources.hh"
#include "DNA_object_types.h"
#include "FN_multi_function.hh"
#include "DEG_depsgraph.hh"
/* -------------------------------------------------------------------- */
/** \name Delete Operator
* \{ */
namespace blender::ed::pointcloud {
static bool active_attribute_poll(bContext *C)
{
if (!editable_pointcloud_in_edit_mode_poll(C)) {
return false;
}
const Object *object = CTX_data_active_object(C);
const ID &object_data = *static_cast<const ID *>(object->data);
if (!geometry::attribute_set_poll(*C, object_data)) {
return false;
}
return true;
}
static void validate_value(const bke::AttributeAccessor attributes,
const StringRef name,
const CPPType &type,
void *buffer)
{
const bke::AttributeValidator validator = attributes.lookup_validator(name);
if (!validator) {
return;
}
BUFFER_FOR_CPP_TYPE_VALUE(type, validated_buffer);
BLI_SCOPED_DEFER([&]() { type.destruct(validated_buffer); });
const IndexMask single_mask(1);
mf::ParamsBuilder params(*validator.function, &single_mask);
params.add_readonly_single_input(GPointer(type, buffer));
params.add_uninitialized_single_output({type, validated_buffer, 1});
mf::ContextBuilder context;
validator.function->call(single_mask, params, context);
type.copy_assign(validated_buffer, buffer);
}
static wmOperatorStatus set_attribute_exec(bContext *C, wmOperator *op)
{
Object *active_object = CTX_data_active_object(C);
PointCloud &active_pointcloud = *id_cast<PointCloud *>(active_object->data);
AttributeOwner active_owner = AttributeOwner::from_id(&active_pointcloud.id);
const StringRef name = *BKE_attributes_active_name_get(active_owner);
const bke::AttributeMetaData meta_data = *active_pointcloud.attributes().lookup_meta_data(name);
const bke::AttrType active_type = meta_data.data_type;
const CPPType &type = bke::attribute_type_to_cpp_type(active_type);
BUFFER_FOR_CPP_TYPE_VALUE(type, buffer);
BLI_SCOPED_DEFER([&]() { type.destruct(buffer); });
const GPointer value = geometry::rna_property_for_attribute_type_retrieve_value(
*op->ptr, active_type, buffer);
const bke::DataTypeConversions &conversions = bke::get_implicit_type_conversions();
for (PointCloud *pointcloud : get_unique_editable_pointclouds(*C)) {
bke::MutableAttributeAccessor attributes = pointcloud->attributes_for_write();
const std::optional<bke::AttributeMetaData> meta_data = attributes.lookup_meta_data(name);
if (!meta_data) {
continue;
}
IndexMaskMemory memory;
const IndexMask selection = retrieve_selected_points(*pointcloud, memory);
if (selection.is_empty()) {
continue;
}
/* Use implicit conversions to try to handle the case where the active attribute has a
* different type on multiple objects. */
const CPPType &dst_type = bke::attribute_type_to_cpp_type(meta_data->data_type);
if (&type != &dst_type && !conversions.is_convertible(type, dst_type)) {
continue;
}
BUFFER_FOR_CPP_TYPE_VALUE(dst_type, dst_buffer);
BLI_SCOPED_DEFER([&]() { dst_type.destruct(dst_buffer); });
conversions.convert_to_uninitialized(type, dst_type, value.get(), dst_buffer);
validate_value(attributes, name, dst_type, dst_buffer);
const GPointer dst_value(dst_type, dst_buffer);
if (selection.size() == attributes.domain_size(meta_data->domain)) {
if (attributes.assign_data(name, bke::AttributeInitValue(dst_value))) {
DEG_id_tag_update(&pointcloud->id, ID_RECALC_GEOMETRY);
WM_event_add_notifier(C, NC_GEOM | ND_DATA, pointcloud);
continue;
}
}
bke::GSpanAttributeWriter attribute = attributes.lookup_for_write_span(name);
dst_type.fill_assign_indices(dst_value.get(), attribute.span.data(), selection);
attribute.finish();
DEG_id_tag_update(&pointcloud->id, ID_RECALC_GEOMETRY);
WM_event_add_notifier(C, NC_GEOM | ND_DATA, pointcloud);
}
return OPERATOR_FINISHED;
}
static wmOperatorStatus set_attribute_invoke(bContext *C, wmOperator *op, const wmEvent *event)
{
Object *active_object = CTX_data_active_object(C);
PointCloud &active_pointcloud = *id_cast<PointCloud *>(active_object->data);
AttributeOwner owner = AttributeOwner::from_id(&active_pointcloud.id);
const bke::AttributeAccessor attributes = active_pointcloud.attributes();
const StringRef name = *BKE_attributes_active_name_get(owner);
const bke::GAttributeReader attribute = attributes.lookup(name);
IndexMaskMemory memory;
const IndexMask selection = retrieve_selected_points(active_pointcloud, memory);
const CPPType &type = attribute.varray.type();
PropertyRNA *prop = geometry::rna_property_for_type(*op->ptr,
bke::cpp_type_to_attribute_type(type));
if (RNA_property_is_set(op->ptr, prop)) {
return WM_operator_props_popup(C, op, event);
}
BUFFER_FOR_CPP_TYPE_VALUE(type, buffer);
BLI_SCOPED_DEFER([&]() { type.destruct(buffer); });
bke::attribute_math::to_static_type(type, [&]<typename T>() {
if constexpr (!std::is_void_v<bke::attribute_math::DefaultMixer<T>>) {
const VArray<T> values_typed = attribute.varray.typed<T>();
bke::attribute_math::DefaultMixer<T> mixer{MutableSpan(static_cast<T *>(buffer), 1)};
selection.foreach_index([&](const int i) { mixer.mix_in(0, values_typed[i]); });
mixer.finalize();
}
});
geometry::rna_property_for_attribute_type_set_value(*op->ptr, *prop, GPointer(type, buffer));
return WM_operator_props_popup(C, op, event);
}
static void set_attribute_ui(bContext *C, wmOperator *op)
{
ui::Layout &layout = op->layout->column(true);
layout.use_property_split_set(true);
layout.use_property_decorate_set(false);
Object *object = CTX_data_active_object(C);
PointCloud &pointcloud = *id_cast<PointCloud *>(object->data);
AttributeOwner owner = AttributeOwner::from_id(&pointcloud.id);
const StringRef name = *BKE_attributes_active_name_get(owner);
const bke::AttributeMetaData meta_data = *pointcloud.attributes().lookup_meta_data(name);
const StringRefNull prop_name = geometry::rna_property_name_for_type(meta_data.data_type);
layout.prop(op->ptr, prop_name, UI_ITEM_NONE, name, ICON_NONE);
}
void POINTCLOUD_OT_attribute_set(wmOperatorType *ot)
{
ot->name = "Set Attribute";
ot->description = "Set values of the active attribute for selected elements";
ot->idname = "POINTCLOUD_OT_attribute_set";
ot->exec = set_attribute_exec;
ot->invoke = set_attribute_invoke;
ot->poll = active_attribute_poll;
ot->ui = set_attribute_ui;
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
geometry::register_rna_properties_for_attribute_types(*ot->srna);
}
} // namespace blender::ed::pointcloud
/** \} */

View File

@@ -0,0 +1,79 @@
/* SPDX-FileCopyrightText: 2025 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BLI_array_utils.hh"
#include "BKE_attribute.hh"
#include "BKE_pointcloud.hh"
#include "ED_pointcloud.hh"
#include "DNA_pointcloud_types.h"
#include "DEG_depsgraph.hh"
#include "WM_api.hh"
namespace blender::ed::pointcloud {
static void duplicate_points(PointCloud &pointcloud, const IndexMask &mask)
{
PointCloud *new_pointcloud = BKE_pointcloud_new_nomain(pointcloud.totpoint + mask.size());
bke::MutableAttributeAccessor dst_attributes = new_pointcloud->attributes_for_write();
pointcloud.attributes().foreach_attribute([&](const bke::AttributeIter &iter) {
const GVArray src = *iter.get();
const CommonVArrayInfo info = src.common_info();
if (info.type == CommonVArrayInfo::Type::Single) {
const bke::AttributeInitValue init(GPointer(src.type(), info.data));
if (dst_attributes.add(iter.name, iter.domain, iter.data_type, init)) {
return;
}
}
bke::GSpanAttributeWriter dst = dst_attributes.lookup_or_add_for_write_only_span(
iter.name, iter.domain, iter.data_type);
array_utils::copy(src, dst.span.take_front(pointcloud.totpoint));
array_utils::gather(src, mask, dst.span.take_back(mask.size()));
dst.finish();
});
BKE_pointcloud_nomain_to_pointcloud(new_pointcloud, &pointcloud);
}
static wmOperatorStatus duplicate_exec(bContext *C, wmOperator * /*op*/)
{
for (PointCloud *pointcloud : get_unique_editable_pointclouds(*C)) {
IndexMaskMemory memory;
const IndexMask selection = retrieve_selected_points(*pointcloud, memory);
if (selection.is_empty()) {
continue;
}
pointcloud->attributes_for_write().remove(".selection");
duplicate_points(*pointcloud, selection);
bke::SpanAttributeWriter selection_attr =
pointcloud->attributes_for_write().lookup_or_add_for_write_span<bool>(
".selection", bke::AttrDomain::Point);
selection_attr.span.take_back(selection.size()).fill(true);
selection_attr.finish();
DEG_id_tag_update(&pointcloud->id, ID_RECALC_GEOMETRY);
WM_event_add_notifier(C, NC_GEOM | ND_DATA, pointcloud);
}
return OPERATOR_FINISHED;
}
void POINTCLOUD_OT_duplicate(wmOperatorType *ot)
{
ot->name = "Duplicate";
ot->idname = "POINTCLOUD_OT_duplicate";
ot->description = "Copy selected points";
ot->exec = duplicate_exec;
ot->poll = editable_pointcloud_in_edit_mode_poll;
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
}
} // namespace blender::ed::pointcloud

View File

@@ -0,0 +1,48 @@
/* 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 "ED_pointcloud.hh"
namespace blender::ed::pointcloud {
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 &pointcloud)
{
const bke::AttributeAccessor attributes = pointcloud.attributes();
const VArray<bool> selection = *attributes.lookup_or_default<bool>(
".selection", bke::AttrDomain::Point, true);
IndexMaskMemory memory;
const IndexMask mask = IndexMask::from_bools_inverse(selection, memory);
if (mask.size() == pointcloud.totpoint) {
return false;
}
PointCloud *pointcloud_new = copy_selection(pointcloud, mask);
BKE_pointcloud_nomain_to_pointcloud(pointcloud_new, &pointcloud);
return true;
}
} // namespace blender::ed::pointcloud

View File

@@ -0,0 +1,98 @@
/* SPDX-FileCopyrightText: 2025 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BLI_map.hh"
#include "DNA_scene_types.h"
#include "BKE_context.hh"
#include "BKE_instances.hh"
#include "BKE_pointcloud.hh"
#include "BKE_report.hh"
#include "DEG_depsgraph.hh"
#include "DEG_depsgraph_build.hh"
#include "WM_api.hh"
#include "WM_types.hh"
#include "ED_object.hh"
#include "ED_pointcloud.hh"
#include "GEO_realize_instances.hh"
namespace blender::ed::pointcloud {
wmOperatorStatus join_objects_exec(bContext *C, wmOperator *op)
{
Main *bmain = CTX_data_main(C);
Scene *scene = CTX_data_scene(C);
Object *active_object = CTX_data_active_object(C);
BLI_assert(active_object);
BLI_assert(active_object->type == OB_POINTCLOUD);
PointCloud &active_pointcloud = *id_cast<PointCloud *>(active_object->data);
const float4x4 &world_to_active = active_object->world_to_object();
Vector<Object *> objects{active_object};
bool active_object_selected = false;
CTX_DATA_BEGIN (C, Object *, object, selected_editable_objects) {
if (object == active_object) {
active_object_selected = true;
continue;
}
if (object->type != OB_POINTCLOUD) {
continue;
}
objects.append(object);
}
CTX_DATA_END;
if (!active_object_selected) {
BKE_report(op->reports, RPT_WARNING, "Active object is not a selected point cloud object");
return OPERATOR_CANCELLED;
}
bke::Instances instances;
instances.resize(objects.size());
MutableSpan<float4x4> transforms = instances.transforms_for_write();
MutableSpan<int> references = instances.reference_handles_for_write();
Map<const PointCloud *, int> reference_by_orig_points;
for (const int i : objects.index_range()) {
transforms[i] = world_to_active * objects[i]->object_to_world();
const PointCloud *orig_points = id_cast<const PointCloud *>(objects[i]->data);
references[i] = reference_by_orig_points.lookup_or_add_cb(orig_points, [&]() {
auto geometry = bke::GeometrySet::from_pointcloud(BKE_pointcloud_copy_for_eval(orig_points));
return instances.add_new_reference(std::move(geometry));
});
}
bke::GeometrySet realized_geometry = geometry::realize_instances(
bke::GeometrySet::from_instances(
&instances, bke::GeometryOwnershipType::ReadOnly),
geometry::RealizeInstancesOptions())
.geometry;
if (!realized_geometry.has_pointcloud()) {
BKE_report(op->reports, RPT_WARNING, "No point cloud data to join");
return OPERATOR_CANCELLED;
}
PointCloud *realized_points =
realized_geometry.get_component_for_write<bke::PointCloudComponent>().release();
BKE_pointcloud_nomain_to_pointcloud(realized_points, &active_pointcloud);
for (Object *object : objects.as_span().drop_front(1)) {
object::base_free_and_unlink(bmain, scene, object);
}
DEG_relations_tag_update(bmain);
DEG_id_tag_update(&active_object->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY);
DEG_id_tag_update(&scene->id, ID_RECALC_SELECT);
WM_event_add_notifier(C, NC_SCENE | ND_OB_ACTIVE, scene);
WM_event_add_notifier(C, NC_SCENE | ND_LAYER_CONTENT, scene);
return OPERATOR_FINISHED;
}
} // namespace blender::ed::pointcloud

View File

@@ -0,0 +1,272 @@
/* SPDX-FileCopyrightText: 2025 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup edpointcloud
* Implements the Point Cloud operators.
*/
#include "BKE_attribute.hh"
#include "BKE_context.hh"
#include "BKE_lib_id.hh"
#include "ED_pointcloud.hh"
#include "ED_screen.hh"
#include "ED_select_utils.hh"
#include "DNA_pointcloud_types.h"
#include "DNA_windowmanager_types.h"
#include "DEG_depsgraph.hh"
#include "RNA_access.hh"
#include "RNA_define.hh"
#include "UI_interface.hh"
#include "UI_interface_layout.hh"
#include "WM_api.hh"
namespace blender::ed::pointcloud {
static bool object_has_editable_pointcloud(const Main &bmain, const Object &object)
{
if (object.type != OB_POINTCLOUD) {
return false;
}
if (object.mode != OB_MODE_EDIT) {
return false;
}
if (!BKE_id_is_editable(&bmain, static_cast<const ID *>(object.data))) {
return false;
}
return true;
}
static bool pointcloud_poll_impl(bContext *C,
const bool check_editable,
const bool check_edit_mode)
{
Object *object = CTX_data_active_object(C);
if (object == nullptr || object->type != OB_POINTCLOUD) {
return false;
}
if (check_editable) {
if (!ED_operator_object_active_editable_ex(C, object)) {
return false;
}
}
if (check_edit_mode) {
if ((object->mode & OB_MODE_EDIT) == 0) {
return false;
}
}
return true;
}
static bool editable_pointcloud_poll(bContext *C)
{
return pointcloud_poll_impl(C, false, false);
}
bool editable_pointcloud_in_edit_mode_poll(bContext *C)
{
return pointcloud_poll_impl(C, true, true);
}
VectorSet<PointCloud *> get_unique_editable_pointclouds(const bContext &C)
{
VectorSet<PointCloud *> unique_points;
const Main &bmain = *CTX_data_main(&C);
Object *object = CTX_data_active_object(&C);
if (object && object_has_editable_pointcloud(bmain, *object)) {
unique_points.add_new(id_cast<PointCloud *>(object->data));
}
CTX_DATA_BEGIN (&C, Object *, object, selected_objects) {
if (object_has_editable_pointcloud(bmain, *object)) {
unique_points.add(id_cast<PointCloud *>(object->data));
}
}
CTX_DATA_END;
return unique_points;
}
static bool has_anything_selected(const Span<PointCloud *> pointclouds)
{
return std::any_of(pointclouds.begin(), pointclouds.end(), [](const PointCloud *pointcloud) {
return has_anything_selected(*pointcloud);
});
}
static wmOperatorStatus select_all_exec(bContext *C, wmOperator *op)
{
int action = RNA_enum_get(op->ptr, "action");
VectorSet<PointCloud *> unique_pointcloud = get_unique_editable_pointclouds(*C);
if (action == SEL_TOGGLE) {
action = has_anything_selected(unique_pointcloud) ? SEL_DESELECT : SEL_SELECT;
}
for (PointCloud *pointcloud : unique_pointcloud) {
/* (De)select all the curves. */
select_all(*pointcloud, action);
/* 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 POINTCLOUD_OT_select_all(wmOperatorType *ot)
{
ot->name = "(De)select All";
ot->idname = "POINTCLOUD_OT_select_all";
ot->description = "(De)select all points";
ot->exec = select_all_exec;
ot->poll = editable_pointcloud_poll;
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
WM_operator_properties_select_all(ot);
}
static wmOperatorStatus 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,
bke::AttrType::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)
{
ui::Layout &layout = *op->layout;
layout.prop(op->ptr, "seed", UI_ITEM_NONE, std::nullopt, ICON_NONE);
layout.prop(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 = "Randomize 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 wmOperatorStatus delete_exec(bContext *C, wmOperator * /*op*/)
{
for (PointCloud *pointcloud : get_unique_editable_pointclouds(*C)) {
if (remove_selection(*pointcloud)) {
DEG_id_tag_update(&pointcloud->id, ID_RECALC_GEOMETRY);
WM_event_add_notifier(C, NC_GEOM | ND_DATA, &pointcloud);
}
}
return OPERATOR_FINISHED;
}
} // namespace pointcloud_delete
static void POINTCLOUD_OT_delete(wmOperatorType *ot)
{
ot->name = "Delete";
ot->idname = __func__;
ot->description = "Remove selected points";
ot->exec = pointcloud_delete::delete_exec;
ot->poll = editable_pointcloud_in_edit_mode_poll;
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
}
void operatortypes_pointcloud()
{
WM_operatortype_append(POINTCLOUD_OT_attribute_set);
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);
}
void operatormacros_pointcloud()
{
wmOperatorType *ot;
wmOperatorTypeMacro *otmacro;
ot = WM_operatortype_append_macro("POINTCLOUD_OT_duplicate_move",
"Duplicate",
"Make copies of selected elements and move them",
OPTYPE_UNDO | OPTYPE_REGISTER);
WM_operatortype_macro_define(ot, "POINTCLOUD_OT_duplicate");
otmacro = WM_operatortype_macro_define(ot, "TRANSFORM_OT_translate");
RNA_boolean_set(otmacro->ptr, "use_proportional_edit", false);
RNA_boolean_set(otmacro->ptr, "mirror", false);
}
void keymap_pointcloud(wmKeyConfig *keyconf)
{
/* Only set in editmode point cloud, by space_view3d listener. */
wmKeyMap *keymap = WM_keymap_ensure(keyconf, "Point Cloud", SPACE_EMPTY, RGN_TYPE_WINDOW);
keymap->poll = editable_pointcloud_in_edit_mode_poll;
}
} // namespace blender::ed::pointcloud

View File

@@ -0,0 +1,281 @@
/* SPDX-FileCopyrightText: 2025 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup edpointcloud
*/
#include "BLI_array_utils.hh"
#include "BLI_index_mask.hh"
#include "BLI_lasso_2d.hh"
#include "BLI_math_vector.hh"
#include "BLI_rect.h"
#include "BKE_attribute.hh"
#include "ED_pointcloud.hh"
#include "ED_select_utils.hh"
#include "ED_view3d.hh"
#include "DNA_pointcloud_types.h"
namespace blender::ed::pointcloud {
bool has_anything_selected(const PointCloud &pointcloud)
{
const VArray<bool> selection = *pointcloud.attributes().lookup<bool>(".selection");
return !selection || array_utils::contains(selection, selection.index_range(), true);
}
bke::GSpanAttributeWriter ensure_selection_attribute(PointCloud &pointcloud,
bke::AttrType create_type)
{
const bke::AttrDomain selection_domain = bke::AttrDomain::Point;
const StringRef attribute_name = ".selection";
bke::MutableAttributeAccessor attributes = pointcloud.attributes_for_write();
if (attributes.contains(attribute_name)) {
return attributes.lookup_for_write_span(attribute_name);
}
switch (create_type) {
case bke::AttrType::Bool:
attributes.add(
attribute_name, selection_domain, bke::AttrType::Bool, bke::AttributeInitValue(true));
break;
case bke::AttrType::Float:
attributes.add(
attribute_name, selection_domain, bke::AttrType::Float, bke::AttributeInitValue(1.0f));
break;
default:
BLI_assert_unreachable();
}
return attributes.lookup_for_write_span(attribute_name);
}
void fill_selection_false(GMutableSpan selection, const IndexMask &mask)
{
if (selection.type().is<bool>()) {
index_mask::masked_fill(selection.typed<bool>(), false, mask);
}
else if (selection.type().is<float>()) {
index_mask::masked_fill(selection.typed<float>(), 0.0f, 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>()) {
index_mask::masked_fill(selection.typed<bool>(), true, mask);
}
else if (selection.type().is<float>()) {
index_mask::masked_fill(selection.typed<float>(), 1.0f, mask);
}
}
static void invert_selection(MutableSpan<float> selection, const IndexMask &mask)
{
mask.foreach_index_optimized<int64_t>(
[&](const int64_t i) { selection[i] = 1.0f - selection[i]; }, exec_mode::grain_size(4096));
}
static void invert_selection(GMutableSpan selection, const IndexMask &mask)
{
if (selection.type().is<bool>()) {
array_utils::invert_booleans(selection.typed<bool>(), mask);
}
else if (selection.type().is<float>()) {
invert_selection(selection.typed<float>(), mask);
}
}
static void select_all(PointCloud &pointcloud, const IndexMask &mask, int action)
{
if (action == SEL_SELECT) {
std::optional<IndexRange> range = mask.to_range();
if (range.has_value() && (*range == IndexRange(pointcloud.totpoint))) {
bke::MutableAttributeAccessor attributes = pointcloud.attributes_for_write();
/* As an optimization, just remove the selection attributes when everything is selected. */
attributes.remove(".selection");
return;
}
}
bke::GSpanAttributeWriter selection = ensure_selection_attribute(pointcloud,
bke::AttrType::Bool);
if (action == SEL_SELECT) {
fill_selection_true(selection.span, mask);
}
else if (action == SEL_DESELECT) {
fill_selection_false(selection.span, mask);
}
else if (action == SEL_INVERT) {
invert_selection(selection.span, mask);
}
selection.finish();
}
void select_all(PointCloud &pointcloud, int action)
{
select_all(pointcloud, IndexRange(pointcloud.totpoint), action);
}
static bool apply_selection_operation(PointCloud &pointcloud,
const IndexMask &mask,
eSelectOp sel_op)
{
bool changed = false;
bke::GSpanAttributeWriter selection = ensure_selection_attribute(pointcloud,
bke::AttrType::Bool);
if (sel_op == SEL_OP_SET) {
fill_selection_false(selection.span, IndexRange(selection.span.size()));
changed = true;
}
switch (sel_op) {
case SEL_OP_ADD:
case SEL_OP_SET:
fill_selection_true(selection.span, mask);
break;
case SEL_OP_SUB:
fill_selection_false(selection.span, mask);
break;
case SEL_OP_XOR:
invert_selection(selection.span, mask);
break;
default:
break;
}
changed |= !mask.is_empty();
selection.finish();
return changed;
}
bool select_box(PointCloud &pointcloud,
const ARegion &region,
const float4x4 &projection,
const rcti &rect,
const eSelectOp sel_op)
{
const Span<float3> positions = pointcloud.positions();
IndexMaskMemory memory;
const IndexMask mask = IndexMask::from_predicate(
positions.index_range(), memory, [&](const int point) {
const float2 pos_proj = ED_view3d_project_float_v2_m4(
&region, positions[point], projection);
return BLI_rcti_isect_pt_v(&rect, int2(pos_proj));
});
return apply_selection_operation(pointcloud, mask, sel_op);
}
bool select_lasso(PointCloud &pointcloud,
const ARegion &region,
const float4x4 &projection,
const Span<int2> lasso_coords,
const eSelectOp sel_op)
{
rcti bbox;
BLI_lasso_boundbox(&bbox, lasso_coords);
const Span<float3> positions = pointcloud.positions();
IndexMaskMemory memory;
const IndexMask mask = IndexMask::from_predicate(
positions.index_range(), memory, [&](const int point) {
const float2 pos_proj = ED_view3d_project_float_v2_m4(
&region, positions[point], projection);
if (!BLI_rcti_isect_pt_v(&bbox, int2(pos_proj))) {
return false;
}
if (!BLI_lasso_is_point_inside(lasso_coords, int(pos_proj.x), int(pos_proj.y), IS_CLIPPED))
{
return false;
}
return true;
});
return apply_selection_operation(pointcloud, mask, sel_op);
}
bool select_circle(PointCloud &pointcloud,
const ARegion &region,
const float4x4 &projection,
const int2 coord,
const float radius,
const eSelectOp sel_op)
{
const float radius_sq = radius * radius;
const Span<float3> positions = pointcloud.positions();
IndexMaskMemory memory;
const IndexMask mask = IndexMask::from_predicate(
positions.index_range(), memory, [&](const int point) {
const float2 pos_proj = ED_view3d_project_float_v2_m4(
&region, positions[point], projection);
return math::distance_squared(pos_proj, float2(coord)) <= radius_sq;
});
return apply_selection_operation(pointcloud, mask, sel_op);
}
static FindClosestData closer_elem(const FindClosestData &a, const FindClosestData &b)
{
if (a.distance_sq < b.distance_sq) {
return a;
}
return b;
}
std::optional<FindClosestData> find_closest_point_to_screen_co(
const ARegion &region,
const Span<float3> positions,
const float4x4 &projection,
const IndexMask &points_mask,
const float2 mouse_pos,
const float radius,
const FindClosestData &initial_closest)
{
const float radius_sq = radius * radius;
const FindClosestData new_closest_data = threading::parallel_reduce(
points_mask.index_range(),
1024,
initial_closest,
[&](const IndexRange range, const FindClosestData &init) {
FindClosestData best_match = init;
points_mask.slice(range).foreach_index([&](const int point) {
const float3 &pos = positions[point];
const float2 pos_proj = ED_view3d_project_float_v2_m4(&region, pos, projection);
const float distance_proj_sq = math::distance_squared(pos_proj, mouse_pos);
if (distance_proj_sq > radius_sq || distance_proj_sq > best_match.distance_sq) {
return;
}
best_match = {point, distance_proj_sq};
});
return best_match;
},
closer_elem);
if (new_closest_data.distance_sq < initial_closest.distance_sq) {
return new_closest_data;
}
return {};
}
IndexMask retrieve_selected_points(const PointCloud &pointcloud, IndexMaskMemory &memory)
{
const VArray selection = *pointcloud.attributes().lookup_or_default<bool>(
".selection", bke::AttrDomain::Point, true);
return IndexMask::from_bools(selection, memory);
}
} // namespace blender::ed::pointcloud

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_pointcloud.hh"
#include "DNA_layer_types.h"
#include "DNA_pointcloud_types.h"
#include "DEG_depsgraph.hh"
#include "WM_api.hh"
namespace blender::ed::pointcloud {
static wmOperatorStatus 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(
*bmain, scene, view_layer, CTX_wm_view3d(C));
VectorSet<PointCloud *> src_pointclouds;
for (Base *base_src : bases) {
src_pointclouds.add(id_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 = id_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 = id_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 = id_cast<ID *>(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 POINTCLOUD_OT_separate(wmOperatorType *ot)
{
ot->name = "Separate";
ot->idname = "POINTCLOUD_OT_separate";
ot->description = "Separate selected geometry into a new point cloud";
ot->exec = separate_exec;
ot->poll = editable_pointcloud_in_edit_mode_poll;
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
}
} // namespace blender::ed::pointcloud

View File

@@ -0,0 +1,179 @@
/* SPDX-FileCopyrightText: 2025 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup edpointcloud
*/
#include "BLI_task.hh"
#include "BKE_attribute.hh"
#include "BKE_attribute_storage.hh"
#include "BKE_context.hh"
#include "BKE_main.hh"
#include "BKE_object.hh"
#include "BKE_pointcloud.hh"
#include "BKE_undo_system.hh"
#include "CLG_log.h"
#include "DEG_depsgraph.hh"
#include "ED_pointcloud.hh"
#include "ED_undo.hh"
#include "WM_api.hh"
#include "WM_types.hh"
namespace blender {
static CLG_LogRef LOG = {"undo.pointcloud"};
namespace ed::pointcloud {
namespace undo {
/* -------------------------------------------------------------------- */
/** \name Implements ED Undo System
*
* \note This is similar for all edit-mode types.
* \{ */
struct StepObject {
UndoRefID_Object obedit_ref = {};
bke::AttributeStorage attribute_storage;
int totpoint = 0;
/* Store the bounds caches because they are small. */
SharedCache<Bounds<float3>> bounds_cache;
SharedCache<Bounds<float3>> bounds_with_radius_cache;
};
struct PointCloudUndoStep {
UndoStep step;
/** See #ED_undo_object_editmode_validate_scene_from_windows code comment for details. */
UndoRefID_Scene scene_ref = {};
Array<StepObject> objects;
};
static bool step_encode(bContext *C, Main *bmain, UndoStep *us_p)
{
PointCloudUndoStep *us = reinterpret_cast<PointCloudUndoStep *>(us_p);
Scene *scene = CTX_data_scene(C);
ViewLayer *view_layer = CTX_data_view_layer(C);
Vector<Object *> objects = ED_undo_editmode_objects_from_view_layer(*bmain, scene, view_layer);
us->scene_ref.ptr = scene;
new (&us->objects) Array<StepObject>(objects.size());
threading::parallel_for(us->objects.index_range(), 8, [&](const IndexRange range) {
for (const int i : range) {
Object *ob = objects[i];
StepObject &object = us->objects[i];
const PointCloud &pointcloud = *id_cast<const PointCloud *>(ob->data);
object.obedit_ref.ptr = ob;
object.attribute_storage.wrap() = pointcloud.attribute_storage.wrap();
object.bounds_cache = pointcloud.runtime->bounds_cache;
object.bounds_with_radius_cache = pointcloud.runtime->bounds_with_radius_cache;
object.totpoint = pointcloud.totpoint;
}
});
bmain->is_memfile_undo_flush_needed = true;
return true;
}
static void step_decode(
bContext *C, Main *bmain, UndoStep *us_p, const eUndoStepDir /*dir*/, bool /*is_final*/)
{
PointCloudUndoStep *us = reinterpret_cast<PointCloudUndoStep *>(us_p);
Scene *scene = CTX_data_scene(C);
ViewLayer *view_layer = CTX_data_view_layer(C);
ED_undo_object_editmode_validate_scene_from_windows(
CTX_wm_manager(C), us->scene_ref.ptr, &scene, &view_layer);
ED_undo_object_editmode_restore_helper(scene,
view_layer,
&us->objects.first().obedit_ref.ptr,
us->objects.size(),
sizeof(decltype(us->objects)::value_type));
BLI_assert(BKE_object_is_in_editmode(us->objects.first().obedit_ref.ptr));
for (const StepObject &object : us->objects) {
PointCloud &pointcloud = *id_cast<PointCloud *>(object.obedit_ref.ptr->data);
const bool positions_changed = [&]() {
const bke::Attribute *attr_a = pointcloud.attribute_storage.wrap().lookup("position");
const bke::Attribute *attr_b = object.attribute_storage.wrap().lookup("position");
if (!attr_b && !attr_a) {
return false;
}
if (!attr_a || !attr_b) {
return true;
}
return std::get<bke::Attribute::ArrayData>(attr_a->data()).data !=
std::get<bke::Attribute::ArrayData>(attr_b->data()).data;
}();
pointcloud.attribute_storage.wrap() = object.attribute_storage.wrap();
pointcloud.totpoint = object.totpoint;
pointcloud.runtime->bounds_cache = object.bounds_cache;
pointcloud.runtime->bounds_with_radius_cache = object.bounds_with_radius_cache;
if (positions_changed) {
pointcloud.runtime->bvh_cache.tag_dirty();
}
DEG_id_tag_update(&pointcloud.id, ID_RECALC_GEOMETRY);
}
ED_undo_object_set_active_or_warn(
*bmain, scene, view_layer, us->objects.first().obedit_ref.ptr, us_p->name, &LOG);
bmain->is_memfile_undo_flush_needed = true;
WM_event_add_notifier(C, NC_GEOM | ND_DATA, nullptr);
}
static void step_free(UndoStep *us_p)
{
PointCloudUndoStep *us = reinterpret_cast<PointCloudUndoStep *>(us_p);
us->objects.~Array();
}
static void foreach_ID_ref(UndoStep *us_p,
UndoTypeForEachIDRefFn foreach_ID_ref_fn,
void *user_data)
{
PointCloudUndoStep *us = reinterpret_cast<PointCloudUndoStep *>(us_p);
foreach_ID_ref_fn(user_data, (reinterpret_cast<UndoRefID *>(&us->scene_ref)));
for (const StepObject &object : us->objects) {
foreach_ID_ref_fn(
user_data,
(reinterpret_cast<UndoRefID *>(const_cast<UndoRefID_Object *>(&object.obedit_ref))));
}
}
/** \} */
} // namespace undo
void undosys_type_register(UndoType *ut)
{
ut->name = "Edit Point Cloud";
ut->poll = editable_pointcloud_in_edit_mode_poll;
ut->step_encode = undo::step_encode;
ut->step_decode = undo::step_decode;
ut->step_free = undo::step_free;
ut->step_foreach_ID_ref = undo::foreach_ID_ref;
ut->flags = UNDOTYPE_FLAG_NEED_CONTEXT_FOR_ENCODE;
ut->step_size = sizeof(undo::PointCloudUndoStep);
}
} // namespace ed::pointcloud
} // namespace blender