From bb76b9db406565eb5faa6b20e75099d33756629e Mon Sep 17 00:00:00 2001 From: Hans Goudey Date: Tue, 11 Aug 2026 14:44:55 +0200 Subject: [PATCH] Mesh: Store skin modifier data as generic attributes Replace the internal storage of skin radius and the "root" and "loose" flags with generic attributes with names "skin_modifier_{radius/root/ loose}". File format backward and forward compatibility is maintained, as is API compatibility. Overall this means the data should be propagated in more places and supported in Geometry Nodes, node tools, etc. One possible behavior change is that the default value is not baked into the type anymore, but the existing operators used to create the data maintain the old default. Pull Request: https://projects.blender.org/blender/blender/pulls/162049 --- .../blenkernel/BKE_mesh_legacy_convert.hh | 6 + .../intern/attribute_legacy_convert.cc | 2 +- .../blender/blenkernel/intern/customdata.cc | 61 +---- .../blenkernel/intern/data_transfer.cc | 46 ++-- .../blenkernel/intern/data_transfer_intern.hh | 1 + source/blender/blenkernel/intern/mesh.cc | 44 ++- .../blenkernel/intern/mesh_data_update.cc | 7 - .../blenkernel/intern/mesh_legacy_convert.cc | 124 +++++++++ .../blender/blenkernel/intern/subdiv_mesh.cc | 58 ---- .../blenloader/intern/versioning_530.cc | 12 + source/blender/bmesh/operators/bmo_extrude.cc | 15 +- .../draw/engines/overlay/overlay_mesh.hh | 3 +- .../extract_mesh_vbo_skin_roots.cc | 12 +- source/blender/editors/mesh/mesh_data.cc | 19 +- source/blender/editors/mesh/mesh_join.cc | 17 -- .../blender/editors/object/object_modifier.cc | 62 +++-- .../editors/space_view3d/view3d_buttons.cc | 14 +- .../transform/transform_convert_mesh_skin.cc | 40 +-- .../geometry/intern/mesh_merge_verts.cc | 23 -- .../geometry/intern/mesh_split_edges.cc | 1 - .../blender/makesdna/DNA_customdata_types.h | 3 +- source/blender/makesdna/DNA_meshdata_types.h | 8 + source/blender/makesrna/intern/rna_mesh.cc | 256 ++++++++++-------- source/blender/modifiers/intern/MOD_skin.cc | 71 ++--- .../geometry/nodes/node_geo_extrude_mesh.cc | 1 - .../python/bmesh/bmesh_py_types_customdata.cc | 173 ++++++++++-- .../python/bmesh/bmesh_py_types_meshdata.cc | 137 +++++++--- .../python/bmesh/bmesh_py_types_meshdata.hh | 5 +- tests/python/bl_geometry_attributes.py | 58 ++++ 29 files changed, 796 insertions(+), 483 deletions(-) diff --git a/source/blender/blenkernel/BKE_mesh_legacy_convert.hh b/source/blender/blenkernel/BKE_mesh_legacy_convert.hh index 56de941ae05..a063e8c4002 100644 --- a/source/blender/blenkernel/BKE_mesh_legacy_convert.hh +++ b/source/blender/blenkernel/BKE_mesh_legacy_convert.hh @@ -30,6 +30,12 @@ void mesh_sculpt_mask_to_generic(Mesh &mesh); void mesh_freestyle_marks_to_generic(Mesh &mesh); +void mesh_skin_to_generic(Mesh &mesh); +void mesh_skin_to_legacy(AttributeStorage::BlendWriteData &attr_write_data, + CustomData &vert_data, + Vector &vert_layers, + int verts_num); + } // namespace bke void BKE_mesh_legacy_convert_uvs_to_generic(Mesh *mesh); diff --git a/source/blender/blenkernel/intern/attribute_legacy_convert.cc b/source/blender/blenkernel/intern/attribute_legacy_convert.cc index 33fcc19851b..aa1327f375e 100644 --- a/source/blender/blenkernel/intern/attribute_legacy_convert.cc +++ b/source/blender/blenkernel/intern/attribute_legacy_convert.cc @@ -46,6 +46,7 @@ std::optional custom_data_type_to_attr_type(const eCustomDataType data case CD_TESSLOOPNORMAL: case CD_FREESTYLE_EDGE: case CD_FREESTYLE_FACE: + case CD_MVERT_SKIN: return std::nullopt; /* These types are only used for #BMesh. */ @@ -62,7 +63,6 @@ std::optional custom_data_type_to_attr_type(const eCustomDataType data /* Custom data on vertices. */ case CD_MDEFORMVERT: - case CD_MVERT_SKIN: case CD_ORCO: case CD_CLOTH_ORCO: return std::nullopt; diff --git a/source/blender/blenkernel/intern/customdata.cc b/source/blender/blenkernel/intern/customdata.cc index 493bf70d183..128cea4bfa8 100644 --- a/source/blender/blenkernel/intern/customdata.cc +++ b/source/blender/blenkernel/intern/customdata.cc @@ -1101,48 +1101,6 @@ static void layerInterp_shapekey(const void **sources, const float *weights, int /** \} */ -/* -------------------------------------------------------------------- */ -/** \name Callbacks for (#MVertSkin, #CD_MVERT_SKIN) - * \{ */ - -static void layerDefault_mvert_skin(void *data, const int count) -{ - MVertSkin *vs = static_cast(data); - - for (int i = 0; i < count; i++) { - copy_v3_fl(vs[i].radius, 0.25f); - vs[i].flag = eMVertSkinFlag{}; - } -} - -static void layerCopy_mvert_skin(const void *source, void *dest, const int count) -{ - memcpy(dest, source, sizeof(MVertSkin) * count); -} - -static void layerInterp_mvert_skin(const void **sources, - const float *weights, - int count, - void *dest) -{ - float radius[3]; - zero_v3(radius); - - for (int i = 0; i < count; i++) { - const float interp_weight = weights[i]; - const MVertSkin *vs_src = static_cast(sources[i]); - - madd_v3_v3fl(radius, vs_src->radius, interp_weight); - } - - /* Delay writing to the destination in case dest is in sources. */ - MVertSkin *vs_dst = static_cast(dest); - copy_v3_v3(vs_dst->radius, radius); - vs_dst->flag &= ~MVERT_SKIN_ROOT; -} - -/** \} */ - /* -------------------------------------------------------------------- */ /** \name Callbacks for (`short[4][3]`, #CD_TESSLOOPNORMAL) * \{ */ @@ -1845,15 +1803,12 @@ static const LayerTypeInfo LAYERTYPEINFO[CD_NUMTYPES] = { .free = layerFree_grid_paint_mask, .construct = layerConstruct_grid_paint_mask, }, - /* 36: CD_MVERT_SKIN */ + /* 36: CD_MVERT_SKIN */ /* DEPRECATED */ { .size = sizeof(MVertSkin), .alignment = alignof(MVertSkin), .structname = "MVertSkin", .structnum = 1, - .copy = layerCopy_mvert_skin, - .interp = layerInterp_mvert_skin, - .set_default_value = layerDefault_mvert_skin, }, /* 37: CD_FREESTYLE_EDGE */ /* DEPRECATED */ { @@ -2074,7 +2029,7 @@ const CustomData_MeshMasks CD_MASK_BAREMESH_ORIGINDEX = { /*lmask*/ CD_MASK_PROP_INT32, }; const CustomData_MeshMasks CD_MASK_MESH = { - /*vmask*/ (CD_MASK_PROP_FLOAT3 | CD_MASK_MDEFORMVERT | CD_MASK_MVERT_SKIN | CD_MASK_PROP_ALL), + /*vmask*/ (CD_MASK_PROP_FLOAT3 | CD_MASK_MDEFORMVERT | CD_MASK_PROP_ALL), /*emask*/ CD_MASK_PROP_ALL, /*fmask*/ 0, @@ -2084,8 +2039,8 @@ const CustomData_MeshMasks CD_MASK_MESH = { (CD_MASK_MDISPS | CD_MASK_GRID_PAINT_MASK | CD_MASK_PROP_ALL), }; const CustomData_MeshMasks CD_MASK_DERIVEDMESH = { - /*vmask*/ (CD_MASK_ORIGINDEX | CD_MASK_MDEFORMVERT | CD_MASK_SHAPEKEY | CD_MASK_MVERT_SKIN | - CD_MASK_ORCO | CD_MASK_CLOTH_ORCO | CD_MASK_PROP_ALL), + /*vmask*/ (CD_MASK_ORIGINDEX | CD_MASK_MDEFORMVERT | CD_MASK_SHAPEKEY | CD_MASK_ORCO | + CD_MASK_CLOTH_ORCO | CD_MASK_PROP_ALL), /*emask*/ (CD_MASK_ORIGINDEX | CD_MASK_PROP_ALL), /*fmask*/ (CD_MASK_ORIGINDEX | CD_MASK_ORIGSPACE), @@ -2095,8 +2050,7 @@ const CustomData_MeshMasks CD_MASK_DERIVEDMESH = { (CD_MASK_ORIGSPACE_MLOOP | CD_MASK_PROP_ALL), /* XXX: MISSING #CD_MASK_MLOOPTANGENT ? */ }; const CustomData_MeshMasks CD_MASK_BMESH = { - /*vmask*/ (CD_MASK_MDEFORMVERT | CD_MASK_MVERT_SKIN | CD_MASK_SHAPEKEY | - CD_MASK_SHAPE_KEYINDEX | CD_MASK_PROP_ALL), + /*vmask*/ (CD_MASK_MDEFORMVERT | CD_MASK_SHAPEKEY | CD_MASK_SHAPE_KEYINDEX | CD_MASK_PROP_ALL), /*emask*/ CD_MASK_PROP_ALL, /*fmask*/ 0, /*pmask*/ @@ -2105,9 +2059,8 @@ const CustomData_MeshMasks CD_MASK_BMESH = { (CD_MASK_MDISPS | CD_MASK_GRID_PAINT_MASK | CD_MASK_PROP_ALL), }; const CustomData_MeshMasks CD_MASK_EVERYTHING = { - /*vmask*/ (CD_MASK_BM_ELEM_PYPTR | CD_MASK_ORIGINDEX | CD_MASK_MDEFORMVERT | - CD_MASK_MVERT_SKIN | CD_MASK_ORCO | CD_MASK_CLOTH_ORCO | CD_MASK_SHAPEKEY | - CD_MASK_SHAPE_KEYINDEX | CD_MASK_PROP_ALL), + /*vmask*/ (CD_MASK_BM_ELEM_PYPTR | CD_MASK_ORIGINDEX | CD_MASK_MDEFORMVERT | CD_MASK_ORCO | + CD_MASK_CLOTH_ORCO | CD_MASK_SHAPEKEY | CD_MASK_SHAPE_KEYINDEX | CD_MASK_PROP_ALL), /*emask*/ (CD_MASK_BM_ELEM_PYPTR | CD_MASK_ORIGINDEX | CD_MASK_PROP_ALL), /*fmask*/ diff --git a/source/blender/blenkernel/intern/data_transfer.cc b/source/blender/blenkernel/intern/data_transfer.cc index ce28570cb8f..66f2485296c 100644 --- a/source/blender/blenkernel/intern/data_transfer.cc +++ b/source/blender/blenkernel/intern/data_transfer.cc @@ -187,7 +187,7 @@ int BKE_object_data_transfer_dttype_to_cdtype(const int dtdata_type) case DT_TYPE_MDEFORMVERT: return CD_FAKE_MDEFORMVERT; case DT_TYPE_SKIN: - return CD_MVERT_SKIN; + return CD_FAKE_SKIN_RADIUS; case DT_TYPE_BWEIGHT_VERT: return CD_FAKE_BWEIGHT; @@ -1003,35 +1003,7 @@ static bool data_transfer_layersmapping_generate(Vectorvert_data, CD_MVERT_SKIN); - if (data_src) { - void *data_dst = CustomData_get_layer_for_write( - &me_dst->vert_data, CD_MVERT_SKIN, me_dst->verts_num); - if (!data_dst && use_create) { - data_dst = CustomData_add_layer( - &me_dst->vert_data, CD_MVERT_SKIN, CD_SET_DEFAULT, me_dst->verts_num); - } - - if (r_map && data_dst) { - data_transfer_layersmapping_add_item_cd(r_map, - CD_MVERT_SKIN, - mix_mode, - mix_factor, - mix_weights, - data_src, - data_dst, - nullptr, - nullptr); - } - } - else { - if (use_delete) { - CustomData_free_layer(&me_dst->vert_data, CD_MVERT_SKIN, 0); - } - } - } - else if (cddata_type == CD_PROP_BYTE_COLOR) { + if (cddata_type == CD_PROP_BYTE_COLOR) { if (!data_transfer_layersmapping_cdlayers(r_map, CD_PROP_BYTE_COLOR, bke::AttrDomain::Point, @@ -1096,6 +1068,20 @@ static bool data_transfer_layersmapping_generate(Vectoredge_data, edge_layers); CustomData_blend_write_prepare(mesh->face_data, face_layers); CustomData_blend_write_prepare(mesh->corner_data, loop_layers); + if (!is_undo) { + mesh_skin_to_legacy(attribute_data, mesh->vert_data, vert_layers, mesh->verts_num); + } if (attribute_data.attributes.is_empty()) { mesh->attribute_storage.dna_attributes = nullptr; mesh->attribute_storage.dna_attributes_num = 0; @@ -535,33 +538,44 @@ bool BKE_mesh_attribute_required(const StringRef name) void BKE_mesh_ensure_skin_customdata(Mesh *mesh) { + using namespace bke; BMesh *bm = mesh->runtime->edit_mesh ? mesh->runtime->edit_mesh->bm : nullptr; - MVertSkin *vs; if (bm) { - if (!CustomData_has_layer(&bm->vdata, CD_MVERT_SKIN)) { + if (!CustomData_has_layer_named(&bm->vdata, CD_PROP_FLOAT2, "skin_modifier_radius")) { + BM_data_layer_add_named(bm, &bm->vdata, CD_PROP_FLOAT2, "skin_modifier_radius"); + const int offset = CustomData_get_offset_named( + &bm->vdata, CD_PROP_FLOAT2, "skin_modifier_radius"); BMVert *v; BMIter iter; - - BM_data_layer_add(bm, &bm->vdata, CD_MVERT_SKIN); - - /* Mark an arbitrary vertex as root */ BM_ITER_MESH (v, &iter, bm, BM_VERTS_OF_MESH) { - vs = static_cast( - CustomData_bmesh_get(&bm->vdata, v->head.data, CD_MVERT_SKIN)); - vs->flag |= MVERT_SKIN_ROOT; + *static_cast(BM_ELEM_CD_GET_VOID_P(v, offset)) = float2(0.25f); + } + } + if (!CustomData_has_layer_named(&bm->vdata, CD_PROP_BOOL, "skin_modifier_root")) { + BM_data_layer_add_named(bm, &bm->vdata, CD_PROP_BOOL, "skin_modifier_root"); + const int offset = CustomData_get_offset_named( + &bm->vdata, CD_PROP_BOOL, "skin_modifier_root"); + /* Mark an arbitrary vertex as root */ + BMVert *v; + BMIter iter; + BM_ITER_MESH (v, &iter, bm, BM_VERTS_OF_MESH) { + BM_ELEM_CD_SET_BOOL(v, offset, true); break; } } } else { - if (!CustomData_has_layer(&mesh->vert_data, CD_MVERT_SKIN)) { - vs = static_cast( - CustomData_add_layer(&mesh->vert_data, CD_MVERT_SKIN, CD_SET_DEFAULT, mesh->verts_num)); - + MutableAttributeAccessor attributes = mesh->attributes_for_write(); + attributes.add( + "skin_modifier_radius", AttrDomain::Point, AttributeInitValue(float2(0.25f))); + if (attributes.add("skin_modifier_root", AttrDomain::Point, AttributeInitDefaultValue())) + { /* Mark an arbitrary vertex as root */ - if (vs) { - vs->flag |= MVERT_SKIN_ROOT; + if (mesh->verts_num > 0) { + AttributeWriter root = attributes.lookup_for_write("skin_modifier_root"); + root.varray.set(0, true); + root.finish(); } } } diff --git a/source/blender/blenkernel/intern/mesh_data_update.cc b/source/blender/blenkernel/intern/mesh_data_update.cc index 60dfb8810c6..319a764d3e3 100644 --- a/source/blender/blenkernel/intern/mesh_data_update.cc +++ b/source/blender/blenkernel/intern/mesh_data_update.cc @@ -1032,13 +1032,6 @@ static void object_get_datamask(const Depsgraph &depsgraph, r_mask.vmask |= CD_MASK_MDEFORMVERT; } } - - /* Multiple objects can be in edit-mode at once. */ - if (actob && (actob->mode & OB_MODE_EDIT)) { - if (ob.mode & OB_MODE_EDIT) { - r_mask.vmask |= CD_MASK_MVERT_SKIN; - } - } } void mesh_data_update(Depsgraph &depsgraph, diff --git a/source/blender/blenkernel/intern/mesh_legacy_convert.cc b/source/blender/blenkernel/intern/mesh_legacy_convert.cc index 1e476db9dd4..657f9b63022 100644 --- a/source/blender/blenkernel/intern/mesh_legacy_convert.cc +++ b/source/blender/blenkernel/intern/mesh_legacy_convert.cc @@ -28,6 +28,7 @@ #include "BLI_multi_value_map.hh" #include "BLI_ordered_edge.hh" #include "BLI_polyfill_2d.hh" +#include "BLI_resource_scope.hh" #include "BLI_string.hh" #include "BLI_string_utf8.hh" #include "BLI_task.hh" @@ -2491,6 +2492,129 @@ void mesh_freestyle_marks_to_generic(Mesh &mesh) } } +void mesh_skin_to_generic(Mesh &mesh) +{ + if (mesh.attributes().contains("skin_modifier_radius")) { + return; + } + const void *data = nullptr; + for (const int i : IndexRange(mesh.vert_data.totlayer)) { + const CustomDataLayer &layer = mesh.vert_data.layers[i]; + if (layer.type == CD_MVERT_SKIN) { + data = layer.data; + break; + } + } + if (data == nullptr) { + return; + } + const Span src(static_cast(data), mesh.verts_num); + + float2 *radius_data = MEM_new_array_uninitialized(mesh.verts_num, __func__); + bool *root_data = MEM_new_array_uninitialized(mesh.verts_num, __func__); + bool *loose_data = MEM_new_array_uninitialized(mesh.verts_num, __func__); + threading::parallel_for(src.index_range(), 4096, [&](const IndexRange range) { + for (const int i : range) { + radius_data[i] = float2(src[i].radius[0], src[i].radius[1]); + root_data[i] = (src[i].flag & MVERT_SKIN_ROOT) != 0; + loose_data[i] = (src[i].flag & MVERT_SKIN_LOOSE) != 0; + } + }); + + CustomData_free_layers(&mesh.vert_data, CD_MVERT_SKIN); + + AttributeStorage &storage = mesh.attribute_storage.wrap(); + auto add_array = [&](const StringRef name, const AttrType type, void *array_data) { + Attribute::ArrayData array{}; + array.data = array_data; + array.size = mesh.verts_num; + array.sharing_info = ImplicitSharingPtr<>(implicit_sharing::info_for_mem_free(array_data)); + storage.add(name, AttrDomain::Point, type, std::move(array)); + }; + add_array("skin_modifier_radius", AttrType::Float2, radius_data); + add_array("skin_modifier_root", AttrType::Bool, root_data); + add_array("skin_modifier_loose", AttrType::Bool, loose_data); +} + +void mesh_skin_to_legacy(AttributeStorage::BlendWriteData &attr_write_data, + CustomData &vert_data, + Vector &vert_layers, + const int verts_num) +{ + const AttributeArray *radius_array = nullptr; + const AttributeArray *root_array = nullptr; + const AttributeArray *loose_array = nullptr; + Set attrs_to_remove; + for (const int i : attr_write_data.attributes.index_range()) { + const blender::Attribute &dna_attr = attr_write_data.attributes[i]; + if (dna_attr.domain != int8_t(AttrDomain::Point)) { + continue; + } + if (dna_attr.storage_type != int8_t(AttrStorageType::Array)) { + continue; + } + if (dna_attr.data_type == int8_t(AttrType::Float2) && + STREQ(dna_attr.name, "skin_modifier_radius")) + { + radius_array = static_cast(dna_attr.data); + attrs_to_remove.add("skin_modifier_radius"); + } + else if (dna_attr.data_type == int8_t(AttrType::Bool) && + STREQ(dna_attr.name, "skin_modifier_root")) + { + root_array = static_cast(dna_attr.data); + attrs_to_remove.add("skin_modifier_root"); + } + else if (dna_attr.data_type == int8_t(AttrType::Bool) && + STREQ(dna_attr.name, "skin_modifier_loose")) + { + loose_array = static_cast(dna_attr.data); + attrs_to_remove.add("skin_modifier_loose"); + } + } + if (radius_array == nullptr) { + return; + } + + const Span radius(static_cast(radius_array->data), verts_num); + const Span root = root_array ? + Span(static_cast(root_array->data), verts_num) : + Span(); + const Span loose = loose_array ? Span(static_cast(loose_array->data), + verts_num) : + Span(); + + MutableSpan legacy_data = attr_write_data.scope.allocator().construct_array( + verts_num); + threading::parallel_for(IndexRange(verts_num), 4096, [&](const IndexRange range) { + for (const int i : range) { + legacy_data[i].radius[0] = radius[i][0]; + legacy_data[i].radius[1] = radius[i][1]; + legacy_data[i].radius[2] = 0.0f; + legacy_data[i].flag = eMVertSkinFlag(0); + if (!root.is_empty() && root[i]) { + legacy_data[i].flag |= MVERT_SKIN_ROOT; + } + if (!loose.is_empty() && loose[i]) { + legacy_data[i].flag |= MVERT_SKIN_LOOSE; + } + } + }); + + CustomDataLayer layer{}; + layer.type = CD_MVERT_SKIN; + layer.data = legacy_data.data(); + vert_layers.append(layer); + std::ranges::stable_sort(vert_layers, [](const CustomDataLayer &a, const CustomDataLayer &b) { + return a.type < b.type; + }); + vert_data.totlayer = vert_layers.size(); + vert_data.maxlayer = vert_data.totlayer; + + attr_write_data.attributes.remove_if( + [&](const blender::Attribute &attr) { return attrs_to_remove.contains_as(attr.name); }); +} + void mesh_custom_normals_to_generic(Mesh &mesh) { if (mesh.attributes().contains("custom_normal")) { diff --git a/source/blender/blenkernel/intern/subdiv_mesh.cc b/source/blender/blenkernel/intern/subdiv_mesh.cc index 51f03c06bc9..cf4bddcc02a 100644 --- a/source/blender/blenkernel/intern/subdiv_mesh.cc +++ b/source/blender/blenkernel/intern/subdiv_mesh.cc @@ -97,10 +97,6 @@ struct SubdivMeshContext { Span coarse_dverts; MutableSpan subdiv_dverts; - /* Base and result mesh vertex group data. */ - Span coarse_CD_MVERT_SKIN; - MutableSpan subdiv_CD_MVERT_SKIN; - /* Base and result mesh data for interpolating custom normals. */ Span coarse_CD_NORMAL; MutableSpan subdiv_CD_NORMAL; @@ -152,15 +148,6 @@ static void subdiv_mesh_ctx_cache_custom_data_layers(SubdivMeshContext *ctx) if (!ctx->coarse_dverts.is_empty()) { ctx->subdiv_dverts = subdiv_mesh->deform_verts_for_write(); } - if (const auto *src = static_cast( - CustomData_get_layer(&coarse_mesh.vert_data, CD_MVERT_SKIN))) - { - ctx->coarse_CD_MVERT_SKIN = {src, coarse_mesh.verts_num}; - ctx->subdiv_CD_MVERT_SKIN = { - static_cast(CustomData_add_layer( - &subdiv_mesh->vert_data, CD_MVERT_SKIN, CD_CONSTRUCT, subdiv_mesh->verts_num)), - subdiv_mesh->verts_num}; - } if (const auto *src = static_cast( CustomData_get_layer(&coarse_mesh.corner_data, CD_NORMAL))) { @@ -283,19 +270,6 @@ static void loops_of_ptex_get(LoopsOfPtex *loops_of_ptex, /** \name Vertex custom data interpolation helpers * \{ */ -static MVertSkin mix_CD_MVERT_SKIN(const Span src, - const Span src_indices, - const Span weights) -{ - float3 radius(0); - for (const int i : src_indices.index_range()) { - radius += float3(src[src_indices[i]].radius) * weights[i]; - } - MVertSkin result{}; - copy_v3_v3(result.radius, radius); - return result; -} - static float3 mix_normals(const Span src, const Span src_indices, const Span weights) @@ -412,7 +386,6 @@ struct VerticesForInterpolation { * unnecessary copies for regular faces, where we can simply use base vertices. */ Span vert_data; Span dverts_data; - Span CD_MVERT_SKIN_data; /* Vertices data calculated for ptex corners. There are always 4 elements * in these arrays, aligned the following way: * @@ -426,7 +399,6 @@ struct VerticesForInterpolation { LinearAllocator<> allocator; Array storage_spans; std::array dverts_storage = {}; - std::array CD_MVERT_SKIN_storage = {}; /* Indices within vert_data to interpolate for. The indices are aligned * with uv coordinates in a similar way as indices in storage_spans. */ std::array vert_indices; @@ -456,7 +428,6 @@ static void vert_interpolation_from_face(const SubdivMeshContext *ctx, if (coarse_face.size() == 4) { vert_interpolation->vert_data = ctx->coarse_vert_attr_spans; vert_interpolation->dverts_data = ctx->coarse_dverts; - vert_interpolation->CD_MVERT_SKIN_data = ctx->coarse_CD_MVERT_SKIN; vert_interpolation->vert_indices[0] = ctx->coarse_corner_verts[coarse_face.start() + 0]; vert_interpolation->vert_indices[1] = ctx->coarse_corner_verts[coarse_face.start() + 1]; vert_interpolation->vert_indices[2] = ctx->coarse_corner_verts[coarse_face.start() + 2]; @@ -465,7 +436,6 @@ static void vert_interpolation_from_face(const SubdivMeshContext *ctx, else { vert_interpolation->vert_data = vert_interpolation->storage_spans; vert_interpolation->dverts_data = vert_interpolation->dverts_storage; - vert_interpolation->CD_MVERT_SKIN_data = vert_interpolation->CD_MVERT_SKIN_storage; vert_interpolation->vert_indices[0] = 0; vert_interpolation->vert_indices[1] = 1; vert_interpolation->vert_indices[2] = 2; @@ -488,10 +458,6 @@ static void vert_interpolation_from_face(const SubdivMeshContext *ctx, vert_interpolation->dverts_storage[2] = mix_deform_verts( ctx->coarse_dverts, indices, weights, vert_interpolation->dvert_mix_buffer); } - if (!ctx->coarse_CD_MVERT_SKIN.is_empty()) { - vert_interpolation->CD_MVERT_SKIN_storage[2] = mix_CD_MVERT_SKIN( - ctx->coarse_CD_MVERT_SKIN, indices, weights); - } } } @@ -517,9 +483,6 @@ static void vert_interpolation_from_corner(const SubdivMeshContext *ctx, BKE_defvert_array_copy( vert_interpolation->dverts_storage.data(), &ctx->coarse_dverts[vert], 1); } - if (!ctx->coarse_CD_MVERT_SKIN.is_empty()) { - vert_interpolation->CD_MVERT_SKIN_storage[0] = ctx->coarse_CD_MVERT_SKIN[vert]; - } /* Interpolate remaining ptex face corners, which hits loops * middle points. * @@ -544,10 +507,6 @@ static void vert_interpolation_from_corner(const SubdivMeshContext *ctx, vert_interpolation->dverts_storage[1] = mix_deform_verts( ctx->coarse_dverts, first_indices, {0.5f, 0.5f}, vert_interpolation->dvert_mix_buffer); } - if (!ctx->coarse_CD_MVERT_SKIN.is_empty()) { - vert_interpolation->CD_MVERT_SKIN_storage[1] = mix_CD_MVERT_SKIN( - ctx->coarse_CD_MVERT_SKIN, first_indices, {0.5f, 0.5f}); - } mix_attrs(ctx->coarse_vert_attr_spans, last_indices, 0.5f, @@ -558,10 +517,6 @@ static void vert_interpolation_from_corner(const SubdivMeshContext *ctx, vert_interpolation->dverts_storage[3] = mix_deform_verts( ctx->coarse_dverts, last_indices, {0.5f, 0.5f}, vert_interpolation->dvert_mix_buffer); } - if (!ctx->coarse_CD_MVERT_SKIN.is_empty()) { - vert_interpolation->CD_MVERT_SKIN_storage[3] = mix_CD_MVERT_SKIN( - ctx->coarse_CD_MVERT_SKIN, last_indices, {0.5f, 0.5f}); - } } } @@ -958,9 +913,6 @@ static void subdiv_vert_data_copy(const SubdivMeshContext *ctx, BKE_defvert_array_copy( &ctx->subdiv_dverts[subdiv_vert_index], &ctx->coarse_dverts[coarse_vert_index], 1); } - if (!ctx->coarse_CD_MVERT_SKIN.is_empty()) { - ctx->subdiv_CD_MVERT_SKIN[subdiv_vert_index] = ctx->coarse_CD_MVERT_SKIN[coarse_vert_index]; - } } static float4 quad_weights_from_uv(const float u, const float v) @@ -989,12 +941,6 @@ static void subdiv_vert_data_interpolate(const SubdivMeshContext *ctx, Span(&weights.x, 4), vert_interpolation->dvert_mix_buffer); } - if (!ctx->coarse_CD_MVERT_SKIN.is_empty()) { - ctx->subdiv_CD_MVERT_SKIN[subdiv_vert_index] = mix_CD_MVERT_SKIN( - vert_interpolation->CD_MVERT_SKIN_data, - vert_interpolation->vert_indices, - Span(&weights.x, 4)); - } } static void evaluate_vert_and_apply_displacement_copy(const SubdivMeshContext *ctx, @@ -1453,10 +1399,6 @@ static void subdiv_mesh_vert_of_loose_edge_interpolate(SubdivMeshContext *ctx, ctx->subdiv_dverts[subdiv_vert_index] = mix_deform_verts( ctx->coarse_dverts, coarse_vert_indices, {1.0f - u, u}, dvert_mix_buffer); } - if (!ctx->coarse_CD_MVERT_SKIN.is_empty()) { - ctx->subdiv_CD_MVERT_SKIN[subdiv_vert_index] = mix_CD_MVERT_SKIN( - ctx->coarse_CD_MVERT_SKIN, coarse_vert_indices, {1.0f - u, u}); - } } static void subdiv_mesh_vert_of_loose_edge(const ForeachContext *foreach_context, diff --git a/source/blender/blenloader/intern/versioning_530.cc b/source/blender/blenloader/intern/versioning_530.cc index c8f9a56be59..3a6eecc833a 100644 --- a/source/blender/blenloader/intern/versioning_530.cc +++ b/source/blender/blenloader/intern/versioning_530.cc @@ -10,12 +10,14 @@ #include "DNA_ID.h" #include "DNA_brush_types.h" +#include "DNA_mesh_types.h" #include "DNA_scene_types.h" #include "BLI_listbase_iterator.hh" #include "BLI_sys_types.hh" #include "BKE_main.hh" +#include "BKE_mesh_legacy_convert.hh" #include "BKE_node.hh" #include "BKE_node_runtime.hh" #include "BKE_paint.hh" @@ -239,6 +241,16 @@ void blo_do_versions_530(FileData * /*fd*/, Library * /*lib*/, Main *bmain) * * \note Keep this message at the bottom of the function. */ + + /* Keep this versioning always enabled at the bottom of the function; it can only be moved + * behind a subversion bump when the file format is changed (#mesh_skin_to_legacy is removed from + * #mesh_blend_write). Since that function keeps writing the old-format #CD_MVERT_SKIN layer for + * forward compatibility, files saved by *this* version also need + * this conversion to run unconditionally on read, not just for files older than this subversion. + */ + for (Mesh &mesh : bmain->meshes) { + bke::mesh_skin_to_generic(mesh); + } } } // namespace blender diff --git a/source/blender/bmesh/operators/bmo_extrude.cc b/source/blender/bmesh/operators/bmo_extrude.cc index 104d718dcc0..e91be3c8c39 100644 --- a/source/blender/bmesh/operators/bmo_extrude.cc +++ b/source/blender/bmesh/operators/bmo_extrude.cc @@ -155,13 +155,11 @@ static void bm_extrude_copy_face_loop_attributes(BMesh *bm, BMFace *f) } /* Disable the skin root flag on the input vert, assumes that the vert - * data includes an CD_MVERT_SKIN layer */ + * data includes a "skin_modifier_root" layer. */ static void bm_extrude_disable_skin_root(BMesh *bm, BMVert *v) { - MVertSkin *vs; - - vs = static_cast(CustomData_bmesh_get(&bm->vdata, v->head.data, CD_MVERT_SKIN)); - vs->flag &= ~MVERT_SKIN_ROOT; + const int offset = CustomData_get_offset_named(&bm->vdata, CD_PROP_BOOL, "skin_modifier_root"); + BM_ELEM_CD_SET_BOOL(v, offset, false); } void bmo_extrude_edge_only_exec(BMesh *bm, BMOperator *op) @@ -188,7 +186,7 @@ void bmo_extrude_edge_only_exec(BMesh *bm, BMOperator *op) BMO_op_exec(bm, &dupeop); /* disable root flag on all new skin nodes */ - if (CustomData_has_layer(&bm->vdata, CD_MVERT_SKIN)) { + if (CustomData_has_layer_named(&bm->vdata, CD_PROP_BOOL, "skin_modifier_root")) { BMVert *v; BMO_ITER (v, &siter, dupeop.slots_out, "geom.out", BM_VERT) { bm_extrude_disable_skin_root(bm, v); @@ -239,7 +237,8 @@ void bmo_extrude_vert_indiv_exec(BMesh *bm, BMOperator *op) BMOIter siter; BMVert *v, *dupev; BMEdge *e; - const bool has_vskin = CustomData_has_layer(&bm->vdata, CD_MVERT_SKIN); + const bool has_vskin = CustomData_has_layer_named( + &bm->vdata, CD_PROP_BOOL, "skin_modifier_root"); GHash *select_history_map = nullptr; if (use_select_history) { @@ -415,7 +414,7 @@ void bmo_extrude_face_region_exec(BMesh *bm, BMOperator *op) BMO_op_exec(bm, &dupeop); /* disable root flag on all new skin nodes */ - if (CustomData_has_layer(&bm->vdata, CD_MVERT_SKIN)) { + if (CustomData_has_layer_named(&bm->vdata, CD_PROP_BOOL, "skin_modifier_root")) { BMO_ITER (v, &siter, dupeop.slots_out, "geom.out", BM_VERT) { bm_extrude_disable_skin_root(bm, v); } diff --git a/source/blender/draw/engines/overlay/overlay_mesh.hh b/source/blender/draw/engines/overlay/overlay_mesh.hh index ae15df1bbd2..dd00271c73d 100644 --- a/source/blender/draw/engines/overlay/overlay_mesh.hh +++ b/source/blender/draw/engines/overlay/overlay_mesh.hh @@ -474,7 +474,8 @@ class Meshes : Overlay { { Mesh &mesh = DRW_object_get_data_for_drawing(*ob); if (BMEditMesh *em = mesh.runtime->edit_mesh.get()) { - return CustomData_get_offset(&em->bm->vdata, CD_MVERT_SKIN) != -1; + return CustomData_get_offset_named(&em->bm->vdata, CD_PROP_FLOAT2, "skin_modifier_radius") != + -1; } return false; } diff --git a/source/blender/draw/intern/mesh_extractors/extract_mesh_vbo_skin_roots.cc b/source/blender/draw/intern/mesh_extractors/extract_mesh_vbo_skin_roots.cc index 5fecad10d59..15850f48f5e 100644 --- a/source/blender/draw/intern/mesh_extractors/extract_mesh_vbo_skin_roots.cc +++ b/source/blender/draw/intern/mesh_extractors/extract_mesh_vbo_skin_roots.cc @@ -30,13 +30,17 @@ gpu::VertBufPtr extract_skin_roots(const MeshRenderData &mr) }(); Vector skin_roots; - const int offset = CustomData_get_offset(&mr.bm->vdata, CD_MVERT_SKIN); + const int radius_offset = CustomData_get_offset_named( + &mr.bm->vdata, CD_PROP_FLOAT2, "skin_modifier_radius"); + const int root_offset = CustomData_get_offset_named( + &mr.bm->vdata, CD_PROP_BOOL, "skin_modifier_root"); BMIter iter; BMVert *vert; BM_ITER_MESH (vert, &iter, mr.bm, BM_VERTS_OF_MESH) { - const MVertSkin *vs = static_cast BM_ELEM_CD_GET_VOID_P(vert, offset); - if (vs->flag & MVERT_SKIN_ROOT) { - skin_roots.append({(vs->radius[0] + vs->radius[1]) * 0.5f, bm_vert_co_get(mr, vert)}); + if (BM_ELEM_CD_GET_BOOL(vert, root_offset)) { + const float2 *radius = static_cast( + BM_ELEM_CD_GET_VOID_P(vert, radius_offset)); + skin_roots.append({((*radius)[0] + (*radius)[1]) * 0.5f, bm_vert_co_get(mr, vert)}); } } diff --git a/source/blender/editors/mesh/mesh_data.cc b/source/blender/editors/mesh/mesh_data.cc index b3e7a85423d..3c6570ff856 100644 --- a/source/blender/editors/mesh/mesh_data.cc +++ b/source/blender/editors/mesh/mesh_data.cc @@ -586,11 +586,12 @@ static SkinState mesh_customdata_skin_state(bContext *C) return SkinState::Invalid; } if (BMEditMesh *em = mesh->runtime->edit_mesh.get()) { - return CustomData_has_layer(&em->bm->vdata, CD_MVERT_SKIN) ? SkinState::HasSkin : - SkinState::NoSkin; + return CustomData_has_layer_named(&em->bm->vdata, CD_PROP_FLOAT2, "skin_modifier_radius") ? + SkinState::HasSkin : + SkinState::NoSkin; } - return CustomData_has_layer(&mesh->vert_data, CD_MVERT_SKIN) ? SkinState::HasSkin : - SkinState::NoSkin; + return mesh->attributes().contains("skin_modifier_radius") ? SkinState::HasSkin : + SkinState::NoSkin; } static bool mesh_customdata_skin_add_poll(bContext *C) @@ -633,15 +634,19 @@ static wmOperatorStatus mesh_customdata_skin_clear_exec(bContext *C, wmOperator Object *object = ed::object::context_object(C); Mesh *mesh = id_cast(object->data); if (BMEditMesh *em = mesh->runtime->edit_mesh.get()) { - if (!CustomData_has_layer(&em->bm->vdata, CD_MVERT_SKIN)) { + if (!CustomData_has_layer_named(&em->bm->vdata, CD_PROP_FLOAT2, "skin_modifier_radius")) { return OPERATOR_CANCELLED; } - BM_data_layer_free(em->bm, &em->bm->vdata, CD_MVERT_SKIN); + BM_data_layer_free_named(em->bm, &em->bm->vdata, "skin_modifier_radius"); + BM_data_layer_free_named(em->bm, &em->bm->vdata, "skin_modifier_root"); + BM_data_layer_free_named(em->bm, &em->bm->vdata, "skin_modifier_loose"); } else { - if (!CustomData_free_layers(&mesh->vert_data, CD_MVERT_SKIN)) { + if (!mesh->attributes_for_write().remove("skin_modifier_radius")) { return OPERATOR_CANCELLED; } + mesh->attributes_for_write().remove("skin_modifier_root"); + mesh->attributes_for_write().remove("skin_modifier_loose"); } DEG_id_tag_update(&mesh->id, ID_RECALC_GEOMETRY); WM_event_add_notifier(C, NC_GEOM | ND_DATA, mesh); diff --git a/source/blender/editors/mesh/mesh_join.cc b/source/blender/editors/mesh/mesh_join.cc index d46228271cc..0f685999397 100644 --- a/source/blender/editors/mesh/mesh_join.cc +++ b/source/blender/editors/mesh/mesh_join.cc @@ -729,23 +729,6 @@ wmOperatorStatus join_objects_exec(bContext *C, wmOperator *op) } } - /* Copy skin data to the out-of-main mesh. */ - if (std::any_of(objects_to_join.begin(), objects_to_join.end(), [](const Object *object) { - const Mesh &src_mesh = *id_cast(object->data); - return CustomData_has_layer(&src_mesh.vert_data, CD_MVERT_SKIN); - })) - { - MVertSkin *dst = static_cast(CustomData_add_layer( - &dst_mesh->vert_data, CD_MVERT_SKIN, CD_SET_DEFAULT, dst_mesh->verts_num)); - for (const int i : objects_to_join.index_range()) { - const Mesh &src_mesh = *id_cast(objects_to_join[i]->data); - if (const void *src = CustomData_get_layer(&src_mesh.vert_data, CD_MVERT_SKIN)) { - CustomData_copy_elements( - CD_MVERT_SKIN, src, &dst[vert_ranges[i].first()], src_mesh.verts_num); - } - } - } - BKE_mesh_nomain_to_mesh(dst_mesh, active_mesh, active_object, false); for (Object *object : objects_to_join.as_span().drop_front(1)) { diff --git a/source/blender/editors/object/object_modifier.cc b/source/blender/editors/object/object_modifier.cc index ac2d028bc77..005ba573f12 100644 --- a/source/blender/editors/object/object_modifier.cc +++ b/source/blender/editors/object/object_modifier.cc @@ -356,7 +356,7 @@ static bool object_modifier_remove( } } else if (md->type == eModifierType_Skin) { - /* Delete MVertSkin layer if not used by another skin modifier */ + /* Delete skin vertex attributes if not used by another skin modifier */ if (object_modifier_safe_to_delete(bmain, ob, md, eModifierType_Skin)) { modifier_skin_customdata_delete(ob); } @@ -2559,10 +2559,14 @@ static void modifier_skin_customdata_delete(Object *ob) { Mesh *mesh = id_cast(ob->data); if (BMEditMesh *em = mesh->runtime->edit_mesh.get()) { - BM_data_layer_free(em->bm, &em->bm->vdata, CD_MVERT_SKIN); + BM_data_layer_free_named(em->bm, &em->bm->vdata, "skin_modifier_radius"); + BM_data_layer_free_named(em->bm, &em->bm->vdata, "skin_modifier_root"); + BM_data_layer_free_named(em->bm, &em->bm->vdata, "skin_modifier_loose"); } else { - CustomData_free_layer_active(&mesh->vert_data, CD_MVERT_SKIN); + mesh->attributes_for_write().remove("skin_modifier_radius"); + mesh->attributes_for_write().remove("skin_modifier_root"); + mesh->attributes_for_write().remove("skin_modifier_loose"); } } @@ -2582,7 +2586,7 @@ static bool skin_edit_poll(bContext *C) !ID_IS_OVERRIDE_LIBRARY(ob) && !ID_IS_OVERRIDE_LIBRARY(ob->data)); } -static void skin_root_clear(BMVert *bm_vert, Set &visited, const int cd_vert_skin_offset) +static void skin_root_clear(BMVert *bm_vert, Set &visited, const int cd_skin_root_offset) { BMEdge *bm_edge; BMIter bm_iter; @@ -2591,12 +2595,10 @@ static void skin_root_clear(BMVert *bm_vert, Set &visited, const int c BMVert *v2 = BM_edge_other_vert(bm_edge, bm_vert); if (visited.add(v2)) { - MVertSkin *vs = static_cast(BM_ELEM_CD_GET_VOID_P(v2, cd_vert_skin_offset)); - /* clear vertex root flag and add to visited set */ - vs->flag &= ~MVERT_SKIN_ROOT; + BM_ELEM_CD_SET_BOOL(v2, cd_skin_root_offset, false); - skin_root_clear(v2, visited, cd_vert_skin_offset); + skin_root_clear(v2, visited, cd_skin_root_offset); } } } @@ -2612,20 +2614,18 @@ static wmOperatorStatus skin_root_mark_exec(bContext *C, wmOperator * /*op*/) BKE_mesh_ensure_skin_customdata(id_cast(ob->data)); - const int cd_vert_skin_offset = CustomData_get_offset(&bm->vdata, CD_MVERT_SKIN); + const int cd_skin_root_offset = CustomData_get_offset_named( + &bm->vdata, CD_PROP_BOOL, "skin_modifier_root"); BMVert *bm_vert; BMIter bm_iter; BM_ITER_MESH (bm_vert, &bm_iter, bm, BM_VERTS_OF_MESH) { if (BM_elem_flag_test(bm_vert, BM_ELEM_SELECT) && visited.add(bm_vert)) { - MVertSkin *vs = static_cast( - BM_ELEM_CD_GET_VOID_P(bm_vert, cd_vert_skin_offset)); - /* mark vertex as root and add to visited set */ - vs->flag |= MVERT_SKIN_ROOT; + BM_ELEM_CD_SET_BOOL(bm_vert, cd_skin_root_offset, true); /* clear root flag from all connected vertices (recursively) */ - skin_root_clear(bm_vert, visited, cd_vert_skin_offset); + skin_root_clear(bm_vert, visited, cd_skin_root_offset); } } @@ -2661,23 +2661,24 @@ static wmOperatorStatus skin_loose_mark_clear_exec(bContext *C, wmOperator *op) BMesh *bm = em->bm; SkinLooseAction action = static_cast(RNA_enum_get(op->ptr, "action")); - if (!CustomData_has_layer(&bm->vdata, CD_MVERT_SKIN)) { + if (!CustomData_has_layer_named(&bm->vdata, CD_PROP_FLOAT2, "skin_modifier_radius")) { return OPERATOR_CANCELLED; } + BM_data_layer_ensure_named(bm, &bm->vdata, CD_PROP_BOOL, "skin_modifier_loose"); + const int cd_skin_loose_offset = CustomData_get_offset_named( + &bm->vdata, CD_PROP_BOOL, "skin_modifier_loose"); + BMVert *bm_vert; BMIter bm_iter; BM_ITER_MESH (bm_vert, &bm_iter, bm, BM_VERTS_OF_MESH) { if (BM_elem_flag_test(bm_vert, BM_ELEM_SELECT)) { - MVertSkin *vs = static_cast( - CustomData_bmesh_get(&bm->vdata, bm_vert->head.data, CD_MVERT_SKIN)); - switch (action) { case SKIN_LOOSE_MARK: - vs->flag |= MVERT_SKIN_LOOSE; + BM_ELEM_CD_SET_BOOL(bm_vert, cd_skin_loose_offset, true); break; case SKIN_LOOSE_CLEAR: - vs->flag &= ~MVERT_SKIN_LOOSE; + BM_ELEM_CD_SET_BOOL(bm_vert, cd_skin_loose_offset, false); break; } } @@ -2717,7 +2718,9 @@ static wmOperatorStatus skin_radii_equalize_exec(bContext *C, wmOperator * /*op* BMEditMesh *em = BKE_editmesh_from_object(ob); BMesh *bm = em->bm; - if (!CustomData_has_layer(&bm->vdata, CD_MVERT_SKIN)) { + const int cd_skin_radius_offset = CustomData_get_offset_named( + &bm->vdata, CD_PROP_FLOAT2, "skin_modifier_radius"); + if (cd_skin_radius_offset == -1) { return OPERATOR_CANCELLED; } @@ -2725,11 +2728,10 @@ static wmOperatorStatus skin_radii_equalize_exec(bContext *C, wmOperator * /*op* BMIter bm_iter; BM_ITER_MESH (bm_vert, &bm_iter, bm, BM_VERTS_OF_MESH) { if (BM_elem_flag_test(bm_vert, BM_ELEM_SELECT)) { - MVertSkin *vs = static_cast( - CustomData_bmesh_get(&bm->vdata, bm_vert->head.data, CD_MVERT_SKIN)); - float avg = (vs->radius[0] + vs->radius[1]) * 0.5f; - - vs->radius[0] = vs->radius[1] = avg; + float2 *radius = static_cast( + BM_ELEM_CD_GET_VOID_P(bm_vert, cd_skin_radius_offset)); + const float avg = ((*radius)[0] + (*radius)[1]) * 0.5f; + *radius = float2(avg); } } @@ -2822,8 +2824,8 @@ static Object *modifier_skin_armature_create(Depsgraph *depsgraph, Main *bmain, arm->drawtype = ARM_DRAW_TYPE_STICK; arm->edbo = MEM_new_zeroed>("edbo armature"); - MVertSkin *mvert_skin = static_cast( - CustomData_get_layer_for_write(&mesh->vert_data, CD_MVERT_SKIN, mesh->verts_num)); + const VArray skin_root = *mesh->attributes().lookup_or_default( + "skin_modifier_root", bke::AttrDomain::Point, false); Array vert_to_edge_offsets; Array vert_to_edge_indices; @@ -2835,7 +2837,7 @@ static Object *modifier_skin_armature_create(Depsgraph *depsgraph, Main *bmain, /* NOTE: we use EditBones here, easier to set them up and use * edit-armature functions to convert back to regular bones */ for (int v = 0; v < mesh->verts_num; v++) { - if (mvert_skin[v].flag & MVERT_SKIN_ROOT) { + if (skin_root[v]) { EditBone *bone = nullptr; /* Unless the skin root has just one adjacent edge, create @@ -2875,7 +2877,7 @@ static wmOperatorStatus skin_armature_create_exec(bContext *C, wmOperator *op) Mesh *mesh = id_cast(ob->data); ModifierData *skin_md; - if (!CustomData_has_layer(&mesh->vert_data, CD_MVERT_SKIN)) { + if (!mesh->attributes().contains("skin_modifier_radius")) { BKE_reportf(op->reports, RPT_WARNING, "Mesh '%s' has no skin vertex data", mesh->id.name + 2); return OPERATOR_CANCELLED; } diff --git a/source/blender/editors/space_view3d/view3d_buttons.cc b/source/blender/editors/space_view3d/view3d_buttons.cc index 782fe04fac2..aabd65acf20 100644 --- a/source/blender/editors/space_view3d/view3d_buttons.cc +++ b/source/blender/editors/space_view3d/view3d_buttons.cc @@ -721,7 +721,8 @@ static void v3d_editvertex_buts( &bm->vdata, CD_PROP_FLOAT, "bevel_weight_vert"); const int cd_vert_crease_offset = CustomData_get_offset_named( &bm->vdata, CD_PROP_FLOAT, "crease_vert"); - const int cd_vert_skin_offset = CustomData_get_offset(&bm->vdata, CD_MVERT_SKIN); + const int cd_vert_skin_offset = CustomData_get_offset_named( + &bm->vdata, CD_PROP_FLOAT2, "skin_modifier_radius"); const int cd_edge_bweight_offset = CustomData_get_offset_named( &bm->edata, CD_PROP_FLOAT, "bevel_weight_edge"); const int cd_edge_crease_offset = CustomData_get_offset_named( @@ -744,9 +745,9 @@ static void v3d_editvertex_buts( } if (has_skinradius) { - MVertSkin *vs = static_cast( + const float2 *radius = static_cast( BM_ELEM_CD_GET_VOID_P(eve, cd_vert_skin_offset)); - add_v2_v2(median->skin, vs->radius); /* Third val not used currently. */ + add_v2_v2(median->skin, *radius); } } } @@ -1479,7 +1480,8 @@ static void v3d_editvertex_buts( for (int i = 0; i < 2; i++) { if (median->skin[i]) { - cd_vert_skin_offset = CustomData_get_offset(&bm->vdata, CD_MVERT_SKIN); + cd_vert_skin_offset = CustomData_get_offset_named( + &bm->vdata, CD_PROP_FLOAT2, "skin_modifier_radius"); BLI_assert(cd_vert_skin_offset != -1); if (ve_median->skin[i] != median->skin[i]) { @@ -1507,14 +1509,14 @@ static void v3d_editvertex_buts( } if (cd_vert_skin_offset != -1) { - MVertSkin *vs = static_cast( + float2 *radius = static_cast( BM_ELEM_CD_GET_VOID_P(eve, cd_vert_skin_offset)); /* That one is not clamped to [0.0, 1.0]. */ for (int i = 0; i < 2; i++) { if (median->skin[i] != 0.0f) { apply_scale_factor( - &vs->radius[i], tot, ve_median->skin[i], median->skin[i], scale_skin[i]); + &(*radius)[i], tot, ve_median->skin[i], median->skin[i], scale_skin[i]); } } } diff --git a/source/blender/editors/transform/transform_convert_mesh_skin.cc b/source/blender/editors/transform/transform_convert_mesh_skin.cc index 3da3b842abe..b32da1d1a32 100644 --- a/source/blender/editors/transform/transform_convert_mesh_skin.cc +++ b/source/blender/editors/transform/transform_convert_mesh_skin.cc @@ -13,6 +13,7 @@ #include "BLI_math_matrix_c.hh" #include "BLI_math_vector_c.hh" +#include "BLI_math_vector_types.hh" #include "BKE_context.hh" #include "BKE_editmesh.hh" @@ -25,7 +26,7 @@ namespace blender::ed::transform { /* -------------------------------------------------------------------- */ -/** \name Edit Mesh #CD_MVERT_SKIN Transform Creation +/** \name Edit Mesh Skin Modifier Radius Transform Creation * \{ */ static float *mesh_skin_transdata_center(const TransIslandData *island_data, @@ -39,18 +40,17 @@ static float *mesh_skin_transdata_center(const TransIslandData *island_data, } static void mesh_skin_transdata_create(TransDataBasic *td, - BMEditMesh *em, BMVert *eve, const TransIslandData *island_data, - const int island_index) + const int island_index, + const int cd_skin_radius_offset) { BLI_assert(BM_elem_flag_test(eve, BM_ELEM_HIDDEN) == 0); - MVertSkin *vs = static_cast( - CustomData_bmesh_get(&em->bm->vdata, eve->head.data, CD_MVERT_SKIN)); td->flag = 0; - if (vs) { - copy_v3_v3(td->iloc, vs->radius); - td->loc = vs->radius; + if (cd_skin_radius_offset != -1) { + float2 *radius = static_cast(BM_ELEM_CD_GET_VOID_P(eve, cd_skin_radius_offset)); + copy_v2_v2(td->iloc, *radius); + td->loc = *radius; } else { td->flag |= TD_SKIP; @@ -88,7 +88,9 @@ static void createTransMeshSkin(bContext * /*C*/, TransInfo *t) * transform data is created by selected vertices. */ - if (!CustomData_has_layer(&bm->vdata, CD_MVERT_SKIN)) { + const int cd_skin_radius_offset = CustomData_get_offset_named( + &bm->vdata, CD_PROP_FLOAT2, "skin_modifier_radius"); + if (cd_skin_radius_offset == -1) { continue; } @@ -192,21 +194,27 @@ static void createTransMeshSkin(bContext * /*C*/, TransInfo *t) } if (mirror_data.vert_map && mirror_data.vert_map[a].index != -1) { - mesh_skin_transdata_create( - static_cast(td_mirror), em, eve, &island_data, island_index); + mesh_skin_transdata_create(static_cast(td_mirror), + eve, + &island_data, + island_index, + cd_skin_radius_offset); int elem_index = mirror_data.vert_map[a].index; BMVert *v_src = BM_vert_at_index(bm, elem_index); - MVertSkin *vs = static_cast( - CustomData_bmesh_get(&em->bm->vdata, v_src->head.data, CD_MVERT_SKIN)); + float2 *radius_src = static_cast( + BM_ELEM_CD_GET_VOID_P(v_src, cd_skin_radius_offset)); td_mirror->flag |= mirror_data.vert_map[a].flag; - td_mirror->loc_src = vs->radius; + td_mirror->loc_src = *radius_src; td_mirror++; } else if (prop_mode || BM_elem_flag_test(eve, BM_ELEM_SELECT)) { - mesh_skin_transdata_create( - static_cast(td), em, eve, &island_data, island_index); + mesh_skin_transdata_create(static_cast(td), + eve, + &island_data, + island_index, + cd_skin_radius_offset); if (t->around == V3D_AROUND_LOCAL_ORIGINS) { createSpaceNormal(td->axismtx, eve->no); diff --git a/source/blender/geometry/intern/mesh_merge_verts.cc b/source/blender/geometry/intern/mesh_merge_verts.cc index 578349024ca..3548f8aac9b 100644 --- a/source/blender/geometry/intern/mesh_merge_verts.cc +++ b/source/blender/geometry/intern/mesh_merge_verts.cc @@ -1569,29 +1569,6 @@ static Mesh *create_merged_mesh(const Mesh &mesh, result->verts_num); copy_first_from_src(src, dst_to_src_verts, dst); } - if (CustomData_has_layer(&mesh.vert_data, CD_MVERT_SKIN)) { - const Span src( - static_cast(CustomData_get_layer(&mesh.vert_data, CD_MVERT_SKIN)), - mesh.verts_num); - MutableSpan dst(static_cast(CustomData_add_layer( - &result->vert_data, CD_MVERT_SKIN, CD_CONSTRUCT, result->verts_num)), - result->verts_num); - threading::parallel_for(dst.index_range(), 2048, [&](const IndexRange range) { - for (const int dst_vert : range) { - const Span src_verts = dst_to_src_verts[dst_vert]; - if (src_verts.size() == 1) { - dst[dst_vert] = src[src_verts.first()]; - continue; - } - const float src_num_inv = math::rcp(float(src_verts.size())); - for (const int src_vert : src_verts) { - madd_v3_v3fl(dst[dst_vert].radius, src[src_vert].radius, src_num_inv); - dst[dst_vert].flag |= src[src_vert].flag; - } - } - }); - } - /* Edges. */ Array edge_final_map; diff --git a/source/blender/geometry/intern/mesh_split_edges.cc b/source/blender/geometry/intern/mesh_split_edges.cc index 58dacc15629..0a8fb275f74 100644 --- a/source/blender/geometry/intern/mesh_split_edges.cc +++ b/source/blender/geometry/intern/mesh_split_edges.cc @@ -28,7 +28,6 @@ static void propagate_vert_attributes(Mesh &mesh, const Span new_to_old_ver /* These types aren't supported for interpolation below. */ CustomData_free_layers(&mesh.vert_data, CD_SHAPEKEY); CustomData_free_layers(&mesh.vert_data, CD_CLOTH_ORCO); - CustomData_free_layers(&mesh.vert_data, CD_MVERT_SKIN); CustomData_realloc( &mesh.vert_data, mesh.verts_num, mesh.verts_num + new_to_old_verts_map.size()); mesh.verts_num += new_to_old_verts_map.size(); diff --git a/source/blender/makesdna/DNA_customdata_types.h b/source/blender/makesdna/DNA_customdata_types.h index e44af56aa93..e346a9bac75 100644 --- a/source/blender/makesdna/DNA_customdata_types.h +++ b/source/blender/makesdna/DNA_customdata_types.h @@ -85,8 +85,8 @@ enum eCustomDataType : int { CD_PAINT_MASK = 34, #endif CD_GRID_PAINT_MASK = 35, - CD_MVERT_SKIN = 36, #ifdef DNA_DEPRECATED_ALLOW + CD_MVERT_SKIN = 36, CD_FREESTYLE_EDGE = 37, CD_FREESTYLE_FACE = 38, #endif @@ -138,7 +138,6 @@ enum eCustomDataType : int { #define CD_MASK_BM_ELEM_PYPTR (1LL << CD_BM_ELEM_PYPTR) #define CD_MASK_GRID_PAINT_MASK (1LL << CD_GRID_PAINT_MASK) -#define CD_MASK_MVERT_SKIN (1LL << CD_MVERT_SKIN) #define CD_MASK_MLOOPTANGENT (1LL << CD_MLOOPTANGENT) #define CD_MASK_TESSLOOPNORMAL (1LL << CD_TESSLOOPNORMAL) #define CD_MASK_PROP_COLOR (1ULL << CD_PROP_COLOR) diff --git a/source/blender/makesdna/DNA_meshdata_types.h b/source/blender/makesdna/DNA_meshdata_types.h index 497552505d2..af77a858b8a 100644 --- a/source/blender/makesdna/DNA_meshdata_types.h +++ b/source/blender/makesdna/DNA_meshdata_types.h @@ -24,6 +24,8 @@ enum eMSelect_Type : int { ME_FSEL = 2, }; +#ifdef DNA_DEPRECATED_ALLOW + enum eMVertSkinFlag : int { /** * Marks a vertex as the edge-graph root, used for calculating rotations for all connected @@ -39,6 +41,8 @@ enum eMVertSkinFlag : int { }; ENUM_OPERATORS(eMVertSkinFlag) +#endif + /** #MFace.edcode */ enum eMFace_EdgeCode : char { ME_V1V2 = (1 << 0), @@ -183,6 +187,8 @@ struct MDeformVert { int flag; }; +#ifdef DNA_DEPRECATED_ALLOW + struct MVertSkin { /** * Radii of the skin, define how big the generated frames are. @@ -193,6 +199,8 @@ struct MVertSkin { eMVertSkinFlag flag; }; +#endif + /** \} */ /* -------------------------------------------------------------------- */ diff --git a/source/blender/makesrna/intern/rna_mesh.cc b/source/blender/makesrna/intern/rna_mesh.cc index 3ac8d553ced..6bd015acf7b 100644 --- a/source/blender/makesrna/intern/rna_mesh.cc +++ b/source/blender/makesrna/intern/rna_mesh.cc @@ -140,17 +140,6 @@ static Mesh *rna_mesh(const PointerRNA *ptr) return mesh; } -static CustomData *rna_mesh_vdata_helper(Mesh *mesh) -{ - return (mesh->runtime->edit_mesh) ? &mesh->runtime->edit_mesh->bm->vdata : &mesh->vert_data; -} - -static CustomData *rna_mesh_vdata(const PointerRNA *ptr) -{ - Mesh *mesh = rna_mesh(ptr); - return rna_mesh_vdata_helper(mesh); -} - /** \} */ /* -------------------------------------------------------------------- */ @@ -1218,82 +1207,29 @@ static void rna_mesh_color_active_set(PointerRNA *ptr, bool value) } } -static bool rna_skin_vertice_check(CollectionPropertyIterator *, void *data) -{ - CustomDataLayer *layer = static_cast(data); - return (bke::attribute_name_is_anonymous(layer->name) || layer->type != CD_MVERT_SKIN); -} - static void rna_Mesh_skin_vertices_begin(CollectionPropertyIterator *iter, PointerRNA *ptr) { - CustomData *data = rna_mesh_vdata(ptr); - if (data) { - rna_iterator_array_begin(iter, - ptr, - static_cast(data->layers), - sizeof(CustomDataLayer), - data->totlayer, - 0, - rna_skin_vertice_check); - } - else { - rna_iterator_array_begin(iter, ptr, nullptr, 0, 0, 0, nullptr); - } + memset(&iter->internal.array, 0, sizeof(iter->internal.array)); + const PointerRNA attr_ptr = rna_AttributeGroup_lookup_string( + *ptr, "skin_modifier_radius", ATTR_DOMAIN_MASK_POINT, CD_MASK_PROP_FLOAT2); + void *layer = RNA_pointer_is_null(&attr_ptr) ? nullptr : attr_ptr.data; + rna_iterator_array_begin( + iter, ptr, layer, sizeof(CustomDataLayer), layer ? 1 : 0, false, nullptr); +} + +PointerRNA rna_Mesh_skin_vertices_iterator_get(CollectionPropertyIterator *iter) +{ + return RNA_pointer_create_with_parent( + iter->parent, RNA_MeshSkinVertexLayer, rna_iterator_array_get(iter)); } static int rna_Mesh_skin_vertices_length(PointerRNA *ptr) { - CustomData *data = rna_mesh_vdata(ptr); - return data ? CustomData_number_of_layers(data, CD_MVERT_SKIN) - - CustomData_number_of_anonymous_layers(data, CD_MVERT_SKIN) : - 0; + const PointerRNA attr_ptr = rna_AttributeGroup_lookup_string( + *ptr, "skin_modifier_radius", ATTR_DOMAIN_MASK_POINT, CD_MASK_PROP_FLOAT2); + return RNA_pointer_is_null(&attr_ptr) ? 0 : 1; } -static std::optional rna_MeshSkinVertexLayer_path(const PointerRNA *ptr) -{ - const CustomDataLayer *cdl = static_cast(ptr->data); - char name_esc[sizeof(cdl->name) * 2]; - BLI_str_escape(name_esc, cdl->name, sizeof(name_esc)); - return fmt::format("skin_vertices[\"{}\"]", name_esc); -} - -static std::optional rna_VertCustomData_data_path(const PointerRNA *ptr, - const char *collection, - int type); -static std::optional rna_MeshSkinVertex_path(const PointerRNA *ptr) -{ - return rna_VertCustomData_data_path(ptr, "skin_vertices", CD_MVERT_SKIN); -} - -static void rna_MeshSkinVertexLayer_name_set(PointerRNA *ptr, const char *value) -{ - CustomDataLayer *layer = static_cast(ptr->data); - CustomData *cdata = rna_mesh_vdata(ptr); - STRNCPY_UTF8(layer->name, value); - CustomData_set_layer_unique_name(cdata, layer - cdata->layers); -} - -static void rna_MeshSkinVertexLayer_data_begin(CollectionPropertyIterator *iter, PointerRNA *ptr) -{ - Mesh *mesh = rna_mesh(ptr); - CustomDataLayer *layer = static_cast(ptr->data); - rna_iterator_array_begin(iter, - ptr, - layer->data, - sizeof(MVertSkin), - (mesh->runtime->edit_mesh) ? 0 : mesh->verts_num, - 0, - nullptr); -} - -static int rna_MeshSkinVertexLayer_data_length(PointerRNA *ptr) -{ - Mesh *mesh = rna_mesh(ptr); - return (mesh->runtime->edit_mesh) ? 0 : mesh->verts_num; -} - -/* End skin vertices */ - /* poly.vertices - this is faked loop access for convenience */ static int rna_MeshPoly_vertices_get_length(const PointerRNA *ptr, int length[RNA_MAX_ARRAY_DIMENSION]) @@ -1496,30 +1432,6 @@ static std::optional rna_MeshVertex_path(const PointerRNA *ptr) return fmt::format("vertices[{}]", rna_MeshVertex_index_get(const_cast(ptr))); } -static std::optional rna_VertCustomData_data_path(const PointerRNA *ptr, - const char *collection, - int type) -{ - const CustomDataLayer *cdl; - const Mesh *mesh = rna_mesh(ptr); - const CustomData *vdata = rna_mesh_vdata(ptr); - int a, b, totvert = (mesh->runtime->edit_mesh) ? 0 : mesh->verts_num; - - for (cdl = vdata->layers, a = 0; a < vdata->totlayer; cdl++, a++) { - if (cdl->type == type) { - b = (static_cast(ptr->data) - (static_cast(cdl->data))) / - CustomData_sizeof(eCustomDataType(type)); - if (b >= 0 && b < totvert) { - char name_esc[sizeof(cdl->name) * 2]; - BLI_str_escape(name_esc, cdl->name, sizeof(name_esc)); - return fmt::format("{}[\"{}\"].data[{}]", collection, name_esc, b); - } - } - } - - return std::nullopt; -} - struct AttrReverseLookup { StringRefNull name; int index_in_group; @@ -1832,6 +1744,117 @@ static void rna_MeshUVLoop_uv_set(PointerRNA *ptr, const float *value) copy_v2_v2(static_cast(ptr->data), value); } +static std::optional rna_MeshSkinVertexLayer_path(const PointerRNA * /*ptr*/) +{ + /* Strange name-based path to replicate legacy behavior. */ + return "skin_vertices[\"\"]"; +} + +static std::optional vert_index_from_skin_data_ptr(const PointerRNA *ptr) +{ + const Mesh *mesh = rna_mesh(ptr); + const bke::AttributeReader radius = mesh->attributes().lookup( + "skin_modifier_radius", bke::AttrDomain::Point); + if (!radius) { + return std::nullopt; + } + if (!radius.varray.is_span()) { + return std::nullopt; + } + const Span data = radius.varray.get_internal_span(); + const int64_t index = static_cast(ptr->data) - data.data(); + if (index < 0 || index >= data.size()) { + return std::nullopt; + } + return int(index); +} + +static std::optional rna_MeshSkinVertex_path(const PointerRNA *ptr) +{ + const std::optional index = vert_index_from_skin_data_ptr(ptr); + if (!index) { + return std::nullopt; + } + return fmt::format("skin_vertices[\"skin_modifier_radius\"].data[{}]", *index); +} + +static void rna_MeshSkinVertex_radius_get(PointerRNA *ptr, float *value) +{ + copy_v2_v2(value, static_cast(ptr->data)); +} + +static void rna_MeshSkinVertex_radius_set(PointerRNA *ptr, const float *value) +{ + copy_v2_v2(static_cast(ptr->data), value); +} + +static bool rna_MeshSkinVertex_use_root_get(PointerRNA *ptr) +{ + const Mesh *mesh = rna_mesh(ptr); + if (const std::optional index = vert_index_from_skin_data_ptr(ptr)) { + const VArray root = *mesh->attributes().lookup_or_default( + "skin_modifier_root", bke::AttrDomain::Point, false); + return root[*index]; + } + return false; +} + +static void rna_MeshSkinVertex_use_root_set(PointerRNA *ptr, const bool value) +{ + Mesh *mesh = rna_mesh(ptr); + if (const std::optional index = vert_index_from_skin_data_ptr(ptr)) { + bke::AttributeWriter root = mesh->attributes_for_write().lookup_or_add_for_write( + "skin_modifier_root", bke::AttrDomain::Point); + root.varray.set(*index, value); + root.finish(); + } +} + +static bool rna_MeshSkinVertex_use_loose_get(PointerRNA *ptr) +{ + const Mesh *mesh = rna_mesh(ptr); + if (const std::optional index = vert_index_from_skin_data_ptr(ptr)) { + const VArray loose = *mesh->attributes().lookup_or_default( + "skin_modifier_loose", bke::AttrDomain::Point, false); + return loose[*index]; + } + return false; +} + +static void rna_MeshSkinVertex_use_loose_set(PointerRNA *ptr, const bool value) +{ + Mesh *mesh = rna_mesh(ptr); + if (const std::optional index = vert_index_from_skin_data_ptr(ptr)) { + bke::AttributeWriter loose = mesh->attributes_for_write().lookup_or_add_for_write( + "skin_modifier_loose", bke::AttrDomain::Point); + loose.varray.set(*index, value); + loose.finish(); + } +} + +static bool rna_MeshSkinVertexLayer_data_lookup_int(PointerRNA *ptr, int index, PointerRNA *r_ptr) +{ + CollectionPropertyIterator iter; + rna_Attribute_data_begin(&iter, ptr); + if (!iter.valid) { + *r_ptr = PointerRNA_NULL; + return false; + } + + ArrayIterator *internal = &iter.internal.array; + if (index < 0 || index >= internal->length) { + *r_ptr = PointerRNA_NULL; + return false; + } + + internal->ptr += internal->itemsize * index; + + *r_ptr = RNA_pointer_create_with_parent( + iter.parent, RNA_MeshSkinVertex, rna_iterator_array_get(&iter)); + rna_iterator_array_end(&iter); + return true; +} + static std::optional rna_MeshLoopColorLayer_path(const PointerRNA *ptr) { return fmt::format("vertex_colors[\"{}\"]", BLI_str_escape(rna_Attribute_name_get(*ptr))); @@ -2831,6 +2854,7 @@ static void rna_def_uv_layers(BlenderRNA *brna, PropertyRNA *cprop) RNA_def_property_update(prop, 0, "rna_Mesh_update_data_legacy_deg_tag_all"); } +/** TODO: Remove API in 6.0. */ static void rna_def_skin_vertices(BlenderRNA *brna, PropertyRNA * /*cprop*/) { StructRNA *srna; @@ -2839,12 +2863,12 @@ static void rna_def_skin_vertices(BlenderRNA *brna, PropertyRNA * /*cprop*/) srna = RNA_def_struct(brna, "MeshSkinVertexLayer", nullptr); RNA_def_struct_ui_text( srna, "Mesh Skin Vertex Layer", "Per-vertex skin data for use with the Skin modifier"); - RNA_def_struct_sdna(srna, "CustomDataLayer"); RNA_def_struct_path_func(srna, "rna_MeshSkinVertexLayer_path"); prop = RNA_def_property(srna, "name", PROP_STRING, PROP_NONE); RNA_def_struct_name_property(srna, prop); - RNA_def_property_string_funcs(prop, nullptr, nullptr, "rna_MeshSkinVertexLayer_name_set"); + RNA_def_property_string_funcs( + prop, "rna_Attribute_name_get", "rna_Attribute_name_length", "rna_Attribute_name_set"); RNA_def_property_string_maxlength(prop, MAX_CUSTOMDATA_LAYER_NAME_NO_PREFIX); RNA_def_property_ui_text(prop, "Name", "Name of skin layer"); RNA_def_property_update(prop, 0, "rna_Mesh_update_data_legacy_deg_tag_all"); @@ -2854,24 +2878,25 @@ static void rna_def_skin_vertices(BlenderRNA *brna, PropertyRNA * /*cprop*/) RNA_def_property_ui_text(prop, "Data", ""); RNA_def_property_override_flag(prop, PROPOVERRIDE_IGNORE); RNA_def_property_collection_funcs(prop, - "rna_MeshSkinVertexLayer_data_begin", + "rna_Attribute_data_begin", "rna_iterator_array_next", "rna_iterator_array_end", "rna_iterator_array_get", - "rna_MeshSkinVertexLayer_data_length", - nullptr, + "rna_Attribute_data_length", + "rna_MeshSkinVertexLayer_data_lookup_int", nullptr, nullptr); /* SkinVertex struct */ srna = RNA_def_struct(brna, "MeshSkinVertex", nullptr); - RNA_def_struct_sdna(srna, "MVertSkin"); RNA_def_struct_ui_text( srna, "Skin Vertex", "Per-vertex skin data for use with the Skin modifier"); RNA_def_struct_path_func(srna, "rna_MeshSkinVertex_path"); prop = RNA_def_property(srna, "radius", PROP_FLOAT, PROP_UNSIGNED); RNA_def_property_array(prop, 2); + RNA_def_property_float_funcs( + prop, "rna_MeshSkinVertex_radius_get", "rna_MeshSkinVertex_radius_set", nullptr); RNA_def_property_ui_range(prop, 0.001, 100.0, 1, 3); RNA_def_property_ui_text(prop, "Radius", "Radius of the skin"); RNA_def_property_update(prop, 0, "rna_Mesh_update_data_legacy_deg_tag_all"); @@ -2879,7 +2904,8 @@ static void rna_def_skin_vertices(BlenderRNA *brna, PropertyRNA * /*cprop*/) /* Flags */ prop = RNA_def_property(srna, "use_root", PROP_BOOLEAN, PROP_NONE); - RNA_def_property_boolean_sdna(prop, nullptr, "flag", MVERT_SKIN_ROOT); + RNA_def_property_boolean_funcs( + prop, "rna_MeshSkinVertex_use_root_get", "rna_MeshSkinVertex_use_root_set"); RNA_def_property_ui_text(prop, "Root", "Vertex is a root for rotation calculations and armature generation, " @@ -2887,7 +2913,8 @@ static void rna_def_skin_vertices(BlenderRNA *brna, PropertyRNA * /*cprop*/) RNA_def_property_update(prop, 0, "rna_Mesh_update_data_legacy_deg_tag_all"); prop = RNA_def_property(srna, "use_loose", PROP_BOOLEAN, PROP_NONE); - RNA_def_property_boolean_sdna(prop, nullptr, "flag", MVERT_SKIN_LOOSE); + RNA_def_property_boolean_funcs( + prop, "rna_MeshSkinVertex_use_loose_get", "rna_MeshSkinVertex_use_loose_set"); RNA_def_property_ui_text( prop, "Loose", "If vertex has multiple adjacent edges, it is hulled to them directly"); RNA_def_property_update(prop, 0, "rna_Mesh_update_data_legacy_deg_tag_all"); @@ -3157,12 +3184,11 @@ static void rna_def_mesh(BlenderRNA *brna) /* Skin vertices */ prop = RNA_def_property(srna, "skin_vertices", PROP_COLLECTION, PROP_NONE); - RNA_def_property_collection_sdna(prop, nullptr, "vert_data.layers", "vert_data.totlayer"); RNA_def_property_collection_funcs(prop, "rna_Mesh_skin_vertices_begin", - nullptr, - nullptr, - nullptr, + "rna_iterator_array_next", + "rna_iterator_array_end", + "rna_Mesh_skin_vertices_iterator_get", "rna_Mesh_skin_vertices_length", nullptr, nullptr, diff --git a/source/blender/modifiers/intern/MOD_skin.cc b/source/blender/modifiers/intern/MOD_skin.cc index d8a92270774..b7d52225dc0 100644 --- a/source/blender/modifiers/intern/MOD_skin.cc +++ b/source/blender/modifiers/intern/MOD_skin.cc @@ -534,11 +534,11 @@ static float half_v2(const float v[2]) static void end_node_frames(int v, SkinNode *skin_nodes, const Span vert_positions, - const MVertSkin *nodes, + const VArray &radius, GroupedSpan emap, EMat *emat) { - const float *rad = nodes[v].radius; + const float2 rad = radius[v]; float mat[3][3]; if (emap[v].is_empty()) { @@ -619,11 +619,11 @@ static int connection_node_mat(float mat[3][3], int v, GroupedSpan emap, EM static void connection_node_frames(int v, SkinNode *skin_nodes, const Span vert_positions, - const MVertSkin *nodes, + const VArray &radius, GroupedSpan emap, EMat *emat) { - const float *rad = nodes[v].radius; + const float2 rad = radius[v]; float mat[3][3]; EMat *e1, *e2; @@ -663,7 +663,7 @@ static void connection_node_frames(int v, static SkinNode *build_frames(const Span vert_positions, int verts_num, - const MVertSkin *nodes, + const VArray &radius, GroupedSpan emap, EMat *emat) { @@ -673,10 +673,10 @@ static SkinNode *build_frames(const Span vert_positions, for (v = 0; v < verts_num; v++) { if (emap[v].size() <= 1) { - end_node_frames(v, skin_nodes, vert_positions, nodes, emap, emat); + end_node_frames(v, skin_nodes, vert_positions, radius, emap, emat); } else if (emap[v].size() == 2) { - connection_node_frames(v, skin_nodes, vert_positions, nodes, emap, emat); + connection_node_frames(v, skin_nodes, vert_positions, radius, emap, emat); } else { /* Branch node generates no frames */ @@ -732,7 +732,7 @@ static void build_emats_stack(BLI_Stack *stack, EMat *emat, GroupedSpan emap, const Span edges, - const MVertSkin *vs, + const VArray &is_root, const Span vert_positions) { EdgeStackElem stack_elem; @@ -753,7 +753,7 @@ static void build_emats_stack(BLI_Stack *stack, /* Process edge */ - parent_is_branch = ((emap[parent_v].size() > 2) || (vs[parent_v].flag & MVERT_SKIN_ROOT)); + parent_is_branch = ((emap[parent_v].size() > 2) || is_root[parent_v]); v = bke::mesh::edge_other_vert(edges[e], parent_v); emat[e].origin = parent_v; @@ -783,7 +783,7 @@ static void build_emats_stack(BLI_Stack *stack, } } -static EMat *build_edge_mats(const MVertSkin *vs, +static EMat *build_edge_mats(const VArray &is_root, const Span vert_positions, const int verts_num, const Span edges, @@ -804,7 +804,7 @@ static EMat *build_edge_mats(const MVertSkin *vs, /* Edge matrices are built from the root nodes, add all roots with * children to the stack */ for (v = 0; v < verts_num; v++) { - if (vs[v].flag & MVERT_SKIN_ROOT) { + if (is_root[v]) { if (emap[v].size() >= 1) { const int2 &edge = edges[emap[v][0]]; calc_edge_mat(stack_elem.mat, @@ -829,7 +829,7 @@ static EMat *build_edge_mats(const MVertSkin *vs, } while (!BLI_stack_is_empty(stack)) { - build_emats_stack(stack, visited_e, emat, emap, edges, vs, vert_positions); + build_emats_stack(stack, visited_e, emat, emap, edges, is_root, vert_positions); } MEM_delete(visited_e); @@ -850,14 +850,14 @@ static EMat *build_edge_mats(const MVertSkin *vs, * having any special cases for dealing with sharing a frame between * two hulls.) */ static int calc_edge_subdivisions(const Span vert_positions, - const MVertSkin *nodes, + const VArray &radius, + const VArray &is_loose, const int2 &edge, const Span degree) { /* prevent memory errors #38003. */ #define NUM_SUBDIVISIONS_MAX 128 - const MVertSkin *evs[2] = {&nodes[edge[0]], &nodes[edge[1]]}; float avg_radius; const bool v1_branch = degree[edge[0]] > 2; const bool v2_branch = degree[edge[1]] > 2; @@ -865,9 +865,7 @@ static int calc_edge_subdivisions(const Span vert_positions, /* If either end is a branch node marked 'loose', don't subdivide * the edge (or subdivide just twice if both are branches) */ - if ((v1_branch && (evs[0]->flag & MVERT_SKIN_LOOSE)) || - (v2_branch && (evs[1]->flag & MVERT_SKIN_LOOSE))) - { + if ((v1_branch && is_loose[edge[0]]) || (v2_branch && is_loose[edge[1]])) { if (v1_branch && v2_branch) { return 2; } @@ -875,7 +873,7 @@ static int calc_edge_subdivisions(const Span vert_positions, return 0; } - avg_radius = half_v2(evs[0]->radius) + half_v2(evs[1]->radius); + avg_radius = half_v2(radius[edge[0]]) + half_v2(radius[edge[1]]); if (avg_radius != 0.0f) { /* possible (but unlikely) that we overflow INT_MAX */ @@ -912,8 +910,11 @@ static Mesh *subdivide_base(const Mesh *orig) int i, j, k, u, v; float radrat; - const MVertSkin *orignode = static_cast( - CustomData_get_layer(&orig->vert_data, CD_MVERT_SKIN)); + const bke::AttributeAccessor orig_attributes = orig->attributes(); + const VArray orig_radius = *orig_attributes.lookup_or_default( + "skin_modifier_radius", bke::AttrDomain::Point, float2(0.25f)); + const VArray orig_loose = *orig_attributes.lookup_or_default( + "skin_modifier_loose", bke::AttrDomain::Point, false); const Span orig_vert_positions = orig->vert_positions(); const Span orig_edges = orig->edges(); const MDeformVert *origdvert = orig->deform_verts().data(); @@ -927,7 +928,8 @@ static Mesh *subdivide_base(const Mesh *orig) /* Per edge, store how many subdivisions are needed */ Array edge_subd(orig_edge_num, 0); for (i = 0, subd_num = 0; i < orig_edge_num; i++) { - edge_subd[i] += calc_edge_subdivisions(orig_vert_positions, orignode, orig_edges[i], degree); + edge_subd[i] += calc_edge_subdivisions( + orig_vert_positions, orig_radius, orig_loose, orig_edges[i], degree); BLI_assert(edge_subd[i] >= 0); subd_num += edge_subd[i]; } @@ -938,8 +940,8 @@ static Mesh *subdivide_base(const Mesh *orig) MutableSpan out_vert_positions = result->vert_positions_for_write(); MutableSpan result_edges = result->edges_for_write(); - MVertSkin *outnode = static_cast( - CustomData_get_layer_for_write(&result->vert_data, CD_MVERT_SKIN, result->verts_num)); + bke::SpanAttributeWriter outnode = + result->attributes_for_write().lookup_for_write_span("skin_modifier_radius"); MDeformVert *outdvert = nullptr; if (origdvert) { outdvert = result->deform_verts_for_write().data(); @@ -987,7 +989,7 @@ static Mesh *subdivide_base(const Mesh *orig) } u = edge[0]; - radrat = (half_v2(outnode[edge[1]].radius) / half_v2(outnode[edge[0]].radius)); + radrat = (half_v2(outnode.span[edge[1]]) / half_v2(outnode.span[edge[0]])); if (isfinite(radrat)) { radrat = (radrat + 1) / 2; } @@ -1006,7 +1008,7 @@ static Mesh *subdivide_base(const Mesh *orig) out_vert_positions[v], out_vert_positions[edge[0]], out_vert_positions[edge[1]], t); /* Interpolate skin radii */ - interp_v3_v3v3(outnode[v].radius, orignode[edge[0]].radius, orignode[edge[1]].radius, t); + interp_v2_v2v2(outnode.span[v], orig_radius[edge[0]], orig_radius[edge[1]], t); /* Interpolate vertex group weights */ for (k = 0; k < vgroups_num; k++) { @@ -1036,6 +1038,8 @@ static Mesh *subdivide_base(const Mesh *orig) result_edge_i++; } + outnode.finish(); + return result; } @@ -1997,8 +2001,11 @@ static Mesh *base_skin(Mesh *origmesh, SkinModifierData *smd, eSkinErrorFlag *r_ SkinNode *skin_nodes; bool has_valid_root = false; - const MVertSkin *nodes = static_cast( - CustomData_get_layer(&origmesh->vert_data, CD_MVERT_SKIN)); + const bke::AttributeAccessor attributes = origmesh->attributes(); + const VArray radius = *attributes.lookup_or_default( + "skin_modifier_radius", bke::AttrDomain::Point, float2(0.25f)); + const VArray is_root = *attributes.lookup_or_default( + "skin_modifier_root", bke::AttrDomain::Point, false); const Span vert_positions = origmesh->vert_positions(); const Span edges = origmesh->edges(); @@ -2010,8 +2017,8 @@ static Mesh *base_skin(Mesh *origmesh, SkinModifierData *smd, eSkinErrorFlag *r_ const GroupedSpan vert_to_edge = bke::mesh::build_vert_to_edge_map( edges, verts_num, vert_to_edge_offsets, vert_to_edge_indices); - emat = build_edge_mats(nodes, vert_positions, verts_num, edges, vert_to_edge, &has_valid_root); - skin_nodes = build_frames(vert_positions, verts_num, nodes, vert_to_edge, emat); + emat = build_edge_mats(is_root, vert_positions, verts_num, edges, vert_to_edge, &has_valid_root); + skin_nodes = build_frames(vert_positions, verts_num, radius, vert_to_edge, emat); MEM_delete(emat); emat = nullptr; @@ -2039,8 +2046,8 @@ static Mesh *final_skin(SkinModifierData *smd, Mesh *mesh, eSkinErrorFlag *r_err { Mesh *result; - /* Skin node layer is required */ - if (!CustomData_get_layer(&mesh->vert_data, CD_MVERT_SKIN)) { + /* Skin vertex radius attribute is required. */ + if (!mesh->attributes().contains("skin_modifier_radius")) { return mesh; } @@ -2092,7 +2099,7 @@ static Mesh *modify_mesh(ModifierData *md, const ModifierEvalContext *ctx, Mesh static void required_data_mask(ModifierData * /*md*/, CustomData_MeshMasks *r_cddata_masks) { - r_cddata_masks->vmask |= CD_MASK_MVERT_SKIN | CD_MASK_MDEFORMVERT; + r_cddata_masks->vmask |= CD_MASK_MDEFORMVERT; } static void panel_draw(const bContext * /*C*/, Panel *panel) diff --git a/source/blender/nodes/geometry/nodes/node_geo_extrude_mesh.cc b/source/blender/nodes/geometry/nodes/node_geo_extrude_mesh.cc index 285c6a62cb0..eb1f807f08b 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_extrude_mesh.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_extrude_mesh.cc @@ -119,7 +119,6 @@ static void remove_unsupported_vert_data(Mesh &mesh) CustomData_free_layers(&mesh.vert_data, CD_ORCO); CustomData_free_layers(&mesh.vert_data, CD_SHAPEKEY); CustomData_free_layers(&mesh.vert_data, CD_CLOTH_ORCO); - CustomData_free_layers(&mesh.vert_data, CD_MVERT_SKIN); } static void remove_unsupported_corner_data(Mesh &mesh) diff --git a/source/blender/python/bmesh/bmesh_py_types_customdata.cc b/source/blender/python/bmesh/bmesh_py_types_customdata.cc index a44a63057cf..e21f8a43089 100644 --- a/source/blender/python/bmesh/bmesh_py_types_customdata.cc +++ b/source/blender/python/bmesh/bmesh_py_types_customdata.cc @@ -32,6 +32,22 @@ namespace blender { +/** + * Sentinel `type` value for #BPy_BMVertSkin, compositing "skin_modifier_radius", + * "skin_modifier_root", "skin_modifier_loose", maintaining legacy API until it can be removed in + * Blender 6.0. + */ +constexpr int BPY_BM_LAYER_TYPE_SKIN = CD_NUMTYPES; + +static const char *bpy_bm_layer_skin_radius_name = "skin_modifier_radius"; +static const char *bpy_bm_layer_skin_root_name = "skin_modifier_root"; +static const char *bpy_bm_layer_skin_loose_name = "skin_modifier_loose"; + +static bool bpy_bm_layer_type_is_skin(const int type) +{ + return type == BPY_BM_LAYER_TYPE_SKIN; +} + static CustomData *bpy_bm_customdata_get(BMesh *bm, char htype) { switch (htype) { @@ -52,8 +68,10 @@ static CustomData *bpy_bm_customdata_get(BMesh *bm, char htype) static CustomDataLayer *bpy_bmlayeritem_get(BPy_BMLayerItem *self) { CustomData *data = bpy_bm_customdata_get(self->bm, self->htype); - const int index_absolute = CustomData_get_layer_index_n( - data, eCustomDataType(self->type), self->index); + const eCustomDataType real_type = bpy_bm_layer_type_is_skin(self->type) ? + CD_PROP_FLOAT2 : + eCustomDataType(self->type); + const int index_absolute = CustomData_get_layer_index_n(data, real_type, self->index); if (index_absolute != -1) { return &data->layers[index_absolute]; } @@ -162,7 +180,12 @@ static PyObject *bpy_bmlayercollection_active_get(BPy_BMLayerItem *self, void * BPY_BM_CHECK_OBJ(self); data = bpy_bm_customdata_get(self->bm, self->htype); - index = CustomData_get_active_layer(data, eCustomDataType(self->type)); /* type relative */ + if (bpy_bm_layer_type_is_skin(self->type)) { + index = CustomData_get_named_layer(data, CD_PROP_FLOAT2, bpy_bm_layer_skin_radius_name); + } + else { + index = CustomData_get_active_layer(data, eCustomDataType(self->type)); /* type relative */ + } if (index != -1) { return BPy_BMLayerItem_CreatePyObject(self->bm, self->htype, self->type, index); @@ -181,6 +204,10 @@ static PyObject *bpy_bmlayercollection_is_singleton_get(BPy_BMLayerItem *self, v { BPY_BM_CHECK_OBJ(self); + if (bpy_bm_layer_type_is_skin(self->type)) { + Py_RETURN_TRUE; + } + return PyBool_FromLong(CustomData_layertype_is_singleton(eCustomDataType(self->type))); } @@ -256,8 +283,7 @@ static PyGetSetDef bpy_bmlayeraccess_vert_getseters[] = { reinterpret_cast(bpy_bmlayeraccess_collection_get), static_cast(nullptr), bpy_bmlayeraccess_collection__skin_doc, - reinterpret_cast(CD_MVERT_SKIN)}, - + reinterpret_cast(BPY_BM_LAYER_TYPE_SKIN)}, {nullptr, nullptr, nullptr, nullptr, nullptr} /* Sentinel */ }; @@ -458,7 +484,10 @@ static PyObject *bpy_bmlayeritem_copy_from(BPy_BMLayerItem *self, BPy_BMLayerIte return nullptr; } - BM_data_layer_copy(self->bm, data, self->type, value->index, self->index); + const eCustomDataType real_type = bpy_bm_layer_type_is_skin(self->type) ? + CD_PROP_FLOAT2 : + eCustomDataType(self->type); + BM_data_layer_copy(self->bm, data, real_type, value->index, self->index); Py_RETURN_NONE; } @@ -482,6 +511,20 @@ static PyObject *bpy_bmlayercollection_verify(BPy_BMLayerCollection *self) data = bpy_bm_customdata_get(self->bm, self->htype); + if (bpy_bm_layer_type_is_skin(self->type)) { + index = CustomData_get_named_layer(data, CD_PROP_FLOAT2, bpy_bm_layer_skin_radius_name); + if (index == -1) { + /* Because adding CustomData layers to a bmesh will invalidate any existing pointers + * in Python objects we can't lazily add the associated boolean layers later on. */ + BM_data_layer_add_named(self->bm, data, CD_PROP_FLOAT2, bpy_bm_layer_skin_radius_name); + BM_data_layer_add_named(self->bm, data, CD_PROP_BOOL, bpy_bm_layer_skin_root_name); + BM_data_layer_add_named(self->bm, data, CD_PROP_BOOL, bpy_bm_layer_skin_loose_name); + index = CustomData_get_named_layer(data, CD_PROP_FLOAT2, bpy_bm_layer_skin_radius_name); + } + BLI_assert(index != -1); + return BPy_BMLayerItem_CreatePyObject(self->bm, self->htype, self->type, index); + } + index = CustomData_get_active_layer(data, eCustomDataType(self->type)); /* type relative */ if (index == -1) { @@ -530,6 +573,20 @@ static PyObject *bpy_bmlayercollection_new(BPy_BMLayerCollection *self, PyObject data = bpy_bm_customdata_get(self->bm, self->htype); + if (bpy_bm_layer_type_is_skin(self->type)) { + if (CustomData_has_layer_named(data, CD_PROP_FLOAT2, bpy_bm_layer_skin_radius_name)) { + PyErr_SetString(PyExc_ValueError, "layers.new(): is a singleton, use verify() instead"); + return nullptr; + } + /* The name is reserved; user-supplied names are not supported for this composite type. */ + BM_data_layer_add_named(self->bm, data, CD_PROP_FLOAT2, bpy_bm_layer_skin_radius_name); + BM_data_layer_add_named(self->bm, data, CD_PROP_BOOL, bpy_bm_layer_skin_root_name); + BM_data_layer_add_named(self->bm, data, CD_PROP_BOOL, bpy_bm_layer_skin_loose_name); + index = CustomData_get_named_layer(data, CD_PROP_FLOAT2, bpy_bm_layer_skin_radius_name); + BLI_assert(index != -1); + return BPy_BMLayerItem_CreatePyObject(self->bm, self->htype, self->type, index); + } + if (CustomData_layertype_is_singleton(eCustomDataType(self->type)) && CustomData_has_layer(data, eCustomDataType(self->type))) { @@ -587,7 +644,14 @@ static PyObject *bpy_bmlayercollection_remove(BPy_BMLayerCollection *self, BPy_B } data = bpy_bm_customdata_get(self->bm, self->htype); - BM_data_layer_free_n(self->bm, data, self->type, value->index); + if (bpy_bm_layer_type_is_skin(self->type)) { + BM_data_layer_free_n(self->bm, data, CD_PROP_FLOAT2, value->index); + BM_data_layer_free_named(self->bm, data, bpy_bm_layer_skin_root_name); + BM_data_layer_free_named(self->bm, data, bpy_bm_layer_skin_loose_name); + } + else { + BM_data_layer_free_n(self->bm, data, self->type, value->index); + } Py_RETURN_NONE; } @@ -614,6 +678,15 @@ static PyObject *bpy_bmlayercollection_keys(BPy_BMLayerCollection *self) data = bpy_bm_customdata_get(self->bm, self->htype); + if (bpy_bm_layer_type_is_skin(self->type)) { + if (!CustomData_has_layer_named(data, CD_PROP_FLOAT2, bpy_bm_layer_skin_radius_name)) { + return PyList_New(0); + } + ret = PyList_New(1); + PyList_SET_ITEM(ret, 0, PyUnicode_FromString(bpy_bm_layer_skin_radius_name)); + return ret; + } + /* Absolute, but no need to make relative. */ index = CustomData_get_layer_index(data, eCustomDataType(self->type)); @@ -650,6 +723,21 @@ static PyObject *bpy_bmlayercollection_items(BPy_BMLayerCollection *self) BPY_BM_CHECK_OBJ(self); data = bpy_bm_customdata_get(self->bm, self->htype); + + if (bpy_bm_layer_type_is_skin(self->type)) { + index = CustomData_get_named_layer(data, CD_PROP_FLOAT2, bpy_bm_layer_skin_radius_name); + if (index == -1) { + return PyList_New(0); + } + ret = PyList_New(1); + item = PyTuple_New(2); + PyTuple_SET_ITEMS(item, + PyUnicode_FromString(bpy_bm_layer_skin_radius_name), + BPy_BMLayerItem_CreatePyObject(self->bm, self->htype, self->type, index)); + PyList_SET_ITEM(ret, 0, item); + return ret; + } + index = CustomData_get_layer_index(data, eCustomDataType(self->type)); tot = (index != -1) ? CustomData_number_of_layers(data, eCustomDataType(self->type)) : 0; @@ -687,6 +775,18 @@ static PyObject *bpy_bmlayercollection_values(BPy_BMLayerCollection *self) BPY_BM_CHECK_OBJ(self); data = bpy_bm_customdata_get(self->bm, self->htype); + + if (bpy_bm_layer_type_is_skin(self->type)) { + index = CustomData_get_named_layer(data, CD_PROP_FLOAT2, bpy_bm_layer_skin_radius_name); + if (index == -1) { + return PyList_New(0); + } + ret = PyList_New(1); + PyList_SET_ITEM( + ret, 0, BPy_BMLayerItem_CreatePyObject(self->bm, self->htype, self->type, index)); + return ret; + } + index = CustomData_get_layer_index(data, eCustomDataType(self->type)); tot = (index != -1) ? CustomData_number_of_layers(data, eCustomDataType(self->type)) : 0; @@ -737,7 +837,14 @@ static PyObject *bpy_bmlayercollection_get(BPy_BMLayerCollection *self, PyObject int index; data = bpy_bm_customdata_get(self->bm, self->htype); - index = CustomData_get_named_layer(data, eCustomDataType(self->type), key); /* type relative */ + if (bpy_bm_layer_type_is_skin(self->type)) { + index = STREQ(key, bpy_bm_layer_skin_radius_name) ? + CustomData_get_named_layer(data, CD_PROP_FLOAT2, key) : + -1; + } + else { + index = CustomData_get_named_layer(data, eCustomDataType(self->type), key); /* type relative */ + } if (index != -1) { return BPy_BMLayerItem_CreatePyObject(self->bm, self->htype, self->type, index); @@ -816,6 +923,10 @@ static Py_ssize_t bpy_bmlayercollection_length(BPy_BMLayerCollection *self) data = bpy_bm_customdata_get(self->bm, self->htype); + if (bpy_bm_layer_type_is_skin(self->type)) { + return CustomData_has_layer_named(data, CD_PROP_FLOAT2, bpy_bm_layer_skin_radius_name) ? 1 : 0; + } + return CustomData_number_of_layers(data, eCustomDataType(self->type)); } @@ -828,8 +939,15 @@ static PyObject *bpy_bmlayercollection_subscript_str(BPy_BMLayerCollection *self BPY_BM_CHECK_OBJ(self); data = bpy_bm_customdata_get(self->bm, self->htype); - index = CustomData_get_named_layer( - data, eCustomDataType(self->type), keyname); /* type relative */ + if (bpy_bm_layer_type_is_skin(self->type)) { + index = STREQ(keyname, bpy_bm_layer_skin_radius_name) ? + CustomData_get_named_layer(data, CD_PROP_FLOAT2, keyname) : + -1; + } + else { + index = CustomData_get_named_layer( + data, eCustomDataType(self->type), keyname); /* type relative */ + } if (index != -1) { return BPy_BMLayerItem_CreatePyObject(self->bm, self->htype, self->type, index); @@ -852,6 +970,13 @@ static PyObject *bpy_bmlayercollection_subscript_int(BPy_BMLayerCollection *self } if (keynum >= 0) { if (keynum < len) { + if (bpy_bm_layer_type_is_skin(self->type)) { + CustomData *data = bpy_bm_customdata_get(self->bm, self->htype); + const int index = CustomData_get_named_layer( + data, CD_PROP_FLOAT2, bpy_bm_layer_skin_radius_name); + BLI_assert(index != -1); + return BPy_BMLayerItem_CreatePyObject(self->bm, self->htype, self->type, index); + } return BPy_BMLayerItem_CreatePyObject(self->bm, self->htype, self->type, keynum); } } @@ -877,9 +1002,7 @@ static PyObject *bpy_bmlayercollection_subscript_slice(BPy_BMLayerCollection *se tuple = PyTuple_New(stop - start); for (count = start; count < stop; count++) { - PyTuple_SET_ITEM(tuple, - count - start, - BPy_BMLayerItem_CreatePyObject(self->bm, self->htype, self->type, count)); + PyTuple_SET_ITEM(tuple, count - start, bpy_bmlayercollection_subscript_int(self, count)); } return tuple; @@ -961,7 +1084,14 @@ static int bpy_bmlayercollection_contains(BPy_BMLayerCollection *self, PyObject } data = bpy_bm_customdata_get(self->bm, self->htype); - index = CustomData_get_named_layer_index(data, eCustomDataType(self->type), keyname); + if (bpy_bm_layer_type_is_skin(self->type)) { + index = STREQ(keyname, bpy_bm_layer_skin_radius_name) ? + CustomData_get_named_layer_index(data, CD_PROP_FLOAT2, keyname) : + -1; + } + else { + index = CustomData_get_named_layer_index(data, eCustomDataType(self->type), keyname); + } return (index != -1) ? 1 : 0; } @@ -1183,8 +1313,10 @@ static void *bpy_bmlayeritem_ptr_get(BPy_BMElem *py_ele, BPy_BMLayerItem *py_lay data = bpy_bm_customdata_get(py_layer->bm, py_layer->htype); - value = CustomData_bmesh_get_n( - data, ele->head.data, eCustomDataType(py_layer->type), py_layer->index); + const eCustomDataType real_type = bpy_bm_layer_type_is_skin(py_layer->type) ? + CD_PROP_FLOAT2 : + eCustomDataType(py_layer->type); + value = CustomData_bmesh_get_n(data, ele->head.data, real_type, py_layer->index); if (value == nullptr) [[unlikely]] { /* this should be fairly unlikely but possible if layers move about after we get them */ @@ -1251,8 +1383,8 @@ PyObject *BPy_BMLayerItem_GetItem(BPy_BMElem *py_ele, BPy_BMLayerItem *py_layer) ret = Vector_CreatePyObject_wrap(static_cast(value), 3, nullptr); break; } - case CD_MVERT_SKIN: { - ret = BPy_BMVertSkin_CreatePyObject(static_cast(value)); + case BPY_BM_LAYER_TYPE_SKIN: { + ret = BPy_BMVertSkin_CreatePyObject(py_ele->bm, reinterpret_cast(py_ele->ele)); break; } default: { @@ -1371,8 +1503,9 @@ int BPy_BMLayerItem_SetItem(BPy_BMElem *py_ele, BPy_BMLayerItem *py_layer, PyObj } break; } - case CD_MVERT_SKIN: { - ret = BPy_BMVertSkin_AssignPyObject(static_cast(value), py_value); + case BPY_BM_LAYER_TYPE_SKIN: { + ret = BPy_BMVertSkin_AssignPyObject( + py_ele->bm, reinterpret_cast(py_ele->ele), py_value); break; } default: { diff --git a/source/blender/python/bmesh/bmesh_py_types_meshdata.cc b/source/blender/python/bmesh/bmesh_py_types_meshdata.cc index c94537d1636..80f05913441 100644 --- a/source/blender/python/bmesh/bmesh_py_types_meshdata.cc +++ b/source/blender/python/bmesh/bmesh_py_types_meshdata.cc @@ -28,6 +28,7 @@ #include "BLI_math_base_c.hh" #include "BLI_math_color_c.hh" #include "BLI_math_vector_c.hh" +#include "BLI_math_vector_types.hh" #include "BLI_utildefines.hh" #include "BKE_deform.hh" @@ -189,13 +190,23 @@ PyObject *BPy_BMLoopUV_CreatePyObject(BMesh *bm, BMLoop *loop, int layer) /* --- End Mesh Loop UV --- */ /* Mesh Vert Skin - * ************ */ + * ************ + * + * The Skin modifier's per-vertex data used to be a single #MVertSkin struct combining the radius + * and root/loose flags, but now consists of a float2 "skin_modifier_radius" attribute and two + * boolean attributes: "skin_modifier_root" and "skin_modifier_loose". For backwards + * compatibility, the original combined struct is emulated as #BPy_BMVertSkin, compositing the + * three correlated attributes, similar to how #BPy_BMLoopUV emulates the old #MLoopUV struct. */ #define BPy_BMVertSkin_Check(v) (Py_TYPE(v) == &BPy_BMVertSkin_Type) struct BPy_BMVertSkin { PyObject_HEAD - MVertSkin *data; + float2 *radius; + /** May be null if the associated layer doesn't exist. */ + bool *root; + /** May be null if the associated layer doesn't exist. */ + bool *loose; }; PyDoc_STRVAR( @@ -206,54 +217,81 @@ PyDoc_STRVAR( ":type: :class:`mathutils.Vector`\n"); static PyObject *bpy_bmvertskin_radius_get(BPy_BMVertSkin *self, void * /*closure*/) { - return Vector_CreatePyObject_wrap(self->data->radius, 2, nullptr); + return Vector_CreatePyObject_wrap(*self->radius, 2, nullptr); } static int bpy_bmvertskin_radius_set(BPy_BMVertSkin *self, PyObject *value, void * /*closure*/) { float tvec[2]; if (mathutils_array_parse(tvec, 2, 2, value, "BMVertSkin.radius") != -1) { - copy_v2_v2(self->data->radius, tvec); + copy_v2_v2(*self->radius, tvec); return 0; } return -1; } +static bool bpy_bmvertskin_flag_ok_or_error(const bool *flag) +{ + if (flag == nullptr) { + PyErr_SetString(PyExc_RuntimeError, "skin layer has no associated root/loose layer."); + return false; + } + return true; +} + PyDoc_STRVAR( /* Wrap. */ bpy_bmvertskin_flag__use_root_doc, "Use as root vertex. Setting this flag does not clear other roots in the same mesh island.\n" "\n" ":type: bool\n"); +static PyObject *bpy_bmvertskin_use_root_get(BPy_BMVertSkin *self, void * /*closure*/) +{ + if (!bpy_bmvertskin_flag_ok_or_error(self->root)) [[unlikely]] { + return nullptr; + } + return PyBool_FromLong(*self->root); +} + +static int bpy_bmvertskin_use_root_set(BPy_BMVertSkin *self, PyObject *value, void * /*closure*/) +{ + if (!bpy_bmvertskin_flag_ok_or_error(self->root)) [[unlikely]] { + return -1; + } + const int tmp_val = PyC_Long_AsBool(value); + if (tmp_val == -1) [[unlikely]] { + return -1; + } + *self->root = tmp_val; + return 0; +} + PyDoc_STRVAR( /* Wrap. */ bpy_bmvertskin_flag__use_loose_doc, "Use loose vertex.\n" "\n" ":type: bool\n"); - -static PyObject *bpy_bmvertskin_flag_get(BPy_BMVertSkin *self, void *flag_p) +static PyObject *bpy_bmvertskin_use_loose_get(BPy_BMVertSkin *self, void * /*closure*/) { - const int flag = POINTER_AS_INT(flag_p); - return PyBool_FromLong(self->data->flag & flag); + if (!bpy_bmvertskin_flag_ok_or_error(self->loose)) [[unlikely]] { + return nullptr; + } + return PyBool_FromLong(*self->loose); } -static int bpy_bmvertskin_flag_set(BPy_BMVertSkin *self, PyObject *value, void *flag_p) +static int bpy_bmvertskin_use_loose_set(BPy_BMVertSkin *self, PyObject *value, void * /*closure*/) { - const eMVertSkinFlag flag = eMVertSkinFlag(POINTER_AS_INT(flag_p)); - - switch (PyC_Long_AsBool(value)) { - case true: - self->data->flag |= flag; - return 0; - case false: - self->data->flag &= ~flag; - return 0; - default: - /* error is set */ - return -1; + if (!bpy_bmvertskin_flag_ok_or_error(self->loose)) [[unlikely]] { + return -1; } + const int tmp_val = PyC_Long_AsBool(value); + if (tmp_val == -1) [[unlikely]] { + return -1; + } + *self->loose = tmp_val; + return 0; } static PyGetSetDef bpy_bmvertskin_getseters[] = { @@ -264,20 +302,20 @@ static PyGetSetDef bpy_bmvertskin_getseters[] = { bpy_bmvertskin_radius_doc, nullptr}, {"use_root", - reinterpret_cast(bpy_bmvertskin_flag_get), - reinterpret_cast(bpy_bmvertskin_flag_set), + reinterpret_cast(bpy_bmvertskin_use_root_get), + reinterpret_cast(bpy_bmvertskin_use_root_set), bpy_bmvertskin_flag__use_root_doc, - reinterpret_cast(MVERT_SKIN_ROOT)}, + nullptr}, {"use_loose", - reinterpret_cast(bpy_bmvertskin_flag_get), - reinterpret_cast(bpy_bmvertskin_flag_set), + reinterpret_cast(bpy_bmvertskin_use_loose_get), + reinterpret_cast(bpy_bmvertskin_use_loose_set), bpy_bmvertskin_flag__use_loose_doc, - reinterpret_cast(MVERT_SKIN_LOOSE)}, + nullptr}, {nullptr, nullptr, nullptr, nullptr, nullptr} /* Sentinel */ }; -static PyTypeObject BPy_BMVertSkin_Type; /* bm.loops.layers.skin.active */ +static PyTypeObject BPy_BMVertSkin_Type; /* bm.verts.layers.skin.active */ static void bm_init_types_bmvertskin() { @@ -294,21 +332,56 @@ static void bm_init_types_bmvertskin() PyType_Ready(&BPy_BMVertSkin_Type); } -int BPy_BMVertSkin_AssignPyObject(MVertSkin *mvertskin, PyObject *value) +int BPy_BMVertSkin_AssignPyObject(BMesh *bm, BMVert *vert, PyObject *value) { if (!BPy_BMVertSkin_Check(value)) [[unlikely]] { PyErr_Format(PyExc_TypeError, "expected BMVertSkin, not a %.200s", Py_TYPE(value)->tp_name); return -1; } - *(mvertskin) = *((reinterpret_cast(value))->data); + BPy_BMVertSkin *src = reinterpret_cast(value); + + const int cd_radius_offset = CustomData_get_offset_named( + &bm->vdata, CD_PROP_FLOAT2, "skin_modifier_radius"); + *static_cast(BM_ELEM_CD_GET_VOID_P(vert, cd_radius_offset)) = *src->radius; + + if (src->root) { + const int cd_root_offset = CustomData_get_offset_named( + &bm->vdata, CD_PROP_BOOL, "skin_modifier_root"); + if (cd_root_offset != -1) { + BM_ELEM_CD_SET_BOOL(vert, cd_root_offset, *src->root); + } + } + if (src->loose) { + const int cd_loose_offset = CustomData_get_offset_named( + &bm->vdata, CD_PROP_BOOL, "skin_modifier_loose"); + if (cd_loose_offset != -1) { + BM_ELEM_CD_SET_BOOL(vert, cd_loose_offset, *src->loose); + } + } + return 0; } -PyObject *BPy_BMVertSkin_CreatePyObject(MVertSkin *mvertskin) +PyObject *BPy_BMVertSkin_CreatePyObject(BMesh *bm, BMVert *vert) { BPy_BMVertSkin *self = PyObject_New(BPy_BMVertSkin, &BPy_BMVertSkin_Type); - self->data = mvertskin; + + const int cd_radius_offset = CustomData_get_offset_named( + &bm->vdata, CD_PROP_FLOAT2, "skin_modifier_radius"); + const int cd_root_offset = CustomData_get_offset_named( + &bm->vdata, CD_PROP_BOOL, "skin_modifier_root"); + const int cd_loose_offset = CustomData_get_offset_named( + &bm->vdata, CD_PROP_BOOL, "skin_modifier_loose"); + + self->radius = static_cast(BM_ELEM_CD_GET_VOID_P(vert, cd_radius_offset)); + self->root = cd_root_offset != -1 ? + static_cast(BM_ELEM_CD_GET_VOID_P(vert, cd_root_offset)) : + nullptr; + self->loose = cd_loose_offset != -1 ? + static_cast(BM_ELEM_CD_GET_VOID_P(vert, cd_loose_offset)) : + nullptr; + return reinterpret_cast(self); } diff --git a/source/blender/python/bmesh/bmesh_py_types_meshdata.hh b/source/blender/python/bmesh/bmesh_py_types_meshdata.hh index dca091e884d..44dbfe11d15 100644 --- a/source/blender/python/bmesh/bmesh_py_types_meshdata.hh +++ b/source/blender/python/bmesh/bmesh_py_types_meshdata.hh @@ -26,14 +26,13 @@ struct BPy_BMGenericMeshData { struct MDeformVert; struct MLoopCol; -struct MVertSkin; struct BMesh; [[nodiscard]] int BPy_BMLoopUV_AssignPyObject(struct BMesh *bm, BMLoop *loop, PyObject *value); [[nodiscard]] PyObject *BPy_BMLoopUV_CreatePyObject(struct BMesh *bm, BMLoop *loop, int layer); -[[nodiscard]] int BPy_BMVertSkin_AssignPyObject(struct MVertSkin *mvertskin, PyObject *value); -[[nodiscard]] PyObject *BPy_BMVertSkin_CreatePyObject(struct MVertSkin *mvertskin); +[[nodiscard]] int BPy_BMVertSkin_AssignPyObject(struct BMesh *bm, BMVert *vert, PyObject *value); +[[nodiscard]] PyObject *BPy_BMVertSkin_CreatePyObject(struct BMesh *bm, BMVert *vert); [[nodiscard]] int BPy_BMLoopColor_AssignPyObject(struct MLoopCol *mloopcol, PyObject *value); [[nodiscard]] PyObject *BPy_BMLoopColor_CreatePyObject(struct MLoopCol *mloopcol); diff --git a/tests/python/bl_geometry_attributes.py b/tests/python/bl_geometry_attributes.py index b513fcb293c..53506b73fc1 100644 --- a/tests/python/bl_geometry_attributes.py +++ b/tests/python/bl_geometry_attributes.py @@ -174,6 +174,64 @@ class TestMeshAttributeConvert(MeshObjectTest): self.assertTrue(self.mesh.uv_layers.active.name == "UVA") +class TestMeshSkinVertices(MeshObjectTest): + def test_no_skin_data_by_default(self): + self.assertEqual(len(self.mesh.skin_vertices), 0) + + def test_add_and_clear_skin_data(self): + bpy.ops.mesh.customdata_skin_add() + self.assertEqual(len(self.mesh.skin_vertices), 1) + skin_layer = self.mesh.skin_vertices[0] + self.assertEqual(skin_layer.name, "skin_modifier_radius") + self.assertEqual(len(skin_layer.data), len(self.mesh.vertices)) + + bpy.ops.mesh.customdata_skin_clear() + self.assertEqual(len(self.mesh.skin_vertices), 0) + + def test_skin_vertex_radius(self): + bpy.ops.mesh.customdata_skin_add() + skin_layer = self.mesh.skin_vertices[0] + + # Newly added skin data defaults to a radius of 0.25 on both axes. + vert = skin_layer.data[0] + self.assertAlmostEqual(vert.radius[0], 0.25) + self.assertAlmostEqual(vert.radius[1], 0.25) + + vert.radius = (0.5, 0.75) + self.assertAlmostEqual(skin_layer.data[0].radius[0], 0.5) + self.assertAlmostEqual(skin_layer.data[0].radius[1], 0.75) + + def test_skin_vert_root_and_loose(self): + bpy.ops.mesh.customdata_skin_add() + skin_layer = self.mesh.skin_vertices[0] + + # An arbitrary vertex (the first) is marked as root by default, the rest are not, + # and no vertex is loose by default. + self.assertTrue(skin_layer.data[0].use_root) + self.assertFalse(skin_layer.data[1].use_root) + self.assertFalse(skin_layer.data[0].use_loose) + + skin_layer.data[1].use_root = True + self.assertTrue(skin_layer.data[1].use_root) + skin_layer.data[1].use_root = False + self.assertFalse(skin_layer.data[1].use_root) + + skin_layer.data[2].use_loose = True + self.assertTrue(skin_layer.data[2].use_loose) + skin_layer.data[2].use_loose = False + self.assertFalse(skin_layer.data[2].use_loose) + + def test_skin_vertices_ignores_unrelated_float2_point_attribute(self): + # skin_vertices is identified by name, not just by domain and type, so an unrelated + # float2 point attribute should not be treated as skin data. + self.mesh.attributes.new("my_custom_data", 'FLOAT2', 'POINT') + self.assertEqual(len(self.mesh.skin_vertices), 0) + + bpy.ops.mesh.customdata_skin_add() + self.assertEqual(len(self.mesh.skin_vertices), 1) + self.assertEqual(self.mesh.skin_vertices[0].name, "skin_modifier_radius") + + if __name__ == '__main__': import sys sys.argv = [__file__] + (sys.argv[sys.argv.index("--") + 1:] if "--" in sys.argv else [])