Attributes: add operator to convert generic attributes to other types

Editing of generic attributes on the original objects in edit modes is
still very limited. However, when applying a geometry nodes modifier
that generates new attributes. These attributes will show up in the
Attributes panel.

Currently, our exporters are not capable of exporting generic attributes.
Therefore, for the time being, a work around is to apply geometry nodes
and then convert a generic attribute to a task specific attribute like a
uv map, vertex group or vertex color layer. Once more parts of Blender
support generic attributes, this will become less important.

Currently, only meshes are supported by the operator. However, it would
be relatively easy to extend it to other geometry types.

Differential Revision: https://developer.blender.org/D13838
This commit is contained in:
Jacques Lucke 2022-01-21 12:47:35 +01:00
parent 36c40760a5
commit f6888b530a
Notes: blender-bot 2023-02-14 09:21:21 +01:00
Referenced by issue #91379, Add operators to convert generic attributes into task specific data (UVLayers, etc)
Referenced by issue #85962, Many Nodes (Join geometry, Boolean, Object Info?) and realizing instances remove UVs (output float2, not MLoopUV).
7 changed files with 237 additions and 4 deletions

View File

@ -82,6 +82,14 @@ class MESH_MT_shape_key_context_menu(Menu):
layout.operator("object.shape_key_move", icon='TRIA_UP_BAR', text="Move to Top").type = 'TOP'
layout.operator("object.shape_key_move", icon='TRIA_DOWN_BAR', text="Move to Bottom").type = 'BOTTOM'
class MESH_MT_attribute_context_menu(Menu):
bl_label = "Attribute Specials"
def draw(self, context):
layout = self.layout
layout.operator("geometry.attribute_convert")
class MESH_UL_vgroups(UIList):
def draw_item(self, _context, layout, _data, item, icon, _active_data_, _active_propname, _index):
@ -615,6 +623,10 @@ class DATA_PT_mesh_attributes(MeshButtonsPanel, Panel):
col.operator("geometry.attribute_add", icon='ADD', text="")
col.operator("geometry.attribute_remove", icon='REMOVE', text="")
col.separator()
col.menu("MESH_MT_attribute_context_menu", icon='DOWNARROW_HLT', text="")
self.draw_attribute_warnings(context, layout)
def draw_attribute_warnings(self, context, layout):
@ -652,6 +664,7 @@ class DATA_PT_mesh_attributes(MeshButtonsPanel, Panel):
classes = (
MESH_MT_vertex_group_context_menu,
MESH_MT_shape_key_context_menu,
MESH_MT_attribute_context_menu,
MESH_UL_vgroups,
MESH_UL_fmaps,
MESH_UL_shape_keys,

View File

@ -20,9 +20,11 @@ set(INC
../../blenkernel
../../blenlib
../../depsgraph
../../functions
../../makesdna
../../makesrna
../../windowmanager
../../../../intern/guardedalloc
)
set(INC_SYS

View File

@ -21,8 +21,18 @@
* \ingroup edgeometry
*/
#include "MEM_guardedalloc.h"
#include "DNA_mesh_types.h"
#include "DNA_meshdata_types.h"
#include "DNA_scene_types.h"
#include "BKE_attribute.h"
#include "BKE_context.h"
#include "BKE_deform.h"
#include "BKE_geometry_set.hh"
#include "BKE_object_deform.h"
#include "BKE_report.h"
#include "RNA_access.h"
#include "RNA_define.h"
@ -33,10 +43,19 @@
#include "WM_api.h"
#include "WM_types.h"
#include "UI_interface.h"
#include "UI_resources.h"
#include "ED_object.h"
#include "geometry_intern.hh"
namespace blender::ed::geometry {
using fn::CPPType;
using fn::GArray;
using fn::GVArray;
/*********************** Attribute Operators ************************/
static bool geometry_attributes_poll(bContext *C)
@ -76,7 +95,7 @@ static const EnumPropertyItem *geometry_attribute_domain_itemf(bContext *C,
return DummyRNA_NULL_items;
}
return rna_enum_attribute_domain_itemf(static_cast<ID *>(ob->data), r_free);
return rna_enum_attribute_domain_itemf(static_cast<ID *>(ob->data), false, r_free);
}
static int geometry_attribute_add_exec(bContext *C, wmOperator *op)
@ -180,3 +199,187 @@ void GEOMETRY_OT_attribute_remove(wmOperatorType *ot)
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
}
enum class ConvertAttributeMode {
Generic,
UVMap,
VertexGroup,
VertexColor,
};
static bool geometry_attribute_convert_poll(bContext *C)
{
if (!geometry_attributes_poll(C)) {
return false;
}
Object *ob = ED_object_context(C);
ID *data = static_cast<ID *>(ob->data);
if (GS(data->name) != ID_ME) {
return false;
}
CustomDataLayer *layer = BKE_id_attributes_active_get(data);
if (layer == nullptr) {
return false;
}
return true;
}
static int geometry_attribute_convert_exec(bContext *C, wmOperator *op)
{
Object *ob = ED_object_context(C);
ID *ob_data = static_cast<ID *>(ob->data);
CustomDataLayer *layer = BKE_id_attributes_active_get(ob_data);
const std::string name = layer->name;
const ConvertAttributeMode mode = static_cast<ConvertAttributeMode>(
RNA_enum_get(op->ptr, "mode"));
Mesh *mesh = reinterpret_cast<Mesh *>(ob_data);
MeshComponent mesh_component;
mesh_component.replace(mesh, GeometryOwnershipType::Editable);
/* General conversion steps are always the same:
* 1. Convert old data to right domain and data type.
* 2. Copy the data into a new array so that it does not depend on the old attribute anymore.
* 3. Delete the old attribute.
* 4. Create a new attribute based on the previously copied data. */
switch (mode) {
case ConvertAttributeMode::Generic: {
const AttributeDomain dst_domain = static_cast<AttributeDomain>(
RNA_enum_get(op->ptr, "domain"));
const CustomDataType dst_type = static_cast<CustomDataType>(
RNA_enum_get(op->ptr, "data_type"));
if (ELEM(dst_type, CD_PROP_STRING, CD_MLOOPCOL)) {
BKE_report(op->reports, RPT_ERROR, "Cannot convert to the selected type");
return OPERATOR_CANCELLED;
}
GVArray src_varray = mesh_component.attribute_get_for_read(name, dst_domain, dst_type);
const CPPType &cpp_type = src_varray.type();
void *new_data = MEM_malloc_arrayN(src_varray.size(), cpp_type.size(), __func__);
src_varray.materialize_to_uninitialized(new_data);
mesh_component.attribute_try_delete(name);
mesh_component.attribute_try_create(name, dst_domain, dst_type, AttributeInitMove(new_data));
break;
}
case ConvertAttributeMode::UVMap: {
MLoopUV *dst_uvs = static_cast<MLoopUV *>(
MEM_calloc_arrayN(mesh->totloop, sizeof(MLoopUV), __func__));
VArray<float2> src_varray = mesh_component.attribute_get_for_read<float2>(
name, ATTR_DOMAIN_CORNER, {0.0f, 0.0f});
for (const int i : IndexRange(mesh->totloop)) {
copy_v2_v2(dst_uvs[i].uv, src_varray[i]);
}
mesh_component.attribute_try_delete(name);
CustomData_add_layer_named(
&mesh->ldata, CD_MLOOPUV, CD_ASSIGN, dst_uvs, mesh->totloop, name.c_str());
break;
}
case ConvertAttributeMode::VertexColor: {
MLoopCol *dst_colors = static_cast<MLoopCol *>(
MEM_calloc_arrayN(mesh->totloop, sizeof(MLoopCol), __func__));
VArray<ColorGeometry4f> src_varray = mesh_component.attribute_get_for_read<ColorGeometry4f>(
name, ATTR_DOMAIN_CORNER, ColorGeometry4f{0.0f, 0.0f, 0.0f, 1.0f});
for (const int i : IndexRange(mesh->totloop)) {
ColorGeometry4b encoded_color = src_varray[i].encode();
copy_v4_v4_uchar(&dst_colors[i].r, &encoded_color.r);
}
mesh_component.attribute_try_delete(name);
CustomData_add_layer_named(
&mesh->ldata, CD_MLOOPCOL, CD_ASSIGN, dst_colors, mesh->totloop, name.c_str());
break;
}
case ConvertAttributeMode::VertexGroup: {
Array<float> src_weights(mesh->totvert);
VArray<float> src_varray = mesh_component.attribute_get_for_read<float>(
name, ATTR_DOMAIN_POINT, 0.0f);
src_varray.materialize(src_weights);
mesh_component.attribute_try_delete(name);
bDeformGroup *defgroup = BKE_object_defgroup_new(ob, name.c_str());
const int defgroup_index = BLI_findindex(BKE_id_defgroup_list_get(&mesh->id), defgroup);
MDeformVert *dverts = BKE_object_defgroup_data_create(&mesh->id);
for (const int i : IndexRange(mesh->totvert)) {
const float weight = src_weights[i];
if (weight > 0.0f) {
BKE_defvert_add_index_notest(dverts + i, defgroup_index, weight);
}
}
break;
}
}
int *active_index = BKE_id_attributes_active_index_p(&mesh->id);
if (*active_index > 0) {
*active_index -= 1;
}
DEG_id_tag_update(&mesh->id, ID_RECALC_GEOMETRY);
WM_main_add_notifier(NC_GEOM | ND_DATA, &mesh->id);
return OPERATOR_FINISHED;
}
static void geometry_attribute_convert_ui(bContext *UNUSED(C), wmOperator *op)
{
uiLayout *layout = op->layout;
uiItemR(layout, op->ptr, "mode", 0, nullptr, ICON_NONE);
const ConvertAttributeMode mode = static_cast<ConvertAttributeMode>(
RNA_enum_get(op->ptr, "mode"));
if (mode == ConvertAttributeMode::Generic) {
uiItemR(layout, op->ptr, "domain", 0, nullptr, ICON_NONE);
uiItemR(layout, op->ptr, "data_type", 0, nullptr, ICON_NONE);
}
}
static int geometry_attribute_convert_invoke(bContext *C,
wmOperator *op,
const wmEvent *UNUSED(event))
{
return WM_operator_props_dialog_popup(C, op, 300);
}
void GEOMETRY_OT_attribute_convert(wmOperatorType *ot)
{
ot->name = "Convert Attribute";
ot->description = "Change how the attribute is stored";
ot->idname = "GEOMETRY_OT_attribute_convert";
ot->invoke = geometry_attribute_convert_invoke;
ot->exec = geometry_attribute_convert_exec;
ot->poll = geometry_attribute_convert_poll;
ot->ui = geometry_attribute_convert_ui;
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
static EnumPropertyItem mode_items[] = {
{int(ConvertAttributeMode::Generic), "GENERIC", 0, "Generic", ""},
{int(ConvertAttributeMode::UVMap), "UV_MAP", 0, "UV Map", ""},
{int(ConvertAttributeMode::VertexGroup), "VERTEX_GROUP", 0, "Vertex Group", ""},
{int(ConvertAttributeMode::VertexColor), "VERTEX_COLOR", 0, "Vertex Color", ""},
{0, nullptr, 0, nullptr, nullptr},
};
PropertyRNA *prop;
RNA_def_enum(
ot->srna, "mode", mode_items, static_cast<int>(ConvertAttributeMode::Generic), "Mode", "");
prop = RNA_def_enum(ot->srna,
"domain",
rna_enum_attribute_domain_items,
ATTR_DOMAIN_POINT,
"Domain",
"Which geometry element to move the attribute to");
RNA_def_enum_funcs(prop, geometry_attribute_domain_itemf);
RNA_def_enum(
ot->srna, "data_type", rna_enum_attribute_type_items, CD_PROP_FLOAT, "Data Type", "");
}
} // namespace blender::ed::geometry

