From cd04584412a62e43b7b6f4c5d150de2928bcc3e6 Mon Sep 17 00:00:00 2001 From: Hans Goudey Date: Sun, 16 Feb 2025 23:25:22 -0500 Subject: [PATCH] Point Cloud: Implement transform operator This ends up being very simple because point cloud has no connectivity information and each element is just one position. Also implement the 3D transform gizmo. The geometry deformation system isn't implemented in this commit. That can be tacked later. --- .../startup/bl_ui/space_toolsystem_toolbar.py | 2 + scripts/startup/bl_ui/space_view3d.py | 4 +- .../blender/editors/transform/CMakeLists.txt | 1 + .../editors/transform/transform_convert.cc | 4 + .../editors/transform/transform_convert.hh | 4 + .../transform_convert_point_cloud.cc | 146 ++++++++++++++++++ .../editors/transform/transform_gizmo_3d.cc | 24 +++ 7 files changed, 184 insertions(+), 1 deletion(-) create mode 100644 source/blender/editors/transform/transform_convert_point_cloud.cc diff --git a/scripts/startup/bl_ui/space_toolsystem_toolbar.py b/scripts/startup/bl_ui/space_toolsystem_toolbar.py index 750e9033bfb..09f216566f8 100644 --- a/scripts/startup/bl_ui/space_toolsystem_toolbar.py +++ b/scripts/startup/bl_ui/space_toolsystem_toolbar.py @@ -3482,6 +3482,8 @@ class VIEW3D_PT_tools_active(ToolSelectPanelHelper, Panel): *_tools_select, _defs_view3d_generic.cursor, None, + *_tools_transform, + None, *_tools_annotate, _defs_view3d_generic.ruler, ], diff --git a/scripts/startup/bl_ui/space_view3d.py b/scripts/startup/bl_ui/space_view3d.py index 9ac7edae3a4..1c6a1ff37c8 100644 --- a/scripts/startup/bl_ui/space_view3d.py +++ b/scripts/startup/bl_ui/space_view3d.py @@ -1303,7 +1303,7 @@ class VIEW3D_MT_transform(VIEW3D_MT_transform_base, Menu): if context.mode == 'EDIT_MESH': layout.operator("transform.shrink_fatten", text="Shrink/Fatten") layout.operator("transform.skin_resize") - elif context.mode in {'EDIT_CURVE', 'EDIT_GREASE_PENCIL', 'EDIT_CURVES'}: + elif context.mode in {'EDIT_CURVE', 'EDIT_GREASE_PENCIL', 'EDIT_CURVES', 'EDIT_POINT_CLOUD'}: layout.operator("transform.transform", text="Radius").mode = 'CURVE_SHRINKFATTEN' if context.mode != 'EDIT_CURVES' and context.mode != 'EDIT_GREASE_PENCIL': @@ -5899,6 +5899,8 @@ class VIEW3D_MT_edit_pointcloud(Menu): def draw(self, context): layout = self.layout + layout.menu("VIEW3D_MT_transform") + layout.separator() layout.operator("point_cloud.attribute_set") layout.template_node_operator_asset_menu_items(catalog_path=self.bl_label) diff --git a/source/blender/editors/transform/CMakeLists.txt b/source/blender/editors/transform/CMakeLists.txt index a44e09ed96d..dfafbe41a18 100644 --- a/source/blender/editors/transform/CMakeLists.txt +++ b/source/blender/editors/transform/CMakeLists.txt @@ -37,6 +37,7 @@ set(SRC transform_convert_object_texspace.cc transform_convert_paintcurve.cc transform_convert_particle.cc + transform_convert_point_cloud.cc transform_convert_sculpt.cc transform_convert_sequencer.cc transform_convert_sequencer_image.cc diff --git a/source/blender/editors/transform/transform_convert.cc b/source/blender/editors/transform/transform_convert.cc index ee87ed1b0bd..bfa2b73687c 100644 --- a/source/blender/editors/transform/transform_convert.cc +++ b/source/blender/editors/transform/transform_convert.cc @@ -788,6 +788,7 @@ static void init_TransDataContainers(TransInfo *t, Object *obact, Span &TransConvertType_Curve, &TransConvertType_Curves, &TransConvertType_GreasePencil, + &TransConvertType_PointCloud, &TransConvertType_Lattice, &TransConvertType_MBall, &TransConvertType_Mesh, @@ -973,6 +974,9 @@ static TransConvertTypeInfo *convert_type_get(const TransInfo *t, Object **r_obj if (t->obedit_type == OB_CURVES) { return &TransConvertType_Curves; } + if (t->obedit_type == OB_POINTCLOUD) { + return &TransConvertType_PointCloud; + } return nullptr; } if (ob && (ob->mode & OB_MODE_POSE)) { diff --git a/source/blender/editors/transform/transform_convert.hh b/source/blender/editors/transform/transform_convert.hh index 171d79aaa38..18db725df9a 100644 --- a/source/blender/editors/transform/transform_convert.hh +++ b/source/blender/editors/transform/transform_convert.hh @@ -236,6 +236,10 @@ extern TransConvertTypeInfo TransConvertType_Curve; extern TransConvertTypeInfo TransConvertType_Curves; +/* `transform_convert_point_cloud.cc` */ + +extern TransConvertTypeInfo TransConvertType_PointCloud; + /* `transform_convert_graph.cc` */ extern TransConvertTypeInfo TransConvertType_Graph; diff --git a/source/blender/editors/transform/transform_convert_point_cloud.cc b/source/blender/editors/transform/transform_convert_point_cloud.cc new file mode 100644 index 00000000000..6d0c906c422 --- /dev/null +++ b/source/blender/editors/transform/transform_convert_point_cloud.cc @@ -0,0 +1,146 @@ +/* SPDX-FileCopyrightText: 2023 Blender Authors + * + * SPDX-License-Identifier: GPL-2.0-or-later */ + +/** \file + * \ingroup edtransform + */ + +#include "BLI_array.hh" +#include "BLI_array_utils.hh" +#include "BLI_math_matrix.h" +#include "BLI_span.hh" + +#include "DNA_pointcloud_types.h" + +#include "BKE_attribute.hh" +#include "BKE_context.hh" +#include "BKE_geometry_set.hh" + +#include "ED_curves.hh" + +#include "MEM_guardedalloc.h" + +#include "transform.hh" +#include "transform_convert.hh" + +/* -------------------------------------------------------------------- */ +/** \name Curve/Surfaces Transform Creation + * \{ */ + +namespace blender::ed::transform::point_cloud { + +struct PointCloudTransformData { + IndexMaskMemory memory; + IndexMask selection; + + Array positions; + Array radii; +}; + +static PointCloudTransformData *create_transform_custom_data(TransCustomData &custom_data) +{ + PointCloudTransformData *transform_data = MEM_new(__func__); + custom_data.data = transform_data; + custom_data.free_cb = [](TransInfo *, TransDataContainer *, TransCustomData *custom_data) { + PointCloudTransformData *data = static_cast(custom_data->data); + MEM_delete(data); + custom_data->data = nullptr; + }; + return transform_data; +} + +static void createTransPointCloudVerts(bContext * /*C*/, TransInfo *t) +{ + MutableSpan trans_data_contrainers(t->data_container, t->data_container_len); + + for (const int i : trans_data_contrainers.index_range()) { + TransDataContainer &tc = trans_data_contrainers[i]; + PointCloud &point_cloud = *static_cast(tc.obedit->data); + bke::MutableAttributeAccessor attributes = point_cloud.attributes_for_write(); + PointCloudTransformData &transform_data = *create_transform_custom_data(tc.custom.type); + const VArray selection_attr = *attributes.lookup_or_default( + ".selection", bke::AttrDomain::Point, true); + transform_data.selection = IndexMask::from_bools(selection_attr, transform_data.memory); + tc.data_len = transform_data.selection.size(); + if (tc.data_len == 0) { + tc.custom.type.free_cb(t, &tc, &tc.custom.type); + continue; + } + + tc.data = MEM_cnew_array(tc.data_len, __func__); + MutableSpan tc_data = MutableSpan(tc.data, tc.data_len); + + transform_data.positions.reinitialize(tc.data_len); + array_utils::gather(point_cloud.positions(), + transform_data.selection, + transform_data.positions.as_mutable_span()); + + if (t->mode == TFM_CURVE_SHRINKFATTEN) { + transform_data.radii.reinitialize(transform_data.selection.size()); + array_utils::gather( + point_cloud.radius(), transform_data.selection, transform_data.radii.as_mutable_span()); + } + + const float4x4 &transform = tc.obedit->object_to_world(); + const float3x3 mtx_base = transform.view<3, 3>(); + const float3x3 smtx_base = math::pseudo_invert(mtx_base); + + threading::parallel_for(tc_data.index_range(), 1024, [&](const IndexRange range) { + for (const int64_t i : range) { + TransData &td = tc_data[i]; + float3 *elem = &transform_data.positions[i]; + copy_v3_v3(td.iloc, *elem); + copy_v3_v3(td.center, *elem); + td.loc = *elem; + + td.flag = 0; + td.flag = TD_SELECTED; + + if (t->mode == TFM_CURVE_SHRINKFATTEN) { + float *value = &transform_data.radii[i]; + td.val = value; + td.ival = *value; + } + td.ext = nullptr; + + copy_m3_m3(td.smtx, smtx_base.ptr()); + copy_m3_m3(td.mtx, mtx_base.ptr()); + } + }); + } +} + +static void recalcData_point_cloud(TransInfo *t) +{ + const Span trans_data_contrainers(t->data_container, t->data_container_len); + for (const TransDataContainer &tc : trans_data_contrainers) { + const PointCloudTransformData &transform_data = *static_cast( + tc.custom.type.data); + PointCloud &point_cloud = *static_cast(tc.obedit->data); + if (t->mode == TFM_CURVE_SHRINKFATTEN) { + array_utils::scatter(transform_data.radii.as_span(), + transform_data.selection, + point_cloud.radius_for_write()); + point_cloud.tag_radii_changed(); + } + else { + array_utils::scatter(transform_data.positions.as_span(), + transform_data.selection, + point_cloud.positions_for_write()); + point_cloud.tag_positions_changed(); + } + DEG_id_tag_update(&point_cloud.id, ID_RECALC_GEOMETRY); + } +} + +} // namespace blender::ed::transform::point_cloud + +/** \} */ + +TransConvertTypeInfo TransConvertType_PointCloud = { + /*flags*/ (T_EDIT | T_POINTS), + /*create_trans_data*/ blender::ed::transform::point_cloud::createTransPointCloudVerts, + /*recalc_data*/ blender::ed::transform::point_cloud::recalcData_point_cloud, + /*special_aftertrans_update*/ nullptr, +}; diff --git a/source/blender/editors/transform/transform_gizmo_3d.cc b/source/blender/editors/transform/transform_gizmo_3d.cc index 0b129ebb743..e55a76cabf0 100644 --- a/source/blender/editors/transform/transform_gizmo_3d.cc +++ b/source/blender/editors/transform/transform_gizmo_3d.cc @@ -19,6 +19,7 @@ #include "DNA_armature_types.h" #include "DNA_lattice_types.h" #include "DNA_meta_types.h" +#include "DNA_pointcloud_types.h" #include "BKE_armature.hh" #include "BKE_context.hh" @@ -756,6 +757,29 @@ static int gizmo_3d_foreach_selected(const bContext *C, } FOREACH_EDIT_OBJECT_END(); } + else if (obedit->type == OB_POINTCLOUD) { + FOREACH_EDIT_OBJECT_BEGIN (ob_iter, use_mat_local) { + const PointCloud &point_cloud = *static_cast(ob_iter->data); + + float4x4 mat_local; + if (use_mat_local) { + mat_local = obedit->world_to_object() * ob_iter->object_to_world(); + } + + const bke::AttributeAccessor attributes = point_cloud.attributes(); + const VArray selection = *attributes.lookup_or_default( + ".selection", bke::AttrDomain::Point, true); + + IndexMaskMemory memory; + const IndexMask mask = IndexMask::from_bools(selection, memory); + const Span positions = point_cloud.positions(); + totsel += mask.size(); + mask.foreach_index([&](const int point) { + run_coord_with_matrix(positions[point], use_mat_local, mat_local.ptr()); + }); + } + FOREACH_EDIT_OBJECT_END(); + } else if (obedit->type == OB_GREASE_PENCIL) { FOREACH_EDIT_OBJECT_BEGIN (ob_iter, use_mat_local) { GreasePencil &grease_pencil = *static_cast(ob_iter->data);