From 820f0c24c4106ea61c8da0059554e25c780e020c Mon Sep 17 00:00:00 2001 From: Hans Goudey Date: Tue, 17 Oct 2023 09:17:07 +0200 Subject: [PATCH 1/2] Mesh: Add versioning to convert old face maps to boolean attributes See #105317 In 46cf09327001331c77bc, face maps were just converted to an integer attribute. While the internal data remains the same that way, we lose the names and some convenient methods of interaction. This commit additionally converts each face map to a separate boolean attribute using the old name. This does require that no attributes were using that name already. Each boolean attribute can be thought of as a selection, accessible in geometry nodes and properly interpolated by mesh processing algorithms. The selections no longer have to be unique, meaning one face can be part of multiple selections. Pull Request: https://projects.blender.org/blender/blender/pulls/113769 --- .../blender/blenkernel/BKE_blender_version.h | 2 +- .../blenkernel/BKE_mesh_legacy_convert.hh | 2 +- .../blenkernel/intern/mesh_legacy_convert.cc | 56 +++++++++++++++++-- source/blender/blenkernel/intern/object.cc | 1 + .../blenloader/intern/versioning_400.cc | 7 ++- source/blender/makesdna/DNA_object_types.h | 11 ++++ 6 files changed, 68 insertions(+), 11 deletions(-) diff --git a/source/blender/blenkernel/BKE_blender_version.h b/source/blender/blenkernel/BKE_blender_version.h index 1caf205f0eb..030e21d41df 100644 --- a/source/blender/blenkernel/BKE_blender_version.h +++ b/source/blender/blenkernel/BKE_blender_version.h @@ -29,7 +29,7 @@ extern "C" { /* Blender file format version. */ #define BLENDER_FILE_VERSION BLENDER_VERSION -#define BLENDER_FILE_SUBVERSION 33 +#define BLENDER_FILE_SUBVERSION 34 /* Minimum Blender version that supports reading file written with the current * version. Older Blender versions will test this and cancel loading the file, showing a warning to diff --git a/source/blender/blenkernel/BKE_mesh_legacy_convert.hh b/source/blender/blenkernel/BKE_mesh_legacy_convert.hh index e23221eea31..d5d9be43abe 100644 --- a/source/blender/blenkernel/BKE_mesh_legacy_convert.hh +++ b/source/blender/blenkernel/BKE_mesh_legacy_convert.hh @@ -72,7 +72,7 @@ void BKE_mesh_legacy_convert_polys_to_offsets(Mesh *mesh); void BKE_mesh_legacy_convert_loops_to_corners(Mesh *mesh); -void BKE_mesh_legacy_face_map_to_generic(Mesh *mesh); +void BKE_mesh_legacy_face_map_to_generic(Main *bmain); /** * Recreate #MFace Tessellation. diff --git a/source/blender/blenkernel/intern/mesh_legacy_convert.cc b/source/blender/blenkernel/intern/mesh_legacy_convert.cc index 120f9ba9f13..f45dcbf1c17 100644 --- a/source/blender/blenkernel/intern/mesh_legacy_convert.cc +++ b/source/blender/blenkernel/intern/mesh_legacy_convert.cc @@ -16,11 +16,14 @@ #include "DNA_meshdata_types.h" #include "DNA_object_types.h" +#include "BLI_array_utils.hh" +#include "BLI_listbase.h" #include "BLI_map.hh" #include "BLI_math_geom.h" #include "BLI_math_matrix.h" #include "BLI_math_vector_types.hh" #include "BLI_memarena.h" +#include "BLI_multi_value_map.hh" #include "BLI_polyfill_2d.h" #include "BLI_resource_scope.hh" #include "BLI_string.h" @@ -30,6 +33,7 @@ #include "BKE_attribute.hh" #include "BKE_customdata.h" #include "BKE_global.h" +#include "BKE_main.h" #include "BKE_mesh.hh" #include "BKE_mesh_legacy_convert.hh" #include "BKE_multires.hh" @@ -1345,18 +1349,18 @@ void BKE_mesh_legacy_face_set_to_generic(Mesh *mesh) /** \name Face Map Conversion * \{ */ -void BKE_mesh_legacy_face_map_to_generic(Mesh *mesh) +static void move_face_map_data_to_attributes(Mesh *mesh) { using namespace blender; if (mesh->attributes().contains("face_maps")) { return; } - void *data = nullptr; + int *data = nullptr; const ImplicitSharingInfo *sharing_info = nullptr; for (const int i : IndexRange(mesh->face_data.totlayer)) { CustomDataLayer &layer = mesh->face_data.layers[i]; if (layer.type == CD_FACEMAP) { - data = layer.data; + data = static_cast(layer.data); sharing_info = layer.sharing_info; layer.data = nullptr; layer.sharing_info = nullptr; @@ -1364,13 +1368,53 @@ void BKE_mesh_legacy_face_map_to_generic(Mesh *mesh) break; } } - if (data != nullptr) { - CustomData_add_layer_named_with_data( - &mesh->face_data, CD_PROP_INT32, data, mesh->faces_num, "face_maps", sharing_info); + if (!data) { + return; } + + CustomData_add_layer_named_with_data( + &mesh->face_data, CD_PROP_INT32, data, mesh->faces_num, "face_maps", sharing_info); if (sharing_info != nullptr) { sharing_info->remove_user_and_delete_if_last(); } + + MultiValueMap groups; + for (const int i : IndexRange(mesh->faces_num)) { + if (data[i] == -1) { + /* -1 values "didn't have" a face map. */ + continue; + } + groups.add(data[i], i); + } + + bke::MutableAttributeAccessor attributes = mesh->attributes_for_write(); + for (const auto item : groups.items()) { + bke::SpanAttributeWriter attribute = attributes.lookup_or_add_for_write_span( + ".temp_face_map_" + std::to_string(item.key), ATTR_DOMAIN_FACE); + if (attribute) { + attribute.span.fill_indices(item.value.as_span(), true); + attribute.finish(); + } + } +} + +void BKE_mesh_legacy_face_map_to_generic(Main *bmain) +{ + LISTBASE_FOREACH (Mesh *, mesh, &bmain->meshes) { + move_face_map_data_to_attributes(mesh); + } + + LISTBASE_FOREACH (Object *, object, &bmain->objects) { + if (object->type != OB_MESH) { + continue; + } + Mesh *mesh = static_cast(object->data); + int i; + LISTBASE_FOREACH_INDEX (bFaceMap *, face_map, &object->fmaps, i) { + mesh->attributes_for_write().rename(".temp_face_map_" + std::to_string(i), face_map->name); + } + BLI_freelistN(&object->fmaps); + } } /** \} */ diff --git a/source/blender/blenkernel/intern/object.cc b/source/blender/blenkernel/intern/object.cc index 07d7c66c9a3..a162a0b1233 100644 --- a/source/blender/blenkernel/intern/object.cc +++ b/source/blender/blenkernel/intern/object.cc @@ -707,6 +707,7 @@ static void object_blend_read_data(BlendDataReader *reader, ID *id) /* Only for versioning, vertex group names are now stored on object data. */ BLO_read_list(reader, &ob->defbase); + BLO_read_list(reader, &ob->fmaps); /* XXX deprecated - old animation system <<< */ direct_link_nlastrips(reader, &ob->nlastrips); diff --git a/source/blender/blenloader/intern/versioning_400.cc b/source/blender/blenloader/intern/versioning_400.cc index 631652f3dcc..87a713549ad 100644 --- a/source/blender/blenloader/intern/versioning_400.cc +++ b/source/blender/blenloader/intern/versioning_400.cc @@ -311,6 +311,10 @@ void do_versions_after_linking_400(FileData *fd, Main *bmain) } } + if (!MAIN_VERSION_FILE_ATLEAST(bmain, 400, 34)) { + BKE_mesh_legacy_face_map_to_generic(bmain); + } + /** * Versioning code until next subversion bump goes here. * @@ -1097,9 +1101,6 @@ void blo_do_versions_400(FileData *fd, Library * /*lib*/, Main *bmain) } if (!MAIN_VERSION_FILE_ATLEAST(bmain, 400, 6)) { - LISTBASE_FOREACH (Mesh *, mesh, &bmain->meshes) { - BKE_mesh_legacy_face_map_to_generic(mesh); - } FOREACH_NODETREE_BEGIN (bmain, ntree, id) { versioning_replace_legacy_glossy_node(ntree); versioning_remove_microfacet_sharp_distribution(ntree); diff --git a/source/blender/makesdna/DNA_object_types.h b/source/blender/makesdna/DNA_object_types.h index 1758149fa3d..7296e9509a9 100644 --- a/source/blender/makesdna/DNA_object_types.h +++ b/source/blender/makesdna/DNA_object_types.h @@ -60,6 +60,16 @@ typedef struct bDeformGroup { char flag, _pad0[7]; } bDeformGroup; +#ifdef DNA_DEPRECATED_ALLOW +typedef struct bFaceMap { + struct bFaceMap *next, *prev; + /** MAX_VGROUP_NAME. */ + char name[64]; + char flag; + char _pad0[7]; +} bFaceMap; +#endif + #define MAX_VGROUP_NAME 64 /** #bDeformGroup::flag */ @@ -346,6 +356,7 @@ typedef struct Object { ListBase constraintChannels DNA_DEPRECATED; /* XXX deprecated... old animation system */ ListBase effect DNA_DEPRECATED; /* XXX deprecated... keep for readfile */ ListBase defbase DNA_DEPRECATED; /* Only for versioning, moved to object data. */ + ListBase fmaps DNA_DEPRECATED; /* For versioning, moved to generic attributes. */ /** List of ModifierData structures. */ ListBase modifiers; /** List of GpencilModifierData structures. */ From 3d236bc8586fd68ccf80d10279e525c1cd1dbe3b Mon Sep 17 00:00:00 2001 From: Hans Goudey Date: Tue, 17 Oct 2023 09:24:51 +0200 Subject: [PATCH 2/2] Mesh: Add operator to select elements based on boolean attribute Adds a "Select by Attribute" operator as mentioned in the discussion in #105317. This is done in 4.0 to limit the breaking aspect of the removal of face maps. The selection storage functionality is replaced by boolean attributes. We already have a way to control the data in the boolean attribute with the "Set Attribute" operator, but we didn't have a way to convert the attribute into a selection. This operator works on the active attribute if is a boolean attribute and isn't on the face corner domain. It adds to the existing selection similar to other existing operators. While this behavior can be recreated as a node tool, we add it as a builtin operator here to avoid limitations of the new node-based tool system and to make the late-in-the-release-cycle change safer. Pull Request: https://projects.blender.org/blender/blender/pulls/113772 --- scripts/startup/bl_ui/space_view3d.py | 4 + .../blender/editors/mesh/editmesh_select.cc | 106 ++++++++++++++++++ source/blender/editors/mesh/mesh_intern.h | 1 + source/blender/editors/mesh/mesh_ops.cc | 1 + 4 files changed, 112 insertions(+) diff --git a/scripts/startup/bl_ui/space_view3d.py b/scripts/startup/bl_ui/space_view3d.py index 34f547c4eb8..b39a242fd0d 100644 --- a/scripts/startup/bl_ui/space_view3d.py +++ b/scripts/startup/bl_ui/space_view3d.py @@ -1846,6 +1846,10 @@ class VIEW3D_MT_select_edit_mesh(Menu): layout.operator("mesh.select_axis", text="Side of Active") layout.operator("mesh.select_mirror") + layout.separator() + + layout.operator("mesh.select_by_attribute", text="By Attribute") + layout.template_node_operator_asset_menu_items(catalog_path=self.bl_label) diff --git a/source/blender/editors/mesh/editmesh_select.cc b/source/blender/editors/mesh/editmesh_select.cc index 5d62ecf2bcb..511e7b287b3 100644 --- a/source/blender/editors/mesh/editmesh_select.cc +++ b/source/blender/editors/mesh/editmesh_select.cc @@ -6,6 +6,8 @@ * \ingroup edmesh */ +#include + #include "MEM_guardedalloc.h" #include "BLI_bitmap.h" @@ -5358,4 +5360,108 @@ void MESH_OT_loop_to_region(wmOperatorType *ot) "Select bigger regions instead of smaller ones"); } +static bool edbm_select_by_attribute_poll(bContext *C) +{ + if (!ED_operator_editmesh(C)) { + return false; + } + Object *obedit = CTX_data_edit_object(C); + const Mesh *mesh = static_cast(obedit->data); + const CustomDataLayer *layer = BKE_id_attributes_active_get(&const_cast(mesh->id)); + if (!layer) { + CTX_wm_operator_poll_msg_set(C, "There must be an active attribute"); + return false; + } + if (layer->type != CD_PROP_BOOL) { + CTX_wm_operator_poll_msg_set(C, "The active attribute must have a boolean type"); + return false; + } + if (BKE_id_attribute_domain(&mesh->id, layer) == ATTR_DOMAIN_CORNER) { + CTX_wm_operator_poll_msg_set( + C, "The active attribute must be on the vertex, edge, or face domain"); + return false; + } + return true; +} + +static std::optional domain_to_iter_type(const eAttrDomain domain) +{ + switch (domain) { + case ATTR_DOMAIN_POINT: + return BM_VERTS_OF_MESH; + case ATTR_DOMAIN_EDGE: + return BM_EDGES_OF_MESH; + case ATTR_DOMAIN_FACE: + return BM_FACES_OF_MESH; + default: + return std::nullopt; + } +} + +static int edbm_select_by_attribute_exec(bContext *C, wmOperator * /*op*/) +{ + const Scene *scene = CTX_data_scene(C); + ViewLayer *view_layer = CTX_data_view_layer(C); + uint objects_len = 0; + Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data( + scene, view_layer, CTX_wm_view3d(C), &objects_len); + for (uint ob_index = 0; ob_index < objects_len; ob_index++) { + Object *obedit = objects[ob_index]; + Mesh *mesh = static_cast(obedit->data); + BMEditMesh *em = BKE_editmesh_from_object(obedit); + BMesh *bm = em->bm; + + const CustomDataLayer *layer = BKE_id_attributes_active_get(&mesh->id); + if (!layer) { + continue; + } + if (layer->type != CD_PROP_BOOL) { + continue; + } + if (BKE_id_attribute_domain(&mesh->id, layer) == ATTR_DOMAIN_CORNER) { + continue; + } + const std::optional iter_type = domain_to_iter_type( + BKE_id_attribute_domain(&mesh->id, layer)); + if (!iter_type) { + continue; + } + + bool changed = false; + BMElem *elem; + BMIter iter; + BM_ITER_MESH (elem, &iter, bm, *iter_type) { + if (BM_elem_flag_test(elem, BM_ELEM_HIDDEN | BM_ELEM_SELECT)) { + continue; + } + if (BM_ELEM_CD_GET_BOOL(elem, layer->offset)) { + BM_elem_select_set(bm, elem, true); + changed = true; + } + } + + if (changed) { + EDBM_selectmode_flush(em); + + DEG_id_tag_update(static_cast(obedit->data), ID_RECALC_SELECT); + WM_event_add_notifier(C, NC_GEOM | ND_SELECT, obedit->data); + } + } + MEM_freeN(objects); + + return OPERATOR_FINISHED; +} + +void MESH_OT_select_by_attribute(wmOperatorType *ot) +{ + ot->name = "Select by Attribute"; + ot->idname = "MESH_OT_select_by_attribute"; + ot->description = "Select elements based on the active boolean attribute"; + + ot->exec = edbm_select_by_attribute_exec; + ot->poll = edbm_select_by_attribute_poll; + + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; +} + /** \} */ diff --git a/source/blender/editors/mesh/mesh_intern.h b/source/blender/editors/mesh/mesh_intern.h index 8c45e66c71d..1e2911ea8d0 100644 --- a/source/blender/editors/mesh/mesh_intern.h +++ b/source/blender/editors/mesh/mesh_intern.h @@ -228,6 +228,7 @@ void MESH_OT_select_ungrouped(struct wmOperatorType *ot); void MESH_OT_select_axis(struct wmOperatorType *ot); void MESH_OT_region_to_loop(struct wmOperatorType *ot); void MESH_OT_loop_to_region(struct wmOperatorType *ot); +void MESH_OT_select_by_attribute(struct wmOperatorType *ot); void MESH_OT_shortest_path_select(struct wmOperatorType *ot); extern struct EnumPropertyItem *corner_type_items; diff --git a/source/blender/editors/mesh/mesh_ops.cc b/source/blender/editors/mesh/mesh_ops.cc index 7088dae2e06..252d31a4158 100644 --- a/source/blender/editors/mesh/mesh_ops.cc +++ b/source/blender/editors/mesh/mesh_ops.cc @@ -73,6 +73,7 @@ void ED_operatortypes_mesh() WM_operatortype_append(MESH_OT_edge_rotate); WM_operatortype_append(MESH_OT_shortest_path_select); WM_operatortype_append(MESH_OT_loop_to_region); + WM_operatortype_append(MESH_OT_select_by_attribute); WM_operatortype_append(MESH_OT_region_to_loop); WM_operatortype_append(MESH_OT_select_axis);