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.
This commit is contained in:
Hans Goudey
2025-02-16 23:25:22 -05:00
parent 62b568d393
commit cd04584412
7 changed files with 184 additions and 1 deletions

View File

@@ -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,
],

View File

@@ -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)

View File

@@ -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

View File

@@ -788,6 +788,7 @@ static void init_TransDataContainers(TransInfo *t, Object *obact, Span<Object *>
&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)) {

View File

@@ -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;

View File

@@ -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<float3> positions;
Array<float> radii;
};
static PointCloudTransformData *create_transform_custom_data(TransCustomData &custom_data)
{
PointCloudTransformData *transform_data = MEM_new<PointCloudTransformData>(__func__);
custom_data.data = transform_data;
custom_data.free_cb = [](TransInfo *, TransDataContainer *, TransCustomData *custom_data) {
PointCloudTransformData *data = static_cast<PointCloudTransformData *>(custom_data->data);
MEM_delete(data);
custom_data->data = nullptr;
};
return transform_data;
}
static void createTransPointCloudVerts(bContext * /*C*/, TransInfo *t)
{
MutableSpan<TransDataContainer> 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<PointCloud *>(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<bool>(
".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<TransData>(tc.data_len, __func__);
MutableSpan<TransData> 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<TransDataContainer> trans_data_contrainers(t->data_container, t->data_container_len);
for (const TransDataContainer &tc : trans_data_contrainers) {
const PointCloudTransformData &transform_data = *static_cast<PointCloudTransformData *>(
tc.custom.type.data);
PointCloud &point_cloud = *static_cast<PointCloud *>(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,
};

View File

@@ -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<const PointCloud *>(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<bool>(
".selection", bke::AttrDomain::Point, true);
IndexMaskMemory memory;
const IndexMask mask = IndexMask::from_bools(selection, memory);
const Span<float3> 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<GreasePencil *>(ob_iter->data);