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:
@@ -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,
|
||||
],
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user