Curves: Add edit mode operator to set attribute values

Similar to #104426, this adds a simple operator to set attribute values
for curves edit mode. The operator is very basic and is only meant to
be a first step for more attribute editing features. Some of the
functionality could be achieved with node tools, but without dynamic
socket types or access to the active attribute, it would be incomplete.

Some of the RNA property registration, retrieval, and setting is reused
from the mesh edit mode operator. The rest of the logic is similar but
harder to de-duplicate.

Pull Request: https://projects.blender.org/blender/blender/pulls/105076
This commit is contained in:
Hans Goudey 2023-10-17 12:35:40 +02:00 committed by Hans Goudey
parent edfa6f3235
commit 31ec00895f
8 changed files with 405 additions and 154 deletions

View File

@ -5823,6 +5823,7 @@ class VIEW3D_MT_edit_curves(Menu):
layout.menu("VIEW3D_MT_transform")
layout.separator()
layout.operator("curves.attribute_set")
layout.operator("curves.delete")
layout.template_node_operator_asset_menu_items(catalog_path=self.bl_label)

View File

@ -23,6 +23,7 @@ set(INC_SYS
)
set(SRC
intern/attribute_set.cc
intern/curves_add.cc
intern/curves_data.cc
intern/curves_edit.cc

View File

@ -0,0 +1,227 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup edmesh
*/
#include "BLI_generic_pointer.hh"
#include "BKE_attribute.h"
#include "BKE_attribute_math.hh"
#include "BKE_context.h"
#include "BKE_report.h"
#include "BKE_type_conversions.hh"
#include "WM_api.hh"
#include "WM_types.hh"
#include "ED_curves.hh"
#include "ED_geometry.hh"
#include "ED_object.hh"
#include "ED_screen.hh"
#include "ED_transform.hh"
#include "ED_view3d.hh"
#include "RNA_access.hh"
#include "BLT_translation.h"
#include "UI_interface.hh"
#include "UI_resources.hh"
#include "DNA_object_types.h"
#include "DEG_depsgraph.hh"
#include "DEG_depsgraph_query.hh"
/* -------------------------------------------------------------------- */
/** \name Delete Operator
* \{ */
namespace blender::ed::curves {
static bool active_attribute_poll(bContext *C)
{
if (!editable_curves_in_edit_mode_poll(C)) {
return false;
}
Object *object = CTX_data_active_object(C);
Curves &curves_id = *static_cast<Curves *>(object->data);
const CustomDataLayer *layer = BKE_id_attributes_active_get(&const_cast<ID &>(curves_id.id));
if (!layer) {
CTX_wm_operator_poll_msg_set(C, "No active attribute");
return false;
}
if (layer->type == CD_PROP_STRING) {
CTX_wm_operator_poll_msg_set(C, "Active string attribute not supported");
return false;
}
return true;
}
static IndexMask retrieve_selected_elements(const Curves &curves_id,
const eAttrDomain domain,
IndexMaskMemory &memory)
{
switch (domain) {
case ATTR_DOMAIN_POINT:
return retrieve_selected_points(curves_id, memory);
case ATTR_DOMAIN_CURVE:
return retrieve_selected_curves(curves_id, memory);
default:
BLI_assert_unreachable();
return {};
}
}
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 int set_attribute_exec(bContext *C, wmOperator *op)
{
Object *active_object = CTX_data_active_object(C);
Curves &active_curves_id = *static_cast<Curves *>(active_object->data);
CustomDataLayer *active_attribute = BKE_id_attributes_active_get(&active_curves_id.id);
const eCustomDataType active_type = eCustomDataType(active_attribute->type);
const CPPType &type = *bke::custom_data_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 (Curves *curves_id : get_unique_editable_curves(*C)) {
bke::CurvesGeometry &curves = curves_id->geometry.wrap();
CustomDataLayer *layer = BKE_id_attributes_active_get(&curves_id->id);
if (!layer) {
continue;
}
bke::MutableAttributeAccessor attributes = curves.attributes_for_write();
bke::GSpanAttributeWriter attribute = attributes.lookup_for_write_span(layer->name);
/* Use implicit conversions to try to handle the case where the active attribute has a
* different type on multiple objects. */
const CPPType &dst_type = attribute.span.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, layer->name, dst_type, dst_buffer);
const GPointer dst_value(type, dst_buffer);
IndexMaskMemory memory;
const IndexMask selection = retrieve_selected_elements(*curves_id, attribute.domain, memory);
if (selection.is_empty()) {
attribute.finish();
continue;
}
dst_type.fill_assign_indices(dst_value.get(), attribute.span.data(), selection);
attribute.finish();
DEG_id_tag_update(&curves_id->id, ID_RECALC_GEOMETRY);
WM_event_add_notifier(C, NC_GEOM | ND_DATA, curves_id);
}
return OPERATOR_FINISHED;
}
static int set_attribute_invoke(bContext *C, wmOperator *op, const wmEvent *event)
{
Object *active_object = CTX_data_active_object(C);
Curves &active_curves_id = *static_cast<Curves *>(active_object->data);
CustomDataLayer *active_attribute = BKE_id_attributes_active_get(&active_curves_id.id);
const bke::CurvesGeometry &curves = active_curves_id.geometry.wrap();
const bke::AttributeAccessor attributes = curves.attributes();
const bke::GAttributeReader attribute = attributes.lookup(active_attribute->name);
const eAttrDomain domain = attribute.domain;
IndexMaskMemory memory;
const IndexMask selection = retrieve_selected_elements(active_curves_id, domain, memory);
const CPPType &type = attribute.varray.type();
PropertyRNA *prop = geometry::rna_property_for_type(*op->ptr,
bke::cpp_type_to_custom_data_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::convert_to_static_type(type, [&](auto dummy) {
using T = decltype(dummy);
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)
{
uiLayout *layout = uiLayoutColumn(op->layout, true);
uiLayoutSetPropSep(layout, true);
uiLayoutSetPropDecorate(layout, false);
Object *object = CTX_data_active_object(C);
Curves &curves_id = *static_cast<Curves *>(object->data);
CustomDataLayer *active_attribute = BKE_id_attributes_active_get(&curves_id.id);
const eCustomDataType active_type = eCustomDataType(active_attribute->type);
const StringRefNull prop_name = geometry::rna_property_name_for_type(active_type);
const char *name = active_attribute->name;
uiItemR(layout, op->ptr, prop_name.c_str(), UI_ITEM_NONE, name, ICON_NONE);
}
void CURVES_OT_attribute_set(wmOperatorType *ot)
{
using namespace blender::ed;
using namespace blender::ed::curves;
ot->name = "Set Attribute";
ot->description = "Set values of the active attribute for selected elements";
ot->idname = "CURVES_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::curves
/** \} */

View File

@ -1236,6 +1236,7 @@ static void CURVES_OT_delete(wmOperatorType *ot)
void ED_operatortypes_curves()
{
using namespace blender::ed::curves;
WM_operatortype_append(CURVES_OT_attribute_set);
WM_operatortype_append(CURVES_OT_convert_to_particle_system);
WM_operatortype_append(CURVES_OT_convert_from_particle_system);
WM_operatortype_append(CURVES_OT_snap_curves_to_surface);

View File

@ -12,6 +12,8 @@
#include "DNA_meshdata_types.h"
#include "DNA_scene_types.h"
#include "BLI_color.hh"
#include "BKE_attribute.h"
#include "BKE_context.h"
#include "BKE_deform.h"
@ -42,6 +44,137 @@
namespace blender::ed::geometry {
StringRefNull rna_property_name_for_type(const eCustomDataType type)
{
switch (type) {
case CD_PROP_FLOAT:
return "value_float";
case CD_PROP_FLOAT2:
return "value_float_vector_2d";
case CD_PROP_FLOAT3:
return "value_float_vector_3d";
case CD_PROP_COLOR:
case CD_PROP_BYTE_COLOR:
return "value_color";
case CD_PROP_BOOL:
return "value_bool";
case CD_PROP_INT8:
case CD_PROP_INT32:
return "value_int";
default:
BLI_assert_unreachable();
return "";
}
}
PropertyRNA *rna_property_for_type(PointerRNA &ptr, const eCustomDataType type)
{
return RNA_struct_find_property(&ptr, rna_property_name_for_type(type).c_str());
}
void register_rna_properties_for_attribute_types(StructRNA &srna)
{
static blender::float4 color_default(1);
RNA_def_float(&srna, "value_float", 0.0f, -FLT_MAX, FLT_MAX, "Value", "", -FLT_MAX, FLT_MAX);
RNA_def_float_array(&srna,
"value_float_vector_2d",
2,
nullptr,
-FLT_MAX,
FLT_MAX,
"Value",
"",
-FLT_MAX,
FLT_MAX);
RNA_def_float_array(&srna,
"value_float_vector_3d",
3,
nullptr,
-FLT_MAX,
FLT_MAX,
"Value",
"",
-FLT_MAX,
FLT_MAX);
RNA_def_int(&srna, "value_int", 0, INT_MIN, INT_MAX, "Value", "", INT_MIN, INT_MAX);
RNA_def_float_color(
&srna, "value_color", 4, color_default, -FLT_MAX, FLT_MAX, "Value", "", 0.0f, 1.0f);
RNA_def_boolean(&srna, "value_bool", false, "Value", "");
}
GPointer rna_property_for_attribute_type_retrieve_value(PointerRNA &ptr,
const eCustomDataType type,
void *buffer)
{
const StringRefNull prop_name = rna_property_name_for_type(type);
switch (type) {
case CD_PROP_FLOAT:
*static_cast<float *>(buffer) = RNA_float_get(&ptr, prop_name.c_str());
break;
case CD_PROP_FLOAT2:
RNA_float_get_array(&ptr, prop_name.c_str(), static_cast<float *>(buffer));
break;
case CD_PROP_FLOAT3:
RNA_float_get_array(&ptr, prop_name.c_str(), static_cast<float *>(buffer));
break;
case CD_PROP_COLOR:
RNA_float_get_array(&ptr, prop_name.c_str(), static_cast<float *>(buffer));
break;
case CD_PROP_BYTE_COLOR:
ColorGeometry4f value;
RNA_float_get_array(&ptr, prop_name.c_str(), value);
*static_cast<ColorGeometry4b *>(buffer) = value.encode();
break;
case CD_PROP_BOOL:
*static_cast<bool *>(buffer) = RNA_boolean_get(&ptr, prop_name.c_str());
break;
case CD_PROP_INT8:
*static_cast<int8_t *>(buffer) = RNA_int_get(&ptr, prop_name.c_str());
break;
case CD_PROP_INT32:
*static_cast<int32_t *>(buffer) = RNA_int_get(&ptr, prop_name.c_str());
break;
default:
BLI_assert_unreachable();
}
return GPointer(bke::custom_data_type_to_cpp_type(type), buffer);
}
void rna_property_for_attribute_type_set_value(PointerRNA &ptr,
PropertyRNA &prop,
const GPointer value)
{
switch (bke::cpp_type_to_custom_data_type(*value.type())) {
case CD_PROP_FLOAT:
RNA_property_float_set(&ptr, &prop, *value.get<float>());
break;
case CD_PROP_FLOAT2:
RNA_property_float_set_array(&ptr, &prop, *value.get<float2>());
break;
case CD_PROP_FLOAT3:
RNA_property_float_set_array(&ptr, &prop, *value.get<float3>());
break;
case CD_PROP_BYTE_COLOR:
RNA_property_float_set_array(&ptr, &prop, value.get<ColorGeometry4b>()->decode());
break;
case CD_PROP_COLOR:
RNA_property_float_set_array(&ptr, &prop, *value.get<ColorGeometry4f>());
break;
case CD_PROP_BOOL:
RNA_property_boolean_set(&ptr, &prop, *value.get<bool>());
break;
case CD_PROP_INT8:
RNA_property_int_set(&ptr, &prop, *value.get<int8_t>());
break;
case CD_PROP_INT32:
RNA_property_int_set(&ptr, &prop, *value.get<int32_t>());
break;
default:
BLI_assert_unreachable();
}
}
/*********************** Attribute Operators ************************/
static bool geometry_attributes_poll(bContext *C)

View File

@ -70,6 +70,14 @@ bool curves_poll(bContext *C);
/** \} */
/* -------------------------------------------------------------------- */
/** \name Operators
* \{ */
void CURVES_OT_attribute_set(wmOperatorType *ot);
/** \} */
/* -------------------------------------------------------------------- */
/** \name Mask Functions
* \{ */

View File

@ -18,6 +18,33 @@
struct Mesh;
struct ReportList;
#include "BLI_generic_pointer.hh"
#include "BLI_string_ref.hh"
struct PointerRNA;
struct PropertyRNA;
namespace blender::ed::geometry {
/* -------------------------------------------------------------------- */
/** \name Attribute Value RNA Property Helpers
*
* Functions to make it easier to register RNA properties for the various attribute types and
* retrieve/set their values.
* \{ */
StringRefNull rna_property_name_for_type(eCustomDataType type);
PropertyRNA *rna_property_for_type(PointerRNA &ptr, const eCustomDataType type);
void register_rna_properties_for_attribute_types(StructRNA &srna);
GPointer rna_property_for_attribute_type_retrieve_value(PointerRNA &ptr,
const eCustomDataType type,
void *buffer);
void rna_property_for_attribute_type_set_value(PointerRNA &ptr, PropertyRNA &prop, GPointer value);
/** \} */
} // namespace blender::ed::geometry
void ED_operatortypes_geometry();
/**

View File

@ -25,6 +25,7 @@
#include "RNA_define.hh"
#include "RNA_enum_types.hh"
#include "ED_geometry.hh"
#include "ED_mesh.hh"
#include "ED_object.hh"
#include "ED_screen.hh"
@ -88,33 +89,6 @@ static bool mesh_active_attribute_poll(bContext *C)
namespace set_attribute {
static StringRefNull rna_property_name_for_type(const eCustomDataType type)
{
switch (type) {
case CD_PROP_FLOAT:
return "value_float";
case CD_PROP_FLOAT2:
return "value_float_vector_2d";
case CD_PROP_FLOAT3:
return "value_float_vector_3d";
case CD_PROP_COLOR:
case CD_PROP_BYTE_COLOR:
return "value_color";
case CD_PROP_BOOL:
return "value_bool";
case CD_PROP_INT8:
case CD_PROP_INT32:
return "value_int";
case CD_PROP_INT32_2D:
return "value_int_vector_2d";
case CD_PROP_QUATERNION:
return "value_quat";
default:
BLI_assert_unreachable();
return "";
}
}
static void bmesh_vert_edge_face_layer_selected_values_set(BMesh &bm,
const BMIterType iter_type,
const GPointer value,
@ -186,48 +160,9 @@ static int mesh_set_attribute_exec(bContext *C, wmOperator *op)
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 StringRefNull prop_name = rna_property_name_for_type(active_type);
switch (active_type) {
case CD_PROP_FLOAT:
*static_cast<float *>(buffer) = RNA_float_get(op->ptr, prop_name.c_str());
break;
case CD_PROP_FLOAT2:
RNA_float_get_array(op->ptr, prop_name.c_str(), static_cast<float *>(buffer));
break;
case CD_PROP_FLOAT3:
RNA_float_get_array(op->ptr, prop_name.c_str(), static_cast<float *>(buffer));
break;
case CD_PROP_COLOR:
RNA_float_get_array(op->ptr, prop_name.c_str(), static_cast<float *>(buffer));
break;
case CD_PROP_QUATERNION: {
float4 value;
RNA_float_get_array(op->ptr, prop_name.c_str(), value);
*static_cast<math::Quaternion *>(buffer) = math::normalize(math::Quaternion(value));
break;
}
case CD_PROP_BYTE_COLOR:
ColorGeometry4f value;
RNA_float_get_array(op->ptr, prop_name.c_str(), value);
*static_cast<ColorGeometry4b *>(buffer) = value.encode();
break;
case CD_PROP_BOOL:
*static_cast<bool *>(buffer) = RNA_boolean_get(op->ptr, prop_name.c_str());
break;
case CD_PROP_INT8:
*static_cast<int8_t *>(buffer) = RNA_int_get(op->ptr, prop_name.c_str());
break;
case CD_PROP_INT32:
*static_cast<int32_t *>(buffer) = RNA_int_get(op->ptr, prop_name.c_str());
break;
case CD_PROP_INT32_2D:
RNA_int_get_array(op->ptr, prop_name.c_str(), static_cast<int *>(buffer));
break;
default:
BLI_assert_unreachable();
}
const GPointer value(type, buffer);
const bke::DataTypeConversions &conversions = bke::get_implicit_type_conversions();
bool changed = false;
@ -305,48 +240,12 @@ static int mesh_set_attribute_invoke(bContext *C, wmOperator *op, const wmEvent
return WM_operator_props_popup(C, op, event);
}
const StringRefNull prop_name = rna_property_name_for_type(data_type);
const CPPType &type = *bke::custom_data_type_to_cpp_type(data_type);
const GPointer active_value(type, POINTER_OFFSET(active_elem->head.data, layer->offset));
PropertyRNA *prop = RNA_struct_find_property(op->ptr, prop_name.c_str());
PropertyRNA *prop = geometry::rna_property_for_type(*op->ptr, data_type);
if (!RNA_property_is_set(op->ptr, prop)) {
switch (data_type) {
case CD_PROP_FLOAT:
RNA_property_float_set(op->ptr, prop, *active_value.get<float>());
break;
case CD_PROP_FLOAT2:
RNA_property_float_set_array(op->ptr, prop, *active_value.get<float2>());
break;
case CD_PROP_FLOAT3:
RNA_property_float_set_array(op->ptr, prop, *active_value.get<float3>());
break;
case CD_PROP_BYTE_COLOR:
RNA_property_float_set_array(op->ptr, prop, active_value.get<ColorGeometry4b>()->decode());
break;
case CD_PROP_COLOR:
RNA_property_float_set_array(op->ptr, prop, *active_value.get<ColorGeometry4f>());
break;
case CD_PROP_BOOL:
RNA_property_boolean_set(op->ptr, prop, *active_value.get<bool>());
break;
case CD_PROP_INT8:
RNA_property_int_set(op->ptr, prop, *active_value.get<int8_t>());
break;
case CD_PROP_INT32:
RNA_property_int_set(op->ptr, prop, *active_value.get<int32_t>());
break;
case CD_PROP_INT32_2D:
RNA_property_int_set_array(op->ptr, prop, *active_value.get<int2>());
break;
case CD_PROP_QUATERNION: {
const math::Quaternion value = math::normalize(*active_value.get<math::Quaternion>());
RNA_property_float_set_array(op->ptr, prop, float4(value));
break;
}
default:
BLI_assert_unreachable();
}
geometry::rna_property_for_attribute_type_set_value(*op->ptr, *prop, active_value);
}
return WM_operator_props_popup(C, op, event);
@ -361,7 +260,7 @@ static void mesh_set_attribute_ui(bContext *C, wmOperator *op)
Mesh *mesh = ED_mesh_context(C);
CustomDataLayer *active_attribute = BKE_id_attributes_active_get(&mesh->id);
const eCustomDataType active_type = eCustomDataType(active_attribute->type);
const StringRefNull prop_name = rna_property_name_for_type(active_type);
const StringRefNull prop_name = geometry::rna_property_name_for_type(active_type);
const char *name = active_attribute->name;
uiItemR(layout, op->ptr, prop_name.c_str(), UI_ITEM_NONE, name, ICON_NONE);
}
@ -385,53 +284,7 @@ void MESH_OT_attribute_set(wmOperatorType *ot)
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
static blender::float4 color_default(1);
RNA_def_float(ot->srna, "value_float", 0.0f, -FLT_MAX, FLT_MAX, "Value", "", -FLT_MAX, FLT_MAX);
RNA_def_float_array(ot->srna,
"value_float_vector_2d",
2,
nullptr,
-FLT_MAX,
FLT_MAX,
"Value",
"",
-FLT_MAX,
FLT_MAX);
RNA_def_float_array(ot->srna,
"value_float_vector_3d",
3,
nullptr,
-FLT_MAX,
FLT_MAX,
"Value",
"",
-FLT_MAX,
FLT_MAX);
RNA_def_int(ot->srna, "value_int", 0, INT_MIN, INT_MAX, "Value", "", INT_MIN, INT_MAX);
RNA_def_int_array(ot->srna,
"value_int_vector_2d",
2,
nullptr,
INT_MIN,
INT_MAX,
"Value",
"",
INT_MIN,
INT_MAX);
RNA_def_float_color(
ot->srna, "value_color", 4, color_default, -FLT_MAX, FLT_MAX, "Value", "", 0.0f, 1.0f);
RNA_def_boolean(ot->srna, "value_bool", false, "Value", "");
RNA_def_float_array(ot->srna,
"value_quat",
4,
rna_default_quaternion,
-FLT_MAX,
FLT_MAX,
"Value",
"",
FLT_MAX,
FLT_MAX);
blender::ed::geometry::register_rna_properties_for_attribute_types(*ot->srna);
}
/** \} */