View File

@ -25,6 +25,11 @@
struct wmOperatorType;
namespace blender::ed::geometry {
/* *** geometry_attributes.cc *** */
void GEOMETRY_OT_attribute_add(struct wmOperatorType *ot);
void GEOMETRY_OT_attribute_remove(struct wmOperatorType *ot);
void GEOMETRY_OT_attribute_convert(struct wmOperatorType *ot);
} // namespace blender::ed::geometry

View File

@ -31,6 +31,9 @@
void ED_operatortypes_geometry(void)
{
using namespace blender::ed::geometry;
WM_operatortype_append(GEOMETRY_OT_attribute_add);
WM_operatortype_append(GEOMETRY_OT_attribute_remove);
WM_operatortype_append(GEOMETRY_OT_attribute_convert);
}

View File

@ -35,7 +35,9 @@ struct bNodeType;
#define DEF_ENUM(id) extern const EnumPropertyItem id[];
#include "RNA_enum_items.h"
extern const EnumPropertyItem *rna_enum_attribute_domain_itemf(struct ID *id, bool *r_free);
extern const EnumPropertyItem *rna_enum_attribute_domain_itemf(struct ID *id,
bool include_instances,
bool *r_free);
/**
* For ID filters (#FILTER_ID_AC, #FILTER_ID_AR, ...) an int isn't enough. This version allows 64

View File

@ -166,7 +166,9 @@ static int rna_Attribute_type_get(PointerRNA *ptr)
return layer->type;
}
const EnumPropertyItem *rna_enum_attribute_domain_itemf(ID *id, bool *r_free)
const EnumPropertyItem *rna_enum_attribute_domain_itemf(ID *id,
bool include_instances,
bool *r_free)
{
EnumPropertyItem *item = NULL;
const EnumPropertyItem *domain_item = NULL;
@ -188,6 +190,9 @@ const EnumPropertyItem *rna_enum_attribute_domain_itemf(ID *id, bool *r_free)
if (id_type == ID_ME && ELEM(domain_item->value, ATTR_DOMAIN_CURVE)) {
continue;
}
if (!include_instances && domain_item->value == ATTR_DOMAIN_INSTANCE) {
continue;
}
if (domain_item->value == ATTR_DOMAIN_POINT && id_type == ID_ME) {
RNA_enum_item_add(&item, &totitem, &mesh_vertex_domain_item);
@ -207,7 +212,7 @@ static const EnumPropertyItem *rna_Attribute_domain_itemf(bContext *UNUSED(C),
PropertyRNA *UNUSED(prop),
bool *r_free)
{
return rna_enum_attribute_domain_itemf(ptr->owner_id, r_free);
return rna_enum_attribute_domain_itemf(ptr->owner_id, true, r_free);
}
static int rna_Attribute_domain_get(PointerRNA *ptr)