Add Chromium-only Blender WebEngine parity work
This commit is contained in:
@@ -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)
|
||||
@@ -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
|
||||
|
||||
/** \} */
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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 ®ion,
|
||||
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(
|
||||
®ion, 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 ®ion,
|
||||
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(
|
||||
®ion, 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 ®ion,
|
||||
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(
|
||||
®ion, 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 ®ion,
|
||||
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(®ion, 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
|
||||
@@ -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
|
||||
179
blender-5.2.0/source/blender/editors/pointcloud/intern/undo.cc
Normal file
179
blender-5.2.0/source/blender/editors/pointcloud/intern/undo.cc
Normal 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
|
||||
Reference in New Issue
Block a user