Mesh: Switch from CustomData to AttributeStorage

This commit changes Mesh to use AttributeStorage to store generic
attributes, similar to previous changes for curves and point clouds.
The main benefit is that this matches the generic attribute design
better than CustomData. This is discussed more in #122398.

Most of the necessary changes landed separately over the past year or so
by moving code to use the higher-level attribute API instead of using
CustomData directly. This commits changes the remaining cases where
lower level data access was necessary, and replaces the attribute
provider system.

Resolves #107500

Pull Request: https://projects.blender.org/blender/blender/pulls/152851
This commit is contained in:
Hans Goudey 2026-01-21 17:20:13 +01:00 • committed by Hans Goudey
parent 1797d32181
commit 8adba33029
32 changed files with 1432 additions and 1952 deletions

View file

@ -26,6 +26,7 @@ class AttributeAccessor;
class AttributeStorage;
class MutableAttributeAccessor;
} // namespace bke
struct BMesh;
struct CustomData;
struct CustomDataLayer;
struct ID;
@ -89,27 +90,18 @@ class AttributeOwner {
/**
* Create a new attribute layer.
*/
struct CustomDataLayer *BKE_attribute_new(AttributeOwner &owner,
struct CustomDataLayer *BKE_attribute_new(Mesh &mesh,
BMesh &bm,
StringRef name,
eCustomDataType type,
bke::AttrDomain domain,
struct ReportList *reports);
bool BKE_attribute_remove(AttributeOwner &owner, StringRef name, struct ReportList *reports);
const struct CustomDataLayer *BKE_attribute_search(const AttributeOwner &owner,
StringRef name,
eCustomDataMask type,
AttrDomainMask domain_mask);
struct CustomDataLayer *BKE_attribute_search_for_write(AttributeOwner &owner,
StringRef name,
eCustomDataMask type,
AttrDomainMask domain_mask);
bke::AttrDomain BKE_attribute_domain(const AttributeOwner &owner,
bke::AttrDomain BKE_attribute_domain(const Mesh &mesh,
const BMesh &bm,
const struct CustomDataLayer *layer);
int BKE_attribute_domain_size(const AttributeOwner &owner, int domain);
int BKE_attribute_data_length(AttributeOwner &owner, struct CustomDataLayer *layer);
bool BKE_attribute_required(const AttributeOwner &owner, StringRef name);
bool BKE_attribute_rename(AttributeOwner &owner,
StringRef old_name,

View file

@ -4,6 +4,8 @@
#pragma once
#include "BLI_vector.hh"
#include "DNA_attribute_types.h"
#include "BKE_attribute.h"
@ -37,13 +39,6 @@ std::optional<AttrType> custom_data_type_to_attr_type(eCustomDataType data_type)
*/
std::optional<eCustomDataType> attr_type_to_custom_data_type(AttrType attr_type);
/**
* Move attributes from the #AttributeStorage to the mesh's #CustomData structs. Used for forward
* compatibility: converting newer files written with #AttributeStorage while #CustomData is still
* used at runtime.
*/
void mesh_convert_storage_to_customdata(Mesh &mesh);
/**
* Move generic attributes from #CustomData to #AttributeStorage (not including non-generic layer
* types). Use for versioning old files when the newer #AttributeStorage format is used at runtime.
@ -61,6 +56,8 @@ void grease_pencil_convert_customdata_to_storage(GreasePencil &grease_pencil);
/** Abstraction for copying #CustomData layers and #AttributeStorage attributes. */
class LegacyMeshInterpolator {
Vector<GVArray> attrs_src_;
Vector<GMutableSpan> attrs_dst_;
const CustomData &cd_src_;
CustomData &cd_dst_;

View file

@ -30,7 +30,7 @@ namespace blender {
/* Blender file format version. */
#define BLENDER_FILE_VERSION BLENDER_VERSION
#define BLENDER_FILE_SUBVERSION 18
#define BLENDER_FILE_SUBVERSION 19
/* Minimum Blender version that supports reading file written with the current
* version. Older Blender versions will test this and cancel loading the file, showing a warning to

View file

@ -228,14 +228,6 @@ bool CustomData_bmesh_merge_layout(const CustomData *source,
BMesh *bm,
char htype);
/**
* Remove layers that aren't stored in BMesh or are stored as flags on BMesh.
* The `layers` array of the returned #CustomData must be freed, but may be null.
* Used during conversion of #Mesh data to #BMesh storage format.
*/
CustomData CustomData_shallow_copy_remove_non_bmesh_attributes(const CustomData *src,
eCustomDataMask mask);
/**
* NULL's all members and resets the #CustomData.typemap.
*

View file

@ -540,7 +540,6 @@ set(SRC
nla_private.h
particle_private.h
intern/attribute_access_intern.hh
intern/attribute_storage_access.hh
intern/data_transfer_intern.hh
intern/lib_intern.hh

View file

@ -149,46 +149,17 @@ struct DomainInfo {
int length = 0;
};
static std::array<DomainInfo, ATTR_DOMAIN_NUM> get_domains(const AttributeOwner &owner)
static std::array<DomainInfo, ATTR_DOMAIN_NUM> get_domains(BMesh *bm)
{
std::array<DomainInfo, ATTR_DOMAIN_NUM> info;
switch (owner.type()) {
case AttributeOwnerType::Curves:
case AttributeOwnerType::GreasePencil:
case AttributeOwnerType::GreasePencilDrawing:
case AttributeOwnerType::PointCloud: {
/* This should be implemented with #AttributeStorage instead. */
BLI_assert_unreachable();
break;
}
case AttributeOwnerType::Mesh: {
Mesh *mesh = owner.get_mesh();
if (BMEditMesh *em = mesh->runtime->edit_mesh.get()) {
BMesh *bm = em->bm;
info[int(AttrDomain::Point)].customdata = &bm->vdata;
info[int(AttrDomain::Point)].length = bm->totvert;
info[int(AttrDomain::Edge)].customdata = &bm->edata;
info[int(AttrDomain::Edge)].length = bm->totedge;
info[int(AttrDomain::Corner)].customdata = &bm->ldata;
info[int(AttrDomain::Corner)].length = bm->totloop;
info[int(AttrDomain::Face)].customdata = &bm->pdata;
info[int(AttrDomain::Face)].length = bm->totface;
}
else {
info[int(AttrDomain::Point)].customdata = &mesh->vert_data;
info[int(AttrDomain::Point)].length = mesh->verts_num;
info[int(AttrDomain::Edge)].customdata = &mesh->edge_data;
info[int(AttrDomain::Edge)].length = mesh->edges_num;
info[int(AttrDomain::Corner)].customdata = &mesh->corner_data;
info[int(AttrDomain::Corner)].length = mesh->corners_num;
info[int(AttrDomain::Face)].customdata = &mesh->face_data;
info[int(AttrDomain::Face)].length = mesh->faces_num;
}
break;
}
}
info[int(AttrDomain::Point)].customdata = &bm->vdata;
info[int(AttrDomain::Point)].length = bm->totvert;
info[int(AttrDomain::Edge)].customdata = &bm->edata;
info[int(AttrDomain::Edge)].length = bm->totedge;
info[int(AttrDomain::Corner)].customdata = &bm->ldata;
info[int(AttrDomain::Corner)].length = bm->totloop;
info[int(AttrDomain::Face)].customdata = &bm->pdata;
info[int(AttrDomain::Face)].length = bm->totface;
return info;
}
@ -197,9 +168,21 @@ static bool bke_attribute_rename_if_exists(AttributeOwner &owner,
const StringRef new_name,
ReportList *reports)
{
CustomDataLayer *layer = BKE_attribute_search_for_write(
owner, old_name, CD_MASK_PROP_ALL, ATTR_DOMAIN_MASK_ALL);
if (layer == nullptr) {
const bke::AttributeStorage &storage = *owner.get_storage();
if (!storage.lookup(old_name)) {
return false;
}
return BKE_attribute_rename(owner, old_name, new_name, reports);
}
static bool bke_attribute_rename_if_exists(AttributeOwner &owner,
BMesh &bm,
const StringRef old_name,
const StringRef new_name,
ReportList *reports)
{
BMDataLayerLookup attr = BM_data_layer_lookup(bm, old_name);
if (!attr) {
return false;
}
return BKE_attribute_rename(owner, old_name, new_name, reports);
@ -263,57 +246,54 @@ bool BKE_attribute_rename(AttributeOwner &owner,
if (owner.type() == AttributeOwnerType::Mesh) {
Mesh *mesh = owner.get_mesh();
/* NOTE: Checking if the new name matches the old name only makes sense when the name
* is clamped to its maximum length, otherwise assigning an over-long name multiple times
* will add `.001` suffix unnecessarily. */
{
const int new_name_maxncpy = CustomData_name_maxncpy_calc(new_name);
/* NOTE: A function that performs a clamped comparison without copying would be handy. */
char new_name_clamped[MAX_CUSTOMDATA_LAYER_NAME];
new_name.copy_utf8_truncated(new_name_clamped, new_name_maxncpy);
if (old_name == new_name_clamped) {
if (BMEditMesh *em = mesh->runtime->edit_mesh.get()) {
/* NOTE: Checking if the new name matches the old name only makes sense when the name
* is clamped to its maximum length, otherwise assigning an over-long name multiple times
* will add `.001` suffix unnecessarily. */
{
const int new_name_maxncpy = CustomData_name_maxncpy_calc(new_name);
/* NOTE: A function that performs a clamped comparison without copying would be handy. */
char new_name_clamped[MAX_CUSTOMDATA_LAYER_NAME];
new_name.copy_utf8_truncated(new_name_clamped, new_name_maxncpy);
if (old_name == new_name_clamped) {
return false;
}
}
BMDataLayerLookup attr = BM_data_layer_lookup(*em->bm, old_name);
if (!attr) {
BKE_report(reports, RPT_ERROR, "Attribute is not part of this geometry");
return false;
}
if (!mesh_attribute_valid(*mesh, new_name, attr.domain, attr.type, reports)) {
return false;
}
std::string result_name = BKE_attribute_calc_unique_name(owner, new_name);
if (attr.type == bke::AttrType::Float2) {
/* Rename UV sub-attributes. */
char buffer_src[MAX_CUSTOMDATA_LAYER_NAME];
char buffer_dst[MAX_CUSTOMDATA_LAYER_NAME];
bke_attribute_rename_if_exists(owner,
*em->bm,
BKE_uv_map_pin_name_get(old_name, buffer_src),
BKE_uv_map_pin_name_get(result_name, buffer_dst),
reports);
}
if (old_name == BKE_id_attributes_active_color_name(&mesh->id)) {
BKE_id_attributes_active_color_set(&mesh->id, result_name);
}
if (old_name == BKE_id_attributes_default_color_name(&mesh->id)) {
BKE_id_attributes_default_color_set(&mesh->id, result_name);
}
StringRef(result_name).copy_utf8_truncated(const_cast<CustomDataLayer *>(attr.layer)->name);
return true;
}
CustomDataLayer *layer = BKE_attribute_search_for_write(
owner, old_name, CD_MASK_PROP_ALL, ATTR_DOMAIN_MASK_ALL);
if (layer == nullptr) {
BKE_report(reports, RPT_ERROR, "Attribute is not part of this geometry");
return false;
}
if (!mesh_attribute_valid(*mesh,
new_name,
BKE_attribute_domain(owner, layer),
*bke::custom_data_type_to_attr_type(eCustomDataType(layer->type)),
reports))
{
return false;
}
std::string result_name = BKE_attribute_calc_unique_name(owner, new_name);
if (layer->type == CD_PROP_FLOAT2) {
/* Rename UV sub-attributes. */
char buffer_src[MAX_CUSTOMDATA_LAYER_NAME];
char buffer_dst[MAX_CUSTOMDATA_LAYER_NAME];
bke_attribute_rename_if_exists(owner,
BKE_uv_map_pin_name_get(layer->name, buffer_src),
BKE_uv_map_pin_name_get(result_name, buffer_dst),
reports);
}
if (old_name == BKE_id_attributes_active_color_name(&mesh->id)) {
BKE_id_attributes_active_color_set(&mesh->id, result_name);
}
if (old_name == BKE_id_attributes_default_color_name(&mesh->id)) {
BKE_id_attributes_default_color_set(&mesh->id, result_name);
}
StringRef(result_name).copy_utf8_truncated(layer->name);
return true;
}
bke::AttributeStorage &attributes = *owner.get_storage();
@ -334,55 +314,71 @@ bool BKE_attribute_rename(AttributeOwner &owner,
return false;
}
}
attributes.rename(old_name, new_name);
return true;
}
static bool attribute_name_exists(const AttributeOwner &owner, const StringRef name)
{
const std::array<DomainInfo, ATTR_DOMAIN_NUM> info = get_domains(owner);
for (const int domain : IndexRange(ATTR_DOMAIN_NUM)) {
if (!info[domain].customdata) {
continue;
else if (owner.type() == AttributeOwnerType::Mesh) {
Mesh *mesh = owner.get_mesh();
if (!mesh_attribute_valid(*mesh, new_name, attr->domain(), attr->data_type(), reports)) {
return false;
}
if (attr->data_type() == bke::AttrType::Float2) {
/* Rename UV sub-attributes. */
char buffer_src[MAX_CUSTOMDATA_LAYER_NAME];
char buffer_dst[MAX_CUSTOMDATA_LAYER_NAME];
bke_attribute_rename_if_exists(owner,
BKE_uv_map_pin_name_get(attr->name(), buffer_src),
BKE_uv_map_pin_name_get(new_name, buffer_dst),
reports);
}
const CustomData *cdata = info[domain].customdata;
for (int i = 0; i < cdata->totlayer; i++) {
const CustomDataLayer *layer = cdata->layers + i;
if (layer->name == name) {
return true;
}
if (old_name == BKE_id_attributes_active_color_name(&mesh->id)) {
BKE_id_attributes_active_color_set(&mesh->id, new_name);
}
if (old_name == BKE_id_attributes_default_color_name(&mesh->id)) {
BKE_id_attributes_default_color_set(&mesh->id, new_name);
}
}
return false;
attributes.rename(old_name, new_name);
return true;
}
std::string BKE_attribute_calc_unique_name(const AttributeOwner &owner, const StringRef name)
{
const StringRef name_final = name.is_empty() ? DATA_("Attribute") : name;
if (owner.type() == AttributeOwnerType::Mesh) {
return BLI_uniquename_cb(
[&](const StringRef new_name) { return attribute_name_exists(owner, new_name); },
'.',
name_final);
const Mesh &mesh = *owner.get_mesh();
if (mesh.runtime->edit_mesh) {
Set<StringRef, 8> names;
const auto add_names = [&](const CustomData &data) {
for (const CustomDataLayer &layer : Span(data.layers, data.totlayer)) {
if (CD_TYPE_AS_MASK(eCustomDataType(layer.type)) & CD_MASK_PROP_ALL) {
names.add(layer.name);
}
}
};
const BMesh &bm = *mesh.runtime->edit_mesh->bm;
add_names(bm.vdata);
add_names(bm.edata);
add_names(bm.pdata);
add_names(bm.ldata);
return BLI_uniquename_cb(
[&](const StringRef new_name) { return names.contains(new_name); }, '.', name_final);
}
}
bke::AttributeStorage &storage = *owner.get_storage();
return storage.unique_name_calc(name_final);
}
CustomDataLayer *BKE_attribute_new(AttributeOwner &owner,
CustomDataLayer *BKE_attribute_new(Mesh &mesh,
BMesh &bm,
const StringRef name,
const eCustomDataType type,
const AttrDomain domain,
ReportList *reports)
{
using namespace blender::bke;
const std::array<DomainInfo, ATTR_DOMAIN_NUM> info = get_domains(owner);
BLI_assert(mesh.runtime->edit_mesh->bm == &bm);
const std::array<DomainInfo, ATTR_DOMAIN_NUM> info = get_domains(&bm);
CustomData *customdata = info[int(domain)].customdata;
if (customdata == nullptr) {
@ -390,35 +386,13 @@ CustomDataLayer *BKE_attribute_new(AttributeOwner &owner,
return nullptr;
}
std::string uniquename = BKE_attribute_calc_unique_name(owner, name);
std::string uniquename = BKE_attribute_calc_unique_name(AttributeOwner::from_id(&mesh.id), name);
if (owner.type() == AttributeOwnerType::Mesh) {
Mesh *mesh = owner.get_mesh();
if (BMEditMesh *em = mesh->runtime->edit_mesh.get()) {
if (!mesh_attribute_valid(
*mesh, name, domain, *custom_data_type_to_attr_type(type), reports))
{
return nullptr;
}
BM_data_layer_add_named(em->bm, customdata, type, uniquename.c_str());
const int index = CustomData_get_named_layer_index(customdata, type, uniquename);
return (index == -1) ? nullptr : &(customdata->layers[index]);
}
}
std::optional<MutableAttributeAccessor> attributes = owner.get_accessor();
if (!attributes) {
if (!mesh_attribute_valid(mesh, name, domain, *custom_data_type_to_attr_type(type), reports)) {
return nullptr;
}
attributes->add(
uniquename, domain, *custom_data_type_to_attr_type(type), AttributeInitDefaultValue());
BM_data_layer_add_named(&bm, customdata, type, uniquename.c_str());
const int index = CustomData_get_named_layer_index(customdata, type, uniquename);
if (index == -1) {
BKE_reportf(reports, RPT_WARNING, "Layer '%s' could not be created", uniquename.c_str());
}
return (index == -1) ? nullptr : &(customdata->layers[index]);
}
@ -471,7 +445,7 @@ bool BKE_attribute_remove(AttributeOwner &owner, const StringRef name, ReportLis
if (owner.type() == AttributeOwnerType::Mesh) {
Mesh *mesh = owner.get_mesh();
if (BMEditMesh *em = mesh->runtime->edit_mesh.get()) {
const std::array<DomainInfo, ATTR_DOMAIN_NUM> info = get_domains(owner);
const std::array<DomainInfo, ATTR_DOMAIN_NUM> info = get_domains(em->bm);
for (const int domain : IndexRange(ATTR_DOMAIN_NUM)) {
if (CustomData *data = info[domain].customdata) {
const std::string name_copy = name;
@ -579,87 +553,36 @@ bool BKE_attribute_remove(AttributeOwner &owner, const StringRef name, ReportLis
return attributes->remove(name);
}
const CustomDataLayer *BKE_attribute_search(const AttributeOwner &owner,
const StringRef name,
const eCustomDataMask type_mask,
const AttrDomainMask domain_mask)
{
if (name.is_empty()) {
return nullptr;
}
const std::array<DomainInfo, ATTR_DOMAIN_NUM> info = get_domains(owner);
for (AttrDomain domain = AttrDomain::Point; int(domain) < ATTR_DOMAIN_NUM;
domain = AttrDomain(int(domain) + 1))
{
if (!(domain_mask & ATTR_DOMAIN_AS_MASK(domain))) {
continue;
}
CustomData *customdata = info[int(domain)].customdata;
if (customdata == nullptr) {
continue;
}
for (int i = 0; i < customdata->totlayer; i++) {
CustomDataLayer *layer = &customdata->layers[i];
if ((CD_TYPE_AS_MASK(eCustomDataType(layer->type)) & type_mask) && layer->name == name) {
return layer;
}
}
}
return nullptr;
}
CustomDataLayer *BKE_attribute_search_for_write(AttributeOwner &owner,
const StringRef name,
const eCustomDataMask type_mask,
const AttrDomainMask domain_mask)
{
/* Reuse the implementation of the const version.
* Implicit sharing for the layer's data is handled below. */
CustomDataLayer *layer = const_cast<CustomDataLayer *>(
BKE_attribute_search(owner, name, type_mask, domain_mask));
if (!layer) {
return nullptr;
}
const std::array<DomainInfo, ATTR_DOMAIN_NUM> info = get_domains(owner);
const AttrDomain domain = BKE_attribute_domain(owner, layer);
CustomData_ensure_data_is_mutable(layer, info[int(domain)].length);
return layer;
}
int BKE_attributes_length(const AttributeOwner &owner,
const AttrDomainMask domain_mask,
const eCustomDataMask mask,
const bool include_anonymous)
{
if (owner.type() == AttributeOwnerType::Mesh) {
const std::array<DomainInfo, ATTR_DOMAIN_NUM> info = get_domains(owner);
int length = 0;
for (const int domain : IndexRange(ATTR_DOMAIN_NUM)) {
const CustomData *customdata = info[domain].customdata;
if (customdata == nullptr) {
continue;
}
if (!customdata || !((1 << int(domain)) & domain_mask)) {
continue;
}
for (const CustomDataLayer &layer : Span(customdata->layers, customdata->totlayer)) {
if (!(mask & CD_TYPE_AS_MASK(eCustomDataType(layer.type)))) {
const Mesh &mesh = *owner.get_mesh();
if (BMEditMesh *em = mesh.runtime->edit_mesh.get()) {
const std::array<DomainInfo, ATTR_DOMAIN_NUM> info = get_domains(em->bm);
int length = 0;
for (const int domain : IndexRange(ATTR_DOMAIN_NUM)) {
const CustomData *customdata = info[domain].customdata;
if (customdata == nullptr) {
continue;
}
if (!include_anonymous && bke::attribute_name_is_anonymous(layer.name)) {
if (!customdata || !((1 << int(domain)) & domain_mask)) {
continue;
}
length++;
for (const CustomDataLayer &layer : Span(customdata->layers, customdata->totlayer)) {
if (!(mask & CD_TYPE_AS_MASK(eCustomDataType(layer.type)))) {
continue;
}
if (!include_anonymous && bke::attribute_name_is_anonymous(layer.name)) {
continue;
}
length++;
}
}
return length;
}
return length;
}
const bke::AttributeStorage &storage = *owner.get_storage();
if (include_anonymous && domain_mask == ATTR_DOMAIN_MASK_ALL && mask == CD_MASK_PROP_ALL) {
@ -681,9 +604,11 @@ int BKE_attributes_length(const AttributeOwner &owner,
return length;
}
AttrDomain BKE_attribute_domain(const AttributeOwner &owner, const CustomDataLayer *layer)
AttrDomain BKE_attribute_domain(const Mesh &mesh, const BMesh &bm, const CustomDataLayer *layer)
{
const std::array<DomainInfo, ATTR_DOMAIN_NUM> info = get_domains(owner);
BLI_assert(mesh.runtime->edit_mesh->bm == &bm);
UNUSED_VARS_NDEBUG(mesh);
const std::array<DomainInfo, ATTR_DOMAIN_NUM> info = get_domains(&const_cast<BMesh &>(bm));
for (const int domain : IndexRange(ATTR_DOMAIN_NUM)) {
const CustomData *customdata = info[domain].customdata;
@ -701,36 +626,16 @@ AttrDomain BKE_attribute_domain(const AttributeOwner &owner, const CustomDataLay
int BKE_attribute_domain_size(const AttributeOwner &owner, const int domain)
{
const std::array<DomainInfo, ATTR_DOMAIN_NUM> info = get_domains(owner);
return info[domain].length;
}
int BKE_attribute_data_length(AttributeOwner &owner, CustomDataLayer *layer)
{
/* When in mesh editmode, attributes point to bmesh customdata layers, the attribute data is
* empty since custom data is stored per element instead of a single array there (same es UVs
* etc.), see D11998. */
if (owner.type() == AttributeOwnerType::Mesh) {
Mesh *mesh = owner.get_mesh();
if (mesh->runtime->edit_mesh != nullptr) {
return 0;
}
}
const std::array<DomainInfo, ATTR_DOMAIN_NUM> info = get_domains(owner);
for (const int domain : IndexRange(ATTR_DOMAIN_NUM)) {
const CustomData *customdata = info[domain].customdata;
if (customdata == nullptr) {
continue;
}
if (Span(customdata->layers, customdata->totlayer).contains_ptr(layer)) {
const Mesh &mesh = *owner.get_mesh();
if (BMEditMesh *em = mesh.runtime->edit_mesh.get()) {
const BMesh &bm = *em->bm;
const std::array<DomainInfo, ATTR_DOMAIN_NUM> info = get_domains(&const_cast<BMesh &>(bm));
return info[domain].length;
}
}
BLI_assert_msg(0, "Custom data layer not found in geometry");
return 0;
const bke::AttributeAccessor attributes = *owner.get_accessor();
return attributes.domain_size(bke::AttrDomain(domain));
}
bool BKE_attribute_required(const AttributeOwner &owner, const StringRef name)
@ -758,30 +663,33 @@ std::optional<StringRefNull> BKE_attributes_active_name_get(AttributeOwner &owne
return std::nullopt;
}
if (owner.type() == AttributeOwnerType::Mesh) {
if (active_index > BKE_attributes_length(owner, ATTR_DOMAIN_MASK_ALL, CD_MASK_PROP_ALL)) {
active_index = 0;
}
const std::array<DomainInfo, ATTR_DOMAIN_NUM> info = get_domains(owner);
int index = 0;
for (const int domain : IndexRange(ATTR_DOMAIN_NUM)) {
CustomData *customdata = info[domain].customdata;
if (customdata == nullptr) {
continue;
const Mesh *mesh = owner.get_mesh();
if (BMEditMesh *em = mesh->runtime->edit_mesh.get()) {
if (active_index > BKE_attributes_length(owner, ATTR_DOMAIN_MASK_ALL, CD_MASK_PROP_ALL)) {
active_index = 0;
}
for (int i = 0; i < customdata->totlayer; i++) {
CustomDataLayer *layer = &customdata->layers[i];
if (CD_MASK_PROP_ALL & CD_TYPE_AS_MASK(eCustomDataType(layer->type))) {
if (index == active_index) {
if (bke::allow_procedural_attribute_access(layer->name)) {
return layer->name;
const std::array<DomainInfo, ATTR_DOMAIN_NUM> info = get_domains(em->bm);
int index = 0;
for (const int domain : IndexRange(ATTR_DOMAIN_NUM)) {
CustomData *customdata = info[domain].customdata;
if (customdata == nullptr) {
continue;
}
for (int i = 0; i < customdata->totlayer; i++) {
CustomDataLayer *layer = &customdata->layers[i];
if (CD_MASK_PROP_ALL & CD_TYPE_AS_MASK(eCustomDataType(layer->type))) {
if (index == active_index) {
if (bke::allow_procedural_attribute_access(layer->name)) {
return layer->name;
}
return std::nullopt;
}
return std::nullopt;
index++;
}
index++;
}
}
return std::nullopt;
}
return std::nullopt;
}
bke::AttributeStorage &storage = *owner.get_storage();
@ -794,9 +702,13 @@ std::optional<StringRefNull> BKE_attributes_active_name_get(AttributeOwner &owne
void BKE_attributes_active_set(AttributeOwner &owner, const StringRef name)
{
if (owner.type() == AttributeOwnerType::Mesh) {
const int index = BKE_attribute_to_index(owner, name, ATTR_DOMAIN_MASK_ALL, CD_MASK_PROP_ALL);
*BKE_attributes_active_index_p(owner) = index;
return;
const Mesh *mesh = owner.get_mesh();
if (mesh->runtime->edit_mesh) {
const int index = BKE_attribute_to_index(
owner, name, ATTR_DOMAIN_MASK_ALL, CD_MASK_PROP_ALL);
*BKE_attributes_active_index_p(owner) = index;
return;
}
}
bke::AttributeStorage &attributes = *owner.get_storage();
@ -837,28 +749,32 @@ std::optional<StringRef> BKE_attribute_from_index(AttributeOwner &owner,
const bool include_anonymous)
{
if (owner.type() == AttributeOwnerType::Mesh) {
const std::array<DomainInfo, ATTR_DOMAIN_NUM> info = get_domains(owner);
const Mesh &mesh = *owner.get_mesh();
if (BMEditMesh *em = mesh.runtime->edit_mesh.get()) {
const BMesh &bm = *em->bm;
const std::array<DomainInfo, ATTR_DOMAIN_NUM> info = get_domains(&const_cast<BMesh &>(bm));
int index = 0;
for (const int domain : IndexRange(ATTR_DOMAIN_NUM)) {
CustomData *customdata = info[domain].customdata;
if (!customdata || !((1 << int(domain)) & domain_mask)) {
continue;
}
for (const CustomDataLayer &layer : Span(customdata->layers, customdata->totlayer)) {
if (!(layer_mask & CD_TYPE_AS_MASK(eCustomDataType(layer.type)))) {
int index = 0;
for (const int domain : IndexRange(ATTR_DOMAIN_NUM)) {
CustomData *customdata = info[domain].customdata;
if (!customdata || !((1 << int(domain)) & domain_mask)) {
continue;
}
if (!include_anonymous && bke::attribute_name_is_anonymous(layer.name)) {
continue;
for (const CustomDataLayer &layer : Span(customdata->layers, customdata->totlayer)) {
if (!(layer_mask & CD_TYPE_AS_MASK(eCustomDataType(layer.type)))) {
continue;
}
if (!include_anonymous && bke::attribute_name_is_anonymous(layer.name)) {
continue;
}
if (index == lookup_index) {
return layer.name;
}
index++;
}
if (index == lookup_index) {
return layer.name;
}
index++;
}
return std::nullopt;
}
return std::nullopt;
}
bke::AttributeStorage &storage = *owner.get_storage();
@ -894,28 +810,30 @@ int BKE_attribute_to_index(const AttributeOwner &owner,
const bool include_anonymous)
{
if (owner.type() == AttributeOwnerType::Mesh) {
const std::array<DomainInfo, ATTR_DOMAIN_NUM> info = get_domains(owner);
int index = 0;
for (const int domain : IndexRange(ATTR_DOMAIN_NUM)) {
const CustomData *customdata = info[domain].customdata;
if (!customdata || !((1 << int(domain)) & domain_mask)) {
continue;
}
for (const CustomDataLayer &layer : Span(customdata->layers, customdata->totlayer)) {
if (!(layer_mask & CD_TYPE_AS_MASK(eCustomDataType(layer.type)))) {
const Mesh &mesh = *owner.get_mesh();
if (BMEditMesh *em = mesh.runtime->edit_mesh.get()) {
const std::array<DomainInfo, ATTR_DOMAIN_NUM> info = get_domains(em->bm);
int index = 0;
for (const int domain : IndexRange(ATTR_DOMAIN_NUM)) {
const CustomData *customdata = info[domain].customdata;
if (!customdata || !((1 << int(domain)) & domain_mask)) {
continue;
}
if (!include_anonymous && bke::attribute_name_is_anonymous(layer.name)) {
continue;
for (const CustomDataLayer &layer : Span(customdata->layers, customdata->totlayer)) {
if (!(layer_mask & CD_TYPE_AS_MASK(eCustomDataType(layer.type)))) {
continue;
}
if (!include_anonymous && bke::attribute_name_is_anonymous(layer.name)) {
continue;
}
if (layer.name == name) {
return index;
}
index++;
}
if (layer.name == name) {
return index;
}
index++;
}
return -1;
}
return -1;
}
const bke::AttributeStorage &storage = *owner.get_storage();

View file

@ -27,8 +27,6 @@
#include "FN_field.hh"
#include "attribute_access_intern.hh"
#ifndef NDEBUG
# include <iostream>
#endif
@ -323,341 +321,6 @@ AttrDomain attribute_domain_highest_priority(Span<AttrDomain> domains)
return highest_priority_domain;
}
static void *add_generic_custom_data_layer(CustomData &custom_data,
const eCustomDataType data_type,
const eCDAllocType alloctype,
const int domain_size,
const StringRef attribute_id)
{
return CustomData_add_layer_named(&custom_data, data_type, alloctype, domain_size, attribute_id);
}
static const void *add_generic_custom_data_layer_with_existing_data(
CustomData &custom_data,
const eCustomDataType data_type,
const StringRef attribute_id,
const int domain_size,
void *layer_data,
const ImplicitSharingInfo *sharing_info)
{
return CustomData_add_layer_named_with_data(
&custom_data, data_type, layer_data, domain_size, attribute_id, sharing_info);
}
static bool add_custom_data_layer_from_attribute_init(const StringRef attribute_id,
CustomData &custom_data,
const eCustomDataType data_type,
const int domain_num,
const AttributeInit &initializer,
const GPointer custom_default_value_ptr)
{
const int old_layer_num = custom_data.totlayer;
switch (initializer.type) {
case AttributeInit::Type::Construct: {
add_generic_custom_data_layer(
custom_data, data_type, CD_CONSTRUCT, domain_num, attribute_id);
break;
}
case AttributeInit::Type::DefaultValue: {
if (const void *default_value = custom_default_value_ptr.get()) {
const CPPType &type = *custom_default_value_ptr.type();
void *data = add_generic_custom_data_layer(
custom_data, data_type, CD_CONSTRUCT, domain_num, attribute_id);
type.fill_assign_n(default_value, data, domain_num);
}
else {
add_generic_custom_data_layer(
custom_data, data_type, CD_SET_DEFAULT, domain_num, attribute_id);
}
break;
}
case AttributeInit::Type::VArray: {
void *data = add_generic_custom_data_layer(
custom_data, data_type, CD_CONSTRUCT, domain_num, attribute_id);
if (data != nullptr) {
const GVArray &varray = static_cast<const AttributeInitVArray &>(initializer).varray;
varray.materialize_to_uninitialized(varray.index_range(), data);
}
break;
}
case AttributeInit::Type::MoveArray: {
void *data = static_cast<const AttributeInitMoveArray &>(initializer).data;
add_generic_custom_data_layer_with_existing_data(
custom_data, data_type, attribute_id, domain_num, data, nullptr);
break;
}
case AttributeInit::Type::Shared: {
const AttributeInitShared &init = static_cast<const AttributeInitShared &>(initializer);
add_generic_custom_data_layer_with_existing_data(custom_data,
data_type,
attribute_id,
domain_num,
const_cast<void *>(init.data),
init.sharing_info);
break;
}
}
return old_layer_num < custom_data.totlayer;
}
bool BuiltinCustomDataLayerProvider::layer_exists(const CustomData &custom_data) const
{
return CustomData_get_named_layer_index(&custom_data, data_type_, name_) != -1;
}
GAttributeReader BuiltinCustomDataLayerProvider::try_get_for_read(const void *owner) const
{
const CustomData *custom_data = custom_data_access_.get_const_custom_data(owner);
if (custom_data == nullptr) {
return {};
}
/* When the number of elements is zero, layers might have null data but still exist. */
const CPPType &type = *custom_data_type_to_cpp_type(data_type_);
const int element_num = custom_data_access_.get_element_num(owner);
if (element_num == 0) {
if (this->layer_exists(*custom_data)) {
return {GVArray::from_span({type, nullptr, 0}), domain_, nullptr};
}
return {};
}
const int index = CustomData_get_named_layer_index(custom_data, data_type_, name_);
if (index == -1) {
return {};
}
const CustomDataLayer &layer = custom_data->layers[index];
return {GVArray::from_span({type, layer.data, element_num}), domain_, layer.sharing_info};
}
GAttributeWriter BuiltinCustomDataLayerProvider::try_get_for_write(void *owner) const
{
CustomData *custom_data = custom_data_access_.get_custom_data(owner);
if (custom_data == nullptr) {
return {};
}
std::function<void()> tag_modified_fn;
if (update_on_change_ != nullptr) {
tag_modified_fn = [owner, update = update_on_change_]() { update(owner); };
}
/* When the number of elements is zero, layers might have null data but still exist. */
const CPPType &type = *custom_data_type_to_cpp_type(data_type_);
const int element_num = custom_data_access_.get_element_num(owner);
if (element_num == 0) {
if (this->layer_exists(*custom_data)) {
return {GVMutableArray::from_span({type, nullptr, 0}), domain_, std::move(tag_modified_fn)};
}
return {};
}
void *data = CustomData_get_layer_named_for_write(custom_data, data_type_, name_, element_num);
if (data == nullptr) {
return {};
}
return {
GVMutableArray::from_span({type, data, element_num}), domain_, std::move(tag_modified_fn)};
}
bool BuiltinCustomDataLayerProvider::try_delete(void *owner) const
{
if (deletable_ != Deletable) {
return false;
}
CustomData *custom_data = custom_data_access_.get_custom_data(owner);
if (custom_data == nullptr) {
return {};
}
if (CustomData_free_layer_named(custom_data, name_)) {
if (update_on_change_ != nullptr) {
update_on_change_(owner);
}
return true;
}
return false;
}
bool BuiltinCustomDataLayerProvider::try_create(void *owner,
const AttributeInit &initializer) const
{
CustomData *custom_data = custom_data_access_.get_custom_data(owner);
if (custom_data == nullptr) {
return false;
}
const int element_num = custom_data_access_.get_element_num(owner);
if (CustomData_has_layer_named(custom_data, data_type_, name_)) {
/* Exists already. */
return false;
}
if (add_custom_data_layer_from_attribute_init(
name_, *custom_data, data_type_, element_num, initializer, default_value_))
{
if (initializer.type != AttributeInit::Type::Construct) {
/* Avoid calling update function when values are not default-initialized. Without default
* initialization or otherwise meaningful initial values, they should be set elsewhere
* anyway, which will cause a separate update tag. */
if (update_on_change_ != nullptr) {
update_on_change_(owner);
}
}
return true;
}
return false;
}
bool BuiltinCustomDataLayerProvider::exists(const void *owner) const
{
const CustomData *custom_data = custom_data_access_.get_const_custom_data(owner);
if (custom_data == nullptr) {
return false;
}
return CustomData_has_layer_named(custom_data, data_type_, name_);
}
GAttributeReader CustomDataAttributeProvider::try_get_for_read(const void *owner,
const StringRef attribute_id) const
{
const CustomData *custom_data = custom_data_access_.get_const_custom_data(owner);
if (custom_data == nullptr) {
return {};
}
const int element_num = custom_data_access_.get_element_num(owner);
for (const CustomDataLayer &layer : Span(custom_data->layers, custom_data->totlayer)) {
if (layer.name != attribute_id) {
continue;
}
const CPPType *type = custom_data_type_to_cpp_type(eCustomDataType(layer.type));
if (type == nullptr) {
continue;
}
GSpan data{*type, layer.data, element_num};
return {GVArray::from_span(data), domain_, layer.sharing_info};
}
return {};
}
GAttributeWriter CustomDataAttributeProvider::try_get_for_write(void *owner,
const StringRef attribute_id) const
{
CustomData *custom_data = custom_data_access_.get_custom_data(owner);
if (custom_data == nullptr) {
return {};
}
const int element_num = custom_data_access_.get_element_num(owner);
for (CustomDataLayer &layer : MutableSpan(custom_data->layers, custom_data->totlayer)) {
if (layer.name != attribute_id) {
continue;
}
CustomData_get_layer_named_for_write(
custom_data, eCustomDataType(layer.type), layer.name, element_num);
const CPPType *type = custom_data_type_to_cpp_type(eCustomDataType(layer.type));
if (type == nullptr) {
continue;
}
std::function<void()> tag_modified_fn;
if (custom_data_access_.get_tag_modified_function != nullptr) {
tag_modified_fn = custom_data_access_.get_tag_modified_function(owner, attribute_id);
}
GMutableSpan data{*type, layer.data, element_num};
return {GVMutableArray::from_span(data), domain_, tag_modified_fn};
}
return {};
}
bool CustomDataAttributeProvider::try_delete(void *owner, const StringRef attribute_id) const
{
CustomData *custom_data = custom_data_access_.get_custom_data(owner);
if (custom_data == nullptr) {
return false;
}
for (const int i : IndexRange(custom_data->totlayer)) {
const CustomDataLayer &layer = custom_data->layers[i];
if (this->type_is_supported(eCustomDataType(layer.type)) && layer.name == attribute_id) {
CustomData_free_layer(custom_data, eCustomDataType(layer.type), i);
if (custom_data_access_.get_tag_modified_function != nullptr) {
if (const std::function<void()> fn = custom_data_access_.get_tag_modified_function(
owner, attribute_id))
{
fn();
}
}
return true;
}
}
return false;
}
bool CustomDataAttributeProvider::try_create(void *owner,
const StringRef attribute_id,
const AttrDomain domain,
const eCustomDataType data_type,
const AttributeInit &initializer) const
{
if (domain_ != domain) {
return false;
}
if (!this->type_is_supported(data_type)) {
return false;
}
CustomData *custom_data = custom_data_access_.get_custom_data(owner);
if (custom_data == nullptr) {
return false;
}
for (const CustomDataLayer &layer : Span(custom_data->layers, custom_data->totlayer)) {
if (layer.name == attribute_id) {
return false;
}
}
const int element_num = custom_data_access_.get_element_num(owner);
add_custom_data_layer_from_attribute_init(
attribute_id, *custom_data, data_type, element_num, initializer, {});
if (initializer.type != AttributeInit::Type::Construct) {
/* Avoid calling update function when values are not default-initialized. Without default
* initialization or otherwise meaningful initial values, they should be set elsewhere
* anyway, which will cause a separate update tag. */
if (custom_data_access_.get_tag_modified_function != nullptr) {
if (const std::function<void()> fn = custom_data_access_.get_tag_modified_function(
owner, attribute_id))
{
fn();
}
}
}
return true;
}
bool CustomDataAttributeProvider::foreach_attribute(
const void *owner, const FunctionRef<void(const AttributeIter &)> fn) const
{
const CustomData *custom_data = custom_data_access_.get_const_custom_data(owner);
if (custom_data == nullptr) {
return true;
}
for (const CustomDataLayer &layer : Span(custom_data->layers, custom_data->totlayer)) {
const eCustomDataType cd_type = eCustomDataType(layer.type);
if (this->type_is_supported(cd_type)) {
const auto get_fn = [&]() {
const CPPType *type = custom_data_type_to_cpp_type(cd_type);
BLI_assert(type);
GSpan data{*type, layer.data, custom_data_access_.get_element_num(owner)};
return GAttributeReader{GVArray::from_span(data), domain_, layer.sharing_info};
};
const AttrType data_type = *custom_data_type_to_attr_type(cd_type);
AttributeIter iter{layer.name, domain_, data_type, get_fn};
fn(iter);
if (iter.is_stopped()) {
return false;
}
}
}
return true;
}
/* -------------------------------------------------------------------- */
/** \name Attribute API
* \{ */

View file

@ -1,435 +0,0 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include <functional>
#include "BLI_generic_pointer.hh"
#include "BLI_map.hh"
#include "BLI_span.hh"
#include "BLI_string_ref.hh"
#include "BLI_vector.hh"
#include "BLI_vector_set.hh"
#include "BKE_attribute_legacy_convert.hh"
#include "BKE_geometry_set.hh"
namespace blender::bke {
/**
* Utility to group together multiple functions that are used to access custom data on geometry
* components in a generic way.
*/
struct CustomDataAccessInfo {
using CustomDataGetter = CustomData *(*)(void *owner);
using ConstCustomDataGetter = const CustomData *(*)(const void *owner);
using GetElementNum = int (*)(const void *owner);
using GetTagModifiedFunction = std::function<void()> (*)(void *owner, StringRef name);
CustomDataGetter get_custom_data;
ConstCustomDataGetter get_const_custom_data;
GetElementNum get_element_num;
GetTagModifiedFunction get_tag_modified_function;
};
/**
* A #BuiltinAttributeProvider is responsible for exactly one attribute on a geometry component.
* The attribute is identified by its name and has a fixed domain and type. Builtin attributes do
* not follow the same loose rules as other attributes, because they are mapped to internal
* "legacy" data structures. For example, some builtin attributes cannot be deleted.
*/
class BuiltinAttributeProvider {
public:
enum DeletableEnum {
Deletable,
NonDeletable,
};
protected:
const std::string name_;
const AttrDomain domain_;
const eCustomDataType data_type_;
const DeletableEnum deletable_;
const AttributeValidator validator_;
const GPointer default_value_;
public:
BuiltinAttributeProvider(std::string name,
const AttrDomain domain,
const eCustomDataType data_type,
const DeletableEnum deletable,
AttributeValidator validator = {},
const GPointer default_value = {})
: name_(std::move(name)),
domain_(domain),
data_type_(data_type),
deletable_(deletable),
validator_(validator),
default_value_(default_value)
{
}
virtual GAttributeReader try_get_for_read(const void *owner) const = 0;
virtual GAttributeWriter try_get_for_write(void *owner) const = 0;
virtual bool try_delete(void *owner) const = 0;
virtual bool try_create(void *owner, const AttributeInit &initializer) const = 0;
virtual bool exists(const void *owner) const = 0;
StringRefNull name() const
{
return name_;
}
AttrDomain domain() const
{
return domain_;
}
eCustomDataType data_type() const
{
return data_type_;
}
AttributeValidator validator() const
{
return validator_;
}
GPointer default_value() const
{
return default_value_;
}
};
/**
* A #DynamicAttributesProvider manages a set of named attributes on a geometry component. Each
* attribute has a name, domain and type.
*/
class DynamicAttributesProvider {
public:
virtual GAttributeReader try_get_for_read(const void *owner, StringRef attribute_id) const = 0;
virtual GAttributeWriter try_get_for_write(void *owner, StringRef attribute_id) const = 0;
virtual bool try_delete(void *owner, StringRef attribute_id) const = 0;
virtual bool try_create(void *owner,
const StringRef attribute_id,
const AttrDomain domain,
const eCustomDataType data_type,
const AttributeInit &initializer) const
{
UNUSED_VARS(owner, attribute_id, domain, data_type, initializer);
/* Some providers should not create new attributes. */
return false;
};
/**
* Return false when the iteration was stopped.
*/
virtual bool foreach_attribute(const void *owner,
FunctionRef<void(const AttributeIter &)> fn) const = 0;
virtual void foreach_domain(const FunctionRef<void(AttrDomain)> callback) const = 0;
};
/**
* This is the attribute provider for most user generated attributes.
*/
class CustomDataAttributeProvider final : public DynamicAttributesProvider {
private:
static constexpr uint64_t supported_types_mask = CD_MASK_PROP_ALL;
AttrDomain domain_;
CustomDataAccessInfo custom_data_access_;
public:
CustomDataAttributeProvider(const AttrDomain domain,
const CustomDataAccessInfo custom_data_access)
: domain_(domain), custom_data_access_(custom_data_access)
{
}
GAttributeReader try_get_for_read(const void *owner, StringRef attribute_id) const final;
GAttributeWriter try_get_for_write(void *owner, StringRef attribute_id) const final;
bool try_delete(void *owner, StringRef attribute_id) const final;
bool try_create(void *owner,
StringRef attribute_id,
AttrDomain domain,
const eCustomDataType data_type,
const AttributeInit &initializer) const final;
bool foreach_attribute(const void *owner,
FunctionRef<void(const AttributeIter &)> fn) const final;
void foreach_domain(const FunctionRef<void(AttrDomain)> callback) const final
{
callback(domain_);
}
private:
bool type_is_supported(eCustomDataType data_type) const
{
return ((1ULL << data_type) & supported_types_mask) != 0;
}
};
/**
* This provider is used to provide access to builtin attributes. It supports making internal types
* available as different types.
*
* It also supports named builtin attributes, and will look up attributes in #CustomData by name
* if the stored type is the same as the attribute type.
*/
class BuiltinCustomDataLayerProvider final : public BuiltinAttributeProvider {
using AttrUpdateOnChange = void (*)(void *owner);
const CustomDataAccessInfo custom_data_access_;
const AttrUpdateOnChange update_on_change_;
public:
BuiltinCustomDataLayerProvider(std::string attribute_name,
const AttrDomain domain,
const eCustomDataType data_type,
const DeletableEnum deletable,
const CustomDataAccessInfo custom_data_access,
const AttrUpdateOnChange update_on_change,
const AttributeValidator validator = {},
const GPointer default_value = {})
: BuiltinAttributeProvider(
std::move(attribute_name), domain, data_type, deletable, validator, default_value),
custom_data_access_(custom_data_access),
update_on_change_(update_on_change)
{
}
GAttributeReader try_get_for_read(const void *owner) const final;
GAttributeWriter try_get_for_write(void *owner) const final;
bool try_delete(void *owner) const final;
bool try_create(void *owner, const AttributeInit &initializer) const final;
bool exists(const void *owner) const final;
private:
bool layer_exists(const CustomData &custom_data) const;
};
/**
* This is a container for multiple attribute providers that are used by one geometry type
* (e.g. there is a set of attribute providers for mesh components).
*/
class GeometryAttributeProviders {
private:
/**
* Builtin attribute providers are identified by their name. Attribute names that are in this
* map will only be accessed using builtin attribute providers. Therefore, these providers have
* higher priority when an attribute name is looked up. Usually, that means that builtin
* providers are checked before dynamic ones.
*/
Map<std::string, const BuiltinAttributeProvider *> builtin_attribute_providers_;
/**
* An ordered list of dynamic attribute providers. The order is important because that is order
* in which they are checked when an attribute is looked up.
*/
Vector<const DynamicAttributesProvider *> dynamic_attribute_providers_;
/**
* All the domains that are supported by at least one of the providers above.
*/
VectorSet<AttrDomain> supported_domains_;
public:
GeometryAttributeProviders(Span<const BuiltinAttributeProvider *> builtin_attribute_providers,
Span<const DynamicAttributesProvider *> dynamic_attribute_providers)
: dynamic_attribute_providers_(dynamic_attribute_providers)
{
for (const BuiltinAttributeProvider *provider : builtin_attribute_providers) {
/* Use #add_new to make sure that no two builtin attributes have the same name. */
builtin_attribute_providers_.add_new(provider->name(), provider);
supported_domains_.add(provider->domain());
}
for (const DynamicAttributesProvider *provider : dynamic_attribute_providers) {
provider->foreach_domain([&](AttrDomain domain) { supported_domains_.add(domain); });
}
}
const Map<std::string, const BuiltinAttributeProvider *> &builtin_attribute_providers() const
{
return builtin_attribute_providers_;
}
Span<const DynamicAttributesProvider *> dynamic_attribute_providers() const
{
return dynamic_attribute_providers_;
}
Span<AttrDomain> supported_domains() const
{
return supported_domains_;
}
};
namespace attribute_accessor_functions {
template<const GeometryAttributeProviders &providers>
inline std::optional<AttributeDomainAndType> builtin_domain_and_type(const void * /*owner*/,
const StringRef name)
{
if (const BuiltinAttributeProvider *provider =
providers.builtin_attribute_providers().lookup_default_as(name, nullptr))
{
const AttrType data_type = *custom_data_type_to_attr_type(provider->data_type());
return AttributeDomainAndType{provider->domain(), data_type};
}
return std::nullopt;
}
template<const GeometryAttributeProviders &providers>
inline GPointer builtin_default_value(const void * /*owner*/, const StringRef attribute_id)
{
if (const BuiltinAttributeProvider *provider =
providers.builtin_attribute_providers().lookup_default_as(attribute_id, nullptr))
{
return provider->default_value();
}
return {};
}
template<const GeometryAttributeProviders &providers>
inline GAttributeReader lookup(const void *owner, const StringRef name)
{
if (const BuiltinAttributeProvider *provider =
providers.builtin_attribute_providers().lookup_default_as(name, nullptr))
{
return provider->try_get_for_read(owner);
}
for (const DynamicAttributesProvider *provider : providers.dynamic_attribute_providers()) {
GAttributeReader attribute = provider->try_get_for_read(owner, name);
if (attribute) {
return attribute;
}
}
return {};
}
template<const GeometryAttributeProviders &providers>
inline void foreach_attribute(const void *owner,
const FunctionRef<void(const AttributeIter &)> fn,
const AttributeAccessor &accessor)
{
Set<StringRef, 16> handled_attribute_ids;
for (const BuiltinAttributeProvider *provider : providers.builtin_attribute_providers().values())
{
if (provider->exists(owner)) {
const auto get_fn = [&]() { return provider->try_get_for_read(owner); };
AttributeIter iter{provider->name(),
provider->domain(),
*custom_data_type_to_attr_type(provider->data_type()),
get_fn};
iter.is_builtin = true;
iter.accessor = &accessor;
fn(iter);
if (iter.is_stopped()) {
return;
}
handled_attribute_ids.add(iter.name);
}
}
for (const DynamicAttributesProvider *provider : providers.dynamic_attribute_providers()) {
const bool continue_loop = provider->foreach_attribute(owner, [&](const AttributeIter &iter) {
if (handled_attribute_ids.add(iter.name)) {
iter.accessor = &accessor;
fn(iter);
}
});
if (!continue_loop) {
return;
}
}
}
template<const GeometryAttributeProviders &providers>
inline AttributeValidator lookup_validator(const void * /*owner*/, const StringRef name)
{
const BuiltinAttributeProvider *provider =
providers.builtin_attribute_providers().lookup_default_as(name, nullptr);
if (!provider) {
return {};
}
return provider->validator();
}
template<const GeometryAttributeProviders &providers>
inline GAttributeWriter lookup_for_write(void *owner, const StringRef name)
{
if (const BuiltinAttributeProvider *provider =
providers.builtin_attribute_providers().lookup_default_as(name, nullptr))
{
return provider->try_get_for_write(owner);
}
for (const DynamicAttributesProvider *provider : providers.dynamic_attribute_providers()) {
GAttributeWriter attribute = provider->try_get_for_write(owner, name);
if (attribute) {
return attribute;
}
}
return {};
}
template<const GeometryAttributeProviders &providers>
inline bool remove(void *owner, const StringRef name)
{
if (const BuiltinAttributeProvider *provider =
providers.builtin_attribute_providers().lookup_default_as(name, nullptr))
{
return provider->try_delete(owner);
}
for (const DynamicAttributesProvider *provider : providers.dynamic_attribute_providers()) {
if (provider->try_delete(owner, name)) {
return true;
}
}
return false;
}
template<const GeometryAttributeProviders &providers>
inline bool add(void *owner,
const StringRef name,
const AttrDomain domain,
const AttrType data_type,
const AttributeInit &initializer)
{
const eCustomDataType custom_data_type = *attr_type_to_custom_data_type(data_type);
if (const BuiltinAttributeProvider *provider =
providers.builtin_attribute_providers().lookup_default_as(name, nullptr))
{
if (provider->domain() != domain) {
return false;
}
if (provider->data_type() != custom_data_type) {
return false;
}
return provider->try_create(owner, initializer);
}
for (const DynamicAttributesProvider *provider : providers.dynamic_attribute_providers()) {
if (provider->try_create(owner, name, domain, custom_data_type, initializer)) {
return true;
}
}
return false;
}
template<const GeometryAttributeProviders &providers>
inline AttributeAccessorFunctions accessor_functions_for_providers()
{
return AttributeAccessorFunctions{nullptr,
nullptr,
builtin_domain_and_type<providers>,
builtin_default_value<providers>,
lookup<providers>,
nullptr,
foreach_attribute<providers>,
lookup_validator<providers>,
lookup_for_write<providers>,
remove<providers>,
add<providers>};
}
} // namespace attribute_accessor_functions
} // namespace blender::bke

View file

@ -219,52 +219,6 @@ std::optional<eCustomDataType> attr_type_to_custom_data_type(const AttrType attr
return std::nullopt;
}
struct CustomDataAndSizeMutable {
CustomData &data;
int size;
};
static void convert_storage_to_customdata(
AttributeStorage &storage,
const Map<AttrDomain, CustomDataAndSizeMutable> &custom_data_domains)
{
/* Name uniqueness is handled by the #CustomData API. */
storage.foreach([&](const Attribute &attribute) {
const std::optional<eCustomDataType> data_type = attr_type_to_custom_data_type(
attribute.data_type());
if (!data_type) {
return;
}
CustomData &custom_data = custom_data_domains.lookup(attribute.domain()).data;
const int domain_size = custom_data_domains.lookup(attribute.domain()).size;
if (const auto *array_data = std::get_if<Attribute::ArrayData>(&attribute.data())) {
BLI_assert(array_data->size == domain_size);
CustomData_add_layer_named_with_data(&custom_data,
*data_type,
array_data->data,
array_data->size,
attribute.name(),
array_data->sharing_info.get());
}
else if (const auto *single_data = std::get_if<Attribute::SingleData>(&attribute.data())) {
const CPPType &cpp_type = *custom_data_type_to_cpp_type(*data_type);
auto *value = new ImplicitSharedValue<GArray<>>(cpp_type, domain_size);
cpp_type.fill_construct_n(single_data->value, value->data.data(), domain_size);
CustomData_add_layer_named_with_data(
&custom_data, *data_type, value->data.data(), domain_size, attribute.name(), value);
}
});
storage = {};
}
void mesh_convert_storage_to_customdata(Mesh &mesh)
{
convert_storage_to_customdata(mesh.attribute_storage.wrap(),
{{AttrDomain::Point, {mesh.vert_data, mesh.verts_num}},
{AttrDomain::Edge, {mesh.edge_data, mesh.edges_num}},
{AttrDomain::Face, {mesh.face_data, mesh.faces_num}},
{AttrDomain::Corner, {mesh.corner_data, mesh.corners_num}}});
}
void mesh_convert_customdata_to_storage(Mesh &mesh)
{
bke::attribute_legacy_convert_customdata_to_storage(
@ -326,14 +280,73 @@ static CustomData &get_custom_data(Mesh &mesh, const AttrDomain domain)
return const_cast<CustomData &>(get_custom_data(std::as_const(mesh), domain));
}
static int get_domain_size(const Mesh &mesh, const AttrDomain domain)
{
switch (domain) {
case AttrDomain::Point:
return mesh.verts_num;
case AttrDomain::Edge:
return mesh.edges_num;
case AttrDomain::Face:
return mesh.faces_num;
case AttrDomain::Corner:
return mesh.corners_num;
default:
BLI_assert_unreachable();
return 0;
}
}
LegacyMeshInterpolator::LegacyMeshInterpolator(const Mesh &src, Mesh &dst, const AttrDomain domain)
: cd_src_(get_custom_data(src, domain)), cd_dst_(get_custom_data(dst, domain))
{
const AttributeStorage &src_attributes = src.attribute_storage.wrap();
AttributeStorage &dst_attributes = dst.attribute_storage.wrap();
const int src_domain_size = get_domain_size(src, domain);
const int dst_domain_size = get_domain_size(dst, domain);
src_attributes.foreach([&](const Attribute &src_attr) {
if (src_attr.domain() != domain) {
return;
}
Attribute *dst_attr = dst_attributes.lookup(src_attr.name());
if (!dst_attr) {
return;
}
if (dst_attr->domain() != domain) {
return;
}
if (dst_attr->data_type() != src_attr.data_type()) {
return;
}
if (dst_attr->storage_type() != AttrStorageType::Array) {
return;
}
const CPPType &cpp_type = attribute_type_to_cpp_type(src_attr.data_type());
switch (src_attr.storage_type()) {
case AttrStorageType::Single: {
const auto &value = std::get<Attribute::SingleData>(src_attr.data());
attrs_src_.append(GVArray::from_single_ref(cpp_type, src_domain_size, value.value));
break;
}
case AttrStorageType::Array: {
const auto &value = std::get<Attribute::ArrayData>(src_attr.data());
attrs_src_.append(GVArray::from_span({cpp_type, value.data, src_domain_size}));
break;
}
}
auto &value = std::get<Attribute::ArrayData>(dst_attr->data_for_write());
attrs_dst_.append({cpp_type, value.data, dst_domain_size});
});
}
void LegacyMeshInterpolator::copy(const int src_index, const int dst_index, const int count) const
{
CustomData_copy_data(&cd_src_, &cd_dst_, src_index, dst_index, count);
for (const int i : attrs_src_.index_range()) {
const GVArray &src = attrs_src_[i];
GMutableSpan dst = attrs_dst_[i];
src.materialize_compressed(IndexRange(src_index, count), dst[dst_index]);
}
}
void LegacyMeshInterpolator::mix(Span<int> src_indices,
@ -346,6 +359,18 @@ void LegacyMeshInterpolator::mix(Span<int> src_indices,
weights ? weights->data() : nullptr,
src_indices.size(),
dst_index);
for (const int attr_index : attrs_src_.index_range()) {
attribute_math::convert_to_static_type(attrs_src_[attr_index].type(), [&](auto dummy) {
using T = decltype(dummy);
const VArray src = attrs_src_[attr_index].typed<T>();
MutableSpan dst = attrs_dst_[attr_index].typed<T>();
attribute_math::DefaultMixer<T> mixer(dst.slice(dst_index, 1));
for (const int i : src_indices.index_range()) {
mixer.mix_in(0, src[i], weights ? (*weights)[i] : 1.0f);
}
mixer.finalize();
});
}
}
} // namespace blender::bke

View file

@ -849,7 +849,7 @@ Mesh *curve_to_mesh_sweep(const CurvesGeometry &main,
/* Add the position attribute later so it can be shared in some cases. */
Mesh *mesh = BKE_mesh_new_nomain(
0, offsets.edge.last(), offsets.face.last(), offsets.loop.last());
CustomData_free_layer_named(&mesh->vert_data, "position");
mesh->attribute_storage.wrap().remove("position");
mesh->verts_num = offsets.vert.last();
MutableSpan<int2> edges = mesh->edges_for_write();

View file

@ -2381,30 +2381,6 @@ bool CustomData_merge_layout(const CustomData *source,
return customdata_merge_internal(source, dest, mask, alloctype, totelem);
}
CustomData CustomData_shallow_copy_remove_non_bmesh_attributes(const CustomData *src,
const eCustomDataMask mask)
{
Vector<CustomDataLayer> dst_layers;
for (const CustomDataLayer &layer : Span<CustomDataLayer>{src->layers, src->totlayer}) {
if (BM_attribute_stored_in_bmesh_builtin(layer.name)) {
continue;
}
if (!(mask & CD_TYPE_AS_MASK(eCustomDataType(layer.type)))) {
continue;
}
dst_layers.append(layer);
}
CustomData dst = *src;
dst.layers = MEM_new_array_for_free<CustomDataLayer>(dst_layers.size(), __func__);
dst.maxlayer = dst.totlayer = dst_layers.size();
memcpy(dst.layers, dst_layers.data(), dst_layers.as_span().size_in_bytes());
CustomData_update_typemap(&dst);
return dst;
}
/**
* An #ImplicitSharingInfo that knows how to free the entire referenced custom data layer
* (including potentially separately allocated chunks like for vertex groups).

View file

@ -74,6 +74,8 @@
#include "BLO_read_write.hh"
#include "attribute_storage_access.hh"
namespace blender {
/** Using STACK_FIXED_DEPTH to keep the implementation in line with `pbvh.cc`. */
@ -452,9 +454,6 @@ static void mesh_blend_read_data(BlendDataReader *reader, ID *id)
BLO_read_string(reader, &mesh->stencil_uv_map_attribute);
BLO_read_string(reader, &mesh->clone_uv_map_attribute);
/* Forward compatibility. To be removed when runtime format changes. */
bke::mesh_convert_storage_to_customdata(*mesh);
mesh->texspace_flag &= ~ME_TEXSPACE_FLAG_AUTO_EVALUATED;
mesh->runtime = new bke::MeshRuntime();
@ -1092,28 +1091,26 @@ void BKE_mesh_face_offsets_ensure_alloc(Mesh *mesh)
Span<float3> Mesh::vert_positions() const
{
return {static_cast<const float3 *>(
CustomData_get_layer_named(&this->vert_data, CD_PROP_FLOAT3, "position")),
this->verts_num};
return bke::get_span_attribute<float3>(
this->attribute_storage.wrap(), bke::AttrDomain::Point, "position", this->verts_num)
.value_or(Span<float3>());
}
MutableSpan<float3> Mesh::vert_positions_for_write()
{
return {static_cast<float3 *>(CustomData_get_layer_named_for_write(
&this->vert_data, CD_PROP_FLOAT3, "position", this->verts_num)),
this->verts_num};
return bke::get_mutable_attribute<float3>(
this->attribute_storage.wrap(), bke::AttrDomain::Point, "position", this->verts_num);
}
Span<int2> Mesh::edges() const
{
return {static_cast<const int2 *>(
CustomData_get_layer_named(&this->edge_data, CD_PROP_INT32_2D, ".edge_verts")),
this->edges_num};
return bke::get_span_attribute<int2>(
this->attribute_storage.wrap(), bke::AttrDomain::Edge, ".edge_verts", this->edges_num)
.value_or(Span<int2>());
}
MutableSpan<int2> Mesh::edges_for_write()
{
return {static_cast<int2 *>(CustomData_get_layer_named_for_write(
&this->edge_data, CD_PROP_INT32_2D, ".edge_verts", this->edges_num)),
this->edges_num};
return bke::get_mutable_attribute<int2>(
this->attribute_storage.wrap(), bke::AttrDomain::Edge, ".edge_verts", this->edges_num);
}
OffsetIndices<int> Mesh::faces() const
@ -1139,28 +1136,30 @@ MutableSpan<int> Mesh::face_offsets_for_write()
Span<int> Mesh::corner_verts() const
{
return {static_cast<const int *>(
CustomData_get_layer_named(&this->corner_data, CD_PROP_INT32, ".corner_vert")),
this->corners_num};
return bke::get_span_attribute<int>(this->attribute_storage.wrap(),
bke::AttrDomain::Corner,
".corner_vert",
this->corners_num)
.value_or(Span<int>());
}
MutableSpan<int> Mesh::corner_verts_for_write()
{
return {static_cast<int *>(CustomData_get_layer_named_for_write(
&this->corner_data, CD_PROP_INT32, ".corner_vert", this->corners_num)),
this->corners_num};
return bke::get_mutable_attribute<int>(
this->attribute_storage.wrap(), bke::AttrDomain::Corner, ".corner_vert", this->corners_num);
}
Span<int> Mesh::corner_edges() const
{
return {static_cast<const int *>(
CustomData_get_layer_named(&this->corner_data, CD_PROP_INT32, ".corner_edge")),
this->corners_num};
return bke::get_span_attribute<int>(this->attribute_storage.wrap(),
bke::AttrDomain::Corner,
".corner_edge",
this->corners_num)
.value_or(Span<int>());
}
MutableSpan<int> Mesh::corner_edges_for_write()
{
return {static_cast<int *>(CustomData_get_layer_named_for_write(
&this->corner_data, CD_PROP_INT32, ".corner_edge", this->corners_num)),
this->corners_num};
return bke::get_mutable_attribute<int>(
this->attribute_storage.wrap(), bke::AttrDomain::Corner, ".corner_edge", this->corners_num);
}
Span<MDeformVert> Mesh::deform_verts() const
@ -1188,6 +1187,7 @@ void Mesh::count_memory(MemoryCounter &memory) const
{
memory.add_shared(this->runtime->face_offsets_sharing_info,
this->face_offsets().size_in_bytes());
this->attribute_storage.wrap().count_memory(memory);
CustomData_count_memory(this->vert_data, this->verts_num, memory);
CustomData_count_memory(this->edge_data, this->edges_num, memory);
CustomData_count_memory(this->face_data, this->faces_num, memory);
@ -1319,10 +1319,10 @@ Mesh *mesh_new_no_attributes(const int verts_num,
mesh->verts_num = verts_num;
mesh->edges_num = edges_num;
mesh->corners_num = corners_num;
CustomData_free_layer_named(&mesh->vert_data, "position");
CustomData_free_layer_named(&mesh->edge_data, ".edge_verts");
CustomData_free_layer_named(&mesh->corner_data, ".corner_vert");
CustomData_free_layer_named(&mesh->corner_data, ".corner_edge");
mesh->attribute_storage.wrap().remove("position");
mesh->attribute_storage.wrap().remove(".edge_verts");
mesh->attribute_storage.wrap().remove(".corner_vert");
mesh->attribute_storage.wrap().remove(".corner_edge");
return mesh;
}
@ -1448,6 +1448,14 @@ Mesh *BKE_mesh_new_nomain_from_template_ex(const Mesh *me_src,
CustomData_add_layer(&me_dst->fdata_legacy, CD_MFACE, CD_SET_DEFAULT, me_dst->totface_legacy);
}
bke::MutableAttributeAccessor dst_attrs = me_dst->attributes_for_write();
me_src->attribute_storage.wrap().foreach([&](const bke::Attribute &attr) {
if (dst_attrs.contains(attr.name())) {
return;
}
dst_attrs.add(attr.name(), attr.domain(), attr.data_type(), bke::AttributeInitDefaultValue());
});
return me_dst;
}

View file

@ -17,7 +17,7 @@
#include "FN_multi_function_builder.hh"
#include "attribute_access_intern.hh"
#include "attribute_storage_access.hh"
namespace blender::bke {
@ -696,304 +696,338 @@ static GVArray adapt_mesh_attribute_domain(const Mesh &mesh,
static void tag_positions_changed(void *owner)
{
Mesh *mesh = static_cast<Mesh *>(owner);
if (mesh != nullptr) {
mesh->tag_positions_changed();
}
mesh->tag_positions_changed();
}
static void tag_sharpness_changed(void *owner)
{
if (Mesh *mesh = static_cast<Mesh *>(owner)) {
mesh->tag_sharpness_changed();
}
Mesh *mesh = static_cast<Mesh *>(owner);
mesh->tag_sharpness_changed();
}
static void tag_material_index_changed(void *owner)
{
if (Mesh *mesh = static_cast<Mesh *>(owner)) {
mesh->tag_material_index_changed();
Mesh *mesh = static_cast<Mesh *>(owner);
mesh->tag_material_index_changed();
}
static void tag_visibility_changed(void *owner)
{
Mesh &mesh = *static_cast<Mesh *>(owner);
mesh.tag_visibility_changed();
}
static void tag_custom_normals_changed(void *owner)
{
Mesh &mesh = *static_cast<Mesh *>(owner);
mesh.tag_custom_normals_changed();
}
static const auto &changed_tags()
{
static Map<StringRef, AttrUpdateOnChange> attributes{
{"position", tag_positions_changed},
{"sharp_edge", tag_sharpness_changed},
{"sharp_face", tag_sharpness_changed},
{"material_index", tag_material_index_changed},
{".hide_vert", tag_visibility_changed},
{".hide_edge", tag_visibility_changed},
{".hide_poly", tag_visibility_changed},
{"custom_normal", tag_custom_normals_changed},
};
return attributes;
}
static int get_domain_size(const void *owner, const AttrDomain domain)
{
const Mesh *mesh = static_cast<const Mesh *>(owner);
switch (domain) {
case AttrDomain::Point:
return mesh->verts_num;
case AttrDomain::Edge:
return mesh->edges_num;
case AttrDomain::Face:
return mesh->faces_num;
case AttrDomain::Corner:
return mesh->corners_num;
default:
return 0;
}
}
/**
* This provider makes vertex groups available as float attributes.
*/
class MeshVertexGroupsAttributeProvider final : public DynamicAttributesProvider {
public:
GAttributeReader try_get_for_read(const void *owner, const StringRef attribute_id) const final
{
const Mesh *mesh = static_cast<const Mesh *>(owner);
if (mesh == nullptr) {
return {};
}
const int vertex_group_index = BKE_defgroup_name_index(&mesh->vertex_group_names,
attribute_id);
if (vertex_group_index < 0) {
return {};
}
const Span<MDeformVert> dverts = mesh->deform_verts();
return this->get_for_vertex_group_index(*mesh, dverts, vertex_group_index);
static GAttributeReader reader_for_vertex_group_index(const Mesh &mesh,
const Span<MDeformVert> dverts,
const int vertex_group_index)
{
BLI_assert(vertex_group_index >= 0);
if (dverts.is_empty()) {
return {VArray<float>::from_single(0.0f, mesh.verts_num), AttrDomain::Point};
}
return {varray_for_deform_verts(dverts, vertex_group_index), AttrDomain::Point};
}
static GAttributeReader try_get_vertex_group(const void *owner, const StringRef attribute_id)
{
const Mesh *mesh = static_cast<const Mesh *>(owner);
if (mesh == nullptr) {
return {};
}
const int vertex_group_index = BKE_defgroup_name_index(&mesh->vertex_group_names, attribute_id);
if (vertex_group_index < 0) {
return {};
}
const Span<MDeformVert> dverts = mesh->deform_verts();
return reader_for_vertex_group_index(*mesh, dverts, vertex_group_index);
}
static GAttributeWriter try_get_vertex_group_for_write(void *owner, const StringRef attribute_id)
{
Mesh *mesh = static_cast<Mesh *>(owner);
if (mesh == nullptr) {
return {};
}
const int vertex_group_index = BKE_defgroup_name_index(&mesh->vertex_group_names, attribute_id);
if (vertex_group_index < 0) {
return {};
}
MutableSpan<MDeformVert> dverts = mesh->deform_verts_for_write();
return {varray_for_mutable_deform_verts(dverts, vertex_group_index), AttrDomain::Point};
}
static bool try_delete_vertex_group(void *owner, const StringRef name)
{
Mesh *mesh = static_cast<Mesh *>(owner);
if (mesh == nullptr) {
return true;
}
GAttributeReader get_for_vertex_group_index(const Mesh &mesh,
const Span<MDeformVert> dverts,
const int vertex_group_index) const
{
BLI_assert(vertex_group_index >= 0);
if (dverts.is_empty()) {
return {VArray<float>::from_single(0.0f, mesh.verts_num), AttrDomain::Point};
}
return {varray_for_deform_verts(dverts, vertex_group_index), AttrDomain::Point};
int index;
bDeformGroup *group;
if (!BKE_id_defgroup_name_find(&mesh->id, name, &index, &group)) {
return false;
}
BLI_remlink(&mesh->vertex_group_names, group);
MEM_freeN(group);
if (mesh->deform_verts().is_empty()) {
return true;
}
GAttributeWriter try_get_for_write(void *owner, const StringRef attribute_id) const final
{
Mesh *mesh = static_cast<Mesh *>(owner);
if (mesh == nullptr) {
return {};
}
const int vertex_group_index = BKE_defgroup_name_index(&mesh->vertex_group_names,
attribute_id);
if (vertex_group_index < 0) {
return {};
}
MutableSpan<MDeformVert> dverts = mesh->deform_verts_for_write();
return {varray_for_mutable_deform_verts(dverts, vertex_group_index), AttrDomain::Point};
MutableSpan<MDeformVert> dverts = mesh->deform_verts_for_write();
remove_defgroup_index(dverts, index);
return true;
}
static bool foreach_vertex_group(const void *owner, FunctionRef<void(const AttributeIter &)> fn)
{
const Mesh *mesh = static_cast<const Mesh *>(owner);
if (mesh == nullptr) {
return true;
}
const AttributeAccessor accessor = mesh->attributes();
const Span<MDeformVert> dverts = mesh->deform_verts();
bool try_delete(void *owner, const StringRef name) const final
{
Mesh *mesh = static_cast<Mesh *>(owner);
if (mesh == nullptr) {
return true;
}
int index;
bDeformGroup *group;
if (!BKE_id_defgroup_name_find(&mesh->id, name, &index, &group)) {
for (const auto [group_index, group] : mesh->vertex_group_names.enumerate()) {
const int index = group_index;
const auto get_fn = [&]() { return reader_for_vertex_group_index(*mesh, dverts, index); };
AttributeIter iter{group.name, AttrDomain::Point, bke::AttrType::Float, get_fn};
iter.is_builtin = false;
iter.accessor = &accessor;
fn(iter);
if (iter.is_stopped()) {
return false;
}
BLI_remlink(&mesh->vertex_group_names, group);
MEM_freeN(group);
if (mesh->deform_verts().is_empty()) {
return true;
}
MutableSpan<MDeformVert> dverts = mesh->deform_verts_for_write();
remove_defgroup_index(dverts, index);
return true;
}
bool foreach_attribute(const void *owner,
const FunctionRef<void(const AttributeIter &)> fn) const final
{
const Mesh *mesh = static_cast<const Mesh *>(owner);
if (mesh == nullptr) {
return true;
}
const AttributeAccessor accessor = mesh->attributes();
const Span<MDeformVert> dverts = mesh->deform_verts();
for (const auto [group_index, group] : mesh->vertex_group_names.enumerate()) {
const auto get_fn = [&, group_index = group_index]() {
return this->get_for_vertex_group_index(*mesh, dverts, group_index);
};
AttributeIter iter{group.name, AttrDomain::Point, bke::AttrType::Float, get_fn};
iter.is_builtin = false;
iter.accessor = &accessor;
fn(iter);
if (iter.is_stopped()) {
return false;
}
}
return true;
}
void foreach_domain(const FunctionRef<void(AttrDomain)> callback) const final
{
callback(AttrDomain::Point);
}
};
static std::function<void()> get_tag_modified_function(void *owner, const StringRef name)
{
if (name.startswith(".hide")) {
return [owner]() { (static_cast<Mesh *>(owner))->tag_visibility_changed(); };
}
if (name == "custom_normal") {
return [owner]() { (static_cast<Mesh *>(owner))->tag_custom_normals_changed(); };
}
return {};
return true;
}
/**
* In this function all the attribute providers for a mesh component are created. Most data in this
* function is statically allocated, because it does not change over time.
*/
static GeometryAttributeProviders create_attribute_providers_for_mesh()
static const auto &builtin_attributes()
{
#define MAKE_MUTABLE_CUSTOM_DATA_GETTER(NAME) \
[](void *owner) -> CustomData * { \
Mesh *mesh = static_cast<Mesh *>(owner); \
return &mesh->NAME; \
}
#define MAKE_CONST_CUSTOM_DATA_GETTER(NAME) \
[](const void *owner) -> const CustomData * { \
const Mesh *mesh = static_cast<const Mesh *>(owner); \
return &mesh->NAME; \
}
#define MAKE_GET_ELEMENT_NUM_GETTER(NAME) \
[](const void *owner) -> int { \
const Mesh *mesh = static_cast<const Mesh *>(owner); \
return mesh->NAME; \
}
static auto attributes = []() {
Map<StringRef, AttrBuiltinInfo> map;
static CustomDataAccessInfo corner_access = {MAKE_MUTABLE_CUSTOM_DATA_GETTER(corner_data),
MAKE_CONST_CUSTOM_DATA_GETTER(corner_data),
MAKE_GET_ELEMENT_NUM_GETTER(corners_num),
get_tag_modified_function};
static CustomDataAccessInfo point_access = {MAKE_MUTABLE_CUSTOM_DATA_GETTER(vert_data),
MAKE_CONST_CUSTOM_DATA_GETTER(vert_data),
MAKE_GET_ELEMENT_NUM_GETTER(verts_num),
get_tag_modified_function};
static CustomDataAccessInfo edge_access = {MAKE_MUTABLE_CUSTOM_DATA_GETTER(edge_data),
MAKE_CONST_CUSTOM_DATA_GETTER(edge_data),
MAKE_GET_ELEMENT_NUM_GETTER(edges_num),
get_tag_modified_function};
static CustomDataAccessInfo face_access = {MAKE_MUTABLE_CUSTOM_DATA_GETTER(face_data),
MAKE_CONST_CUSTOM_DATA_GETTER(face_data),
MAKE_GET_ELEMENT_NUM_GETTER(faces_num),
get_tag_modified_function};
AttrBuiltinInfo position(AttrDomain::Point, AttrType::Float3);
position.deletable = false;
map.add_new("position", std::move(position));
#undef MAKE_CONST_CUSTOM_DATA_GETTER
#undef MAKE_MUTABLE_CUSTOM_DATA_GETTER
static const auto material_index_clamp = mf::build::SI1_SO<int, int>(
"Material Index Validate",
[](int value) {
/* Use #short for the maximum since many areas still use that type for indices. */
return std::clamp<int>(value, 0, std::numeric_limits<short>::max());
},
mf::build::exec_presets::AllSpanOrSingle());
AttrBuiltinInfo material_index(AttrDomain::Face, AttrType::Int32);
material_index.validator = AttributeValidator{&material_index_clamp};
map.add_new("material_index", std::move(material_index));
static BuiltinCustomDataLayerProvider position("position",
AttrDomain::Point,
CD_PROP_FLOAT3,
BuiltinAttributeProvider::NonDeletable,
point_access,
tag_positions_changed);
static const auto int2_index_clamp = mf::build::SI1_SO<int2, int2>(
"Index Validate",
[](int2 value) { return math::max(value, int2(0)); },
mf::build::exec_presets::AllSpanOrSingle());
AttrBuiltinInfo edge_verts(AttrDomain::Edge, AttrType::Int32_2D);
edge_verts.validator = AttributeValidator{&int2_index_clamp};
map.add_new("edge_verts", std::move(edge_verts));
static const auto material_index_clamp = mf::build::SI1_SO<int, int>(
"Material Index Validate",
[](int value) {
/* Use #short for the maximum since many areas still use that type for indices. */
return std::clamp<int>(value, 0, std::numeric_limits<short>::max());
},
mf::build::exec_presets::AllSpanOrSingle());
static BuiltinCustomDataLayerProvider material_index("material_index",
AttrDomain::Face,
CD_PROP_INT32,
BuiltinAttributeProvider::Deletable,
face_access,
tag_material_index_changed,
AttributeValidator{&material_index_clamp});
/* NOTE: This clamping is more of a last resort, since it's quite easy to make an
* invalid mesh that will crash Blender by arbitrarily editing this attribute. */
static const auto int_index_clamp = mf::build::SI1_SO<int, int>(
"Index Validate",
[](int value) { return std::max(value, 0); },
mf::build::exec_presets::AllSpanOrSingle());
AttrBuiltinInfo corner_vert(AttrDomain::Corner, AttrType::Int32);
corner_vert.validator = AttributeValidator{&int_index_clamp};
map.add_new("corner_vert", std::move(corner_vert));
static const auto int2_index_clamp = mf::build::SI1_SO<int2, int2>(
"Index Validate",
[](int2 value) { return math::max(value, int2(0)); },
mf::build::exec_presets::AllSpanOrSingle());
static BuiltinCustomDataLayerProvider edge_verts(".edge_verts",
AttrDomain::Edge,
CD_PROP_INT32_2D,
BuiltinAttributeProvider::NonDeletable,
edge_access,
nullptr,
AttributeValidator{&int2_index_clamp});
AttrBuiltinInfo corner_edge(AttrDomain::Corner, AttrType::Int32);
corner_edge.validator = AttributeValidator{&int_index_clamp};
map.add_new("corner_edge", std::move(corner_edge));
/* NOTE: This clamping is more of a last resort, since it's quite easy to make an
* invalid mesh that will crash Blender by arbitrarily editing this attribute. */
static const auto int_index_clamp = mf::build::SI1_SO<int, int>(
"Index Validate",
[](int value) { return std::max(value, 0); },
mf::build::exec_presets::AllSpanOrSingle());
static BuiltinCustomDataLayerProvider corner_vert(".corner_vert",
AttrDomain::Corner,
CD_PROP_INT32,
BuiltinAttributeProvider::NonDeletable,
corner_access,
nullptr,
AttributeValidator{&int_index_clamp});
static BuiltinCustomDataLayerProvider corner_edge(".corner_edge",
AttrDomain::Corner,
CD_PROP_INT32,
BuiltinAttributeProvider::NonDeletable,
corner_access,
nullptr,
AttributeValidator{&int_index_clamp});
AttrBuiltinInfo sharp_face(AttrDomain::Face, AttrType::Bool);
map.add_new("sharp_face", std::move(sharp_face));
static BuiltinCustomDataLayerProvider sharp_face("sharp_face",
AttrDomain::Face,
CD_PROP_BOOL,
BuiltinAttributeProvider::Deletable,
face_access,
tag_sharpness_changed);
AttrBuiltinInfo sharp_edge(AttrDomain::Edge, AttrType::Bool);
map.add_new("sharp_edge", std::move(sharp_edge));
static BuiltinCustomDataLayerProvider sharp_edge("sharp_edge",
AttrDomain::Edge,
CD_PROP_BOOL,
BuiltinAttributeProvider::Deletable,
edge_access,
tag_sharpness_changed);
static MeshVertexGroupsAttributeProvider vertex_groups;
static CustomDataAttributeProvider corner_custom_data(AttrDomain::Corner, corner_access);
static CustomDataAttributeProvider point_custom_data(AttrDomain::Point, point_access);
static CustomDataAttributeProvider edge_custom_data(AttrDomain::Edge, edge_access);
static CustomDataAttributeProvider face_custom_data(AttrDomain::Face, face_access);
return GeometryAttributeProviders({&position,
&edge_verts,
&corner_vert,
&corner_edge,
&material_index,
&sharp_face,
&sharp_edge},
{&corner_custom_data,
&vertex_groups,
&point_custom_data,
&edge_custom_data,
&face_custom_data});
return map;
}();
return attributes;
}
static AttributeAccessorFunctions get_mesh_accessor_functions()
{
static const GeometryAttributeProviders providers = create_attribute_providers_for_mesh();
AttributeAccessorFunctions fn =
attribute_accessor_functions::accessor_functions_for_providers<providers>();
fn.domain_size = [](const void *owner, const AttrDomain domain) {
if (owner == nullptr) {
return 0;
}
const Mesh &mesh = *static_cast<const Mesh *>(owner);
switch (domain) {
case AttrDomain::Point:
return mesh.verts_num;
case AttrDomain::Edge:
return mesh.edges_num;
case AttrDomain::Face:
return mesh.faces_num;
case AttrDomain::Corner:
return mesh.corners_num;
default:
return 0;
}
};
AttributeAccessorFunctions fn{};
fn.domain_supported = [](const void * /*owner*/, const AttrDomain domain) {
return ELEM(domain, AttrDomain::Point, AttrDomain::Edge, AttrDomain::Face, AttrDomain::Corner);
};
fn.domain_size = get_domain_size;
fn.builtin_domain_and_type = [](const void * /*owner*/,
const StringRef name) -> std::optional<AttributeDomainAndType> {
const AttrBuiltinInfo *info = builtin_attributes().lookup_ptr(name);
if (!info) {
return std::nullopt;
}
return AttributeDomainAndType{info->domain, info->type};
};
fn.get_builtin_default = [](const void * /*owner*/, StringRef name) -> GPointer {
const AttrBuiltinInfo &info = builtin_attributes().lookup(name);
return info.default_value;
};
fn.lookup = [](const void *owner, const StringRef name) -> GAttributeReader {
const Mesh &mesh = *static_cast<const Mesh *>(owner);
if (GAttributeReader vertex_group = try_get_vertex_group(owner, name)) {
return vertex_group;
}
const AttributeStorage &storage = mesh.attribute_storage.wrap();
const Attribute *attr = storage.lookup(name);
if (!attr) {
return {};
}
const int domain_size = get_domain_size(owner, attr->domain());
return attribute_to_reader(*attr, attr->domain(), domain_size);
};
fn.adapt_domain = [](const void *owner,
const GVArray &varray,
const AttrDomain from_domain,
const AttrDomain to_domain) -> GVArray {
if (owner == nullptr) {
return {};
}
const Mesh &mesh = *static_cast<const Mesh *>(owner);
return adapt_mesh_attribute_domain(mesh, varray, from_domain, to_domain);
};
fn.foreach_attribute = [](const void *owner,
const FunctionRef<void(const AttributeIter &)> fn,
const AttributeAccessor &accessor) {
const Mesh &mesh = *static_cast<const Mesh *>(owner);
const bool should_continue = foreach_vertex_group(
owner, [&](const AttributeIter &iter) { fn(iter); });
if (!should_continue) {
return;
}
const AttributeStorage &storage = mesh.attribute_storage.wrap();
storage.foreach_with_stop([&](const Attribute &attr) {
const auto get_fn = [&]() {
const int domain_size = get_domain_size(owner, attr.domain());
return attribute_to_reader(attr, attr.domain(), domain_size);
};
AttributeIter iter(attr.name(), attr.domain(), attr.data_type(), get_fn);
iter.is_builtin = builtin_attributes().contains(attr.name());
iter.accessor = &accessor;
fn(iter);
return !iter.is_stopped();
});
};
fn.lookup_validator = [](const void * /*owner*/, const StringRef name) -> AttributeValidator {
const AttrBuiltinInfo *info = builtin_attributes().lookup_ptr(name);
if (!info) {
return {};
}
return info->validator;
};
fn.lookup_for_write = [](void *owner, const StringRef name) -> GAttributeWriter {
Mesh &mesh = *static_cast<Mesh *>(owner);
if (GAttributeWriter vertex_group = try_get_vertex_group_for_write(owner, name)) {
return vertex_group;
}
AttributeStorage &storage = mesh.attribute_storage.wrap();
Attribute *attr = storage.lookup(name);
if (!attr) {
return {};
}
const int domain_size = get_domain_size(owner, attr->domain());
return attribute_to_writer(&mesh, changed_tags(), domain_size, *attr);
};
fn.remove = [](void *owner, const StringRef name) -> bool {
Mesh &mesh = *static_cast<Mesh *>(owner);
if (try_delete_vertex_group(owner, name)) {
return true;
}
AttributeStorage &storage = mesh.attribute_storage.wrap();
if (const AttrBuiltinInfo *info = builtin_attributes().lookup_ptr(name)) {
if (!info->deletable) {
return false;
}
}
const std::optional<AttrUpdateOnChange> fn = changed_tags().lookup_try(name);
const bool removed = storage.remove(name);
if (!removed) {
return false;
}
if (fn) {
(*fn)(owner);
}
return true;
};
fn.add = [](void *owner,
const StringRef name,
const AttrDomain domain,
const AttrType type,
const AttributeInit &initializer) {
Mesh &mesh = *static_cast<Mesh *>(owner);
const int domain_size = get_domain_size(owner, domain);
AttributeStorage &storage = mesh.attribute_storage.wrap();
if (const AttrBuiltinInfo *info = builtin_attributes().lookup_ptr(name)) {
if (info->domain != domain || info->type != type) {
return false;
}
}
if (storage.lookup(name)) {
return false;
}
storage.add(name, domain, type, attribute_init_to_data(type, domain_size, initializer));
if (initializer.type != AttributeInit::Type::Construct) {
if (const std::optional<AttrUpdateOnChange> fn = changed_tags().lookup_try(name)) {
(*fn)(owner);
}
}
return true;
};
return fn;
}

View file

@ -497,34 +497,48 @@ void mesh_calc_edges(Mesh &mesh,
dst_attributes.remove(attribute);
}
CustomData_free_layer_named(&mesh.edge_data, ".edge_verts");
for (CustomDataLayer &layer : MutableSpan(mesh.edge_data.layers, mesh.edge_data.totlayer)) {
const void *src_data = layer.data;
const size_t elem_size = CustomData_sizeof(eCustomDataType(layer.type));
void *dst_data = MEM_malloc_arrayN(result_edges_num, elem_size, AT);
if (src_data != nullptr) {
if (layer.type == CD_ORIGINDEX) {
const Span src(static_cast<const int *>(src_data), mesh.edges_num);
MutableSpan dst(static_cast<int *>(dst_data), result_edges_num);
array_utils::gather(src, src_to_dst_mask, dst.take_front(src_to_dst_mask.size()));
dst.slice(back_range_of_new_edges).fill(-1);
}
else {
const CPPType *type = custom_data_type_to_cpp_type(eCustomDataType(layer.type));
BLI_assert(type != nullptr);
const GSpan src(type, src_data, mesh.edges_num);
GMutableSpan dst(type, dst_data, result_edges_num);
array_utils::gather(src, src_to_dst_mask, dst.take_front(src_to_dst_mask.size()));
type->fill_assign_n(type->default_value(),
dst.slice(back_range_of_new_edges).data(),
dst.slice(back_range_of_new_edges).size());
}
layer.sharing_info->remove_user_and_delete_if_last();
mesh.attribute_storage.wrap().remove(".edge_verts");
mesh.attribute_storage.wrap().foreach([&](bke::Attribute &attr) {
if (attr.domain() != bke::AttrDomain::Edge) {
return;
}
switch (attr.storage_type()) {
case AttrStorageType::Single: {
break;
}
case AttrStorageType::Array: {
const CPPType &type = bke::attribute_type_to_cpp_type(attr.data_type());
const auto &src_data = std::get<bke::Attribute::ArrayData>(attr.data());
auto dst_data = bke::Attribute::ArrayData::from_uninitialized(type, result_edges_num);
const GSpan src(type, src_data.data, mesh.edges_num);
GMutableSpan dst(type, dst_data.data, result_edges_num);
array_utils::gather(src, src_to_dst_mask, dst.take_front(src_to_dst_mask.size()));
type.fill_construct_n(type.default_value(),
dst.slice(back_range_of_new_edges).data(),
dst.slice(back_range_of_new_edges).size());
attr.assign_data(std::move(dst_data));
break;
}
}
});
layer.data = dst_data;
layer.sharing_info = implicit_sharing::info_for_mem_free(dst_data);
{
const int orig_index_layer = CustomData_get_layer_index(&mesh.edge_data, CD_ORIGINDEX);
if (orig_index_layer != -1) {
CustomDataLayer &layer = mesh.edge_data.layers[orig_index_layer];
const int *src_data = static_cast<const int *>(layer.data);
int *dst_data = MEM_malloc_arrayN<int>(result_edges_num, __func__);
MutableSpan dst(dst_data, result_edges_num);
if (src_data != nullptr) {
const Span src(src_data, mesh.edges_num);
array_utils::gather(src, src_to_dst_mask, dst.take_front(src_to_dst_mask.size()));
}
dst.slice(back_range_of_new_edges).fill(-1);
layer.sharing_info->remove_user_and_delete_if_last();
layer.data = dst_data;
layer.sharing_info = implicit_sharing::info_for_mem_free(dst_data);
}
}
mesh.edges_num = result_edges_num;

View file

@ -1133,6 +1133,7 @@ void BKE_mesh_nomain_to_mesh(Mesh *mesh_src, Mesh *mesh_dst, Object *ob, bool pr
/* Using #CD_MASK_MESH ensures that only data that should exist in Main meshes is moved. */
const CustomData_MeshMasks mask = CD_MASK_MESH;
mesh_dst->attribute_storage.wrap() = std::move(mesh_src->attribute_storage.wrap());
CustomData_init_from(
&mesh_src->vert_data, &mesh_dst->vert_data, mask.vmask, mesh_src->verts_num);
CustomData_init_from(

View file

@ -35,6 +35,7 @@
#include "BKE_attribute.h"
#include "BKE_attribute.hh"
#include "BKE_attribute_legacy_convert.hh"
#include "BKE_customdata.hh"
#include "BKE_global.hh"
#include "BKE_idprop.hh"
@ -746,6 +747,9 @@ void BKE_mesh_convert_mfaces_to_mpolys(Mesh *mesh)
&mesh->faces_num);
BKE_mesh_legacy_convert_loops_to_corners(mesh);
BKE_mesh_legacy_convert_polys_to_offsets(mesh);
mesh->attribute_storage.wrap().remove(".corner_vert");
mesh->attribute_storage.wrap().remove(".corner_edge");
bke::mesh_convert_customdata_to_storage(*mesh);
mesh_ensure_tessellation_customdata(mesh);
}
@ -795,6 +799,7 @@ void BKE_mesh_do_versions_convert_mfaces_to_mpolys(Mesh *mesh)
&mesh->faces_num);
BKE_mesh_legacy_convert_loops_to_corners(mesh);
BKE_mesh_legacy_convert_polys_to_offsets(mesh);
bke::mesh_convert_customdata_to_storage(*mesh);
CustomData_bmesh_do_versions_update_active_layers(&mesh->fdata_legacy, &mesh->corner_data);
@ -2661,19 +2666,30 @@ void BKE_mesh_calc_edges_tessface(Mesh *mesh)
/* write new edges into a temporary CustomData */
CustomData edgeData;
CustomData_reset(&edgeData);
CustomData_add_layer_named(&edgeData, CD_PROP_INT32_2D, CD_CONSTRUCT, numEdges, ".edge_verts");
CustomData_add_layer(&edgeData, CD_ORIGINDEX, CD_SET_DEFAULT, numEdges);
int2 *ege = static_cast<int2 *>(CustomData_get_layer_named_for_write(
&edgeData, CD_PROP_INT32_2D, ".edge_verts", mesh->edges_num));
int *index = static_cast<int *>(
CustomData_get_layer_for_write(&edgeData, CD_ORIGINDEX, mesh->edges_num));
int *index = (int *)CustomData_get_layer_for_write(&edgeData, CD_ORIGINDEX, mesh->edges_num);
memset(index, ORIGINDEX_NONE, sizeof(int) * numEdges);
MutableSpan(ege, numEdges).copy_from(eh.as_span().cast<int2>());
/* free old CustomData and assign new one */
CustomData_free(&mesh->edge_data);
Set<StringRef> edge_attributes;
mesh->attribute_storage.wrap().foreach([&](const bke::Attribute &attr) {
if (attr.domain() == bke::AttrDomain::Edge) {
edge_attributes.add(attr.name());
}
});
for (const StringRef name : edge_attributes) {
mesh->attribute_storage.wrap().remove(name);
}
OrderedEdge *vector_data = eh.extract_vector().release().data;
bke::Attribute::ArrayData data{};
data.size = numEdges;
data.data = reinterpret_cast<int2 *>(vector_data);
data.sharing_info = ImplicitSharingPtr<>(implicit_sharing::info_for_mem_free(vector_data));
mesh->attribute_storage.wrap().add(
".edge_verts", bke::AttrDomain::Edge, bke::AttrType::Int32_2D, std::move(data));
mesh->edge_data = edgeData;
mesh->edges_num = numEdges;
}

View file

@ -370,22 +370,51 @@ static void remove_invalid_faces(Mesh &mesh, const IndexMask &valid_faces)
const OffsetIndices new_faces = offset_indices::gather_selected_offsets(
old_faces, valid_faces, new_face_offsets);
for (CustomDataLayer &layer : MutableSpan(mesh.face_data.layers, mesh.face_data.totlayer)) {
const eCustomDataType cd_type = eCustomDataType(layer.type);
if (CD_TYPE_AS_MASK(cd_type) & CD_MASK_PROP_ALL) {
const CPPType &type = *bke::custom_data_type_to_cpp_type(cd_type);
const GSpan src(type, layer.data, mesh.faces_num);
void *dst_data = MEM_malloc_arrayN(valid_faces_num, type.size, __func__);
GMutableSpan dst(type, dst_data, valid_faces_num);
array_utils::gather(src, valid_faces, dst);
layer.sharing_info->remove_user_and_delete_if_last();
layer.data = dst_data;
layer.sharing_info = implicit_sharing::info_for_mem_free(dst_data);
mesh.attribute_storage.wrap().foreach([&](bke::Attribute &attr) {
const CPPType &type = attribute_type_to_cpp_type(attr.data_type());
switch (attr.domain()) {
case AttrDomain::Face: {
switch (attr.storage_type()) {
case AttrStorageType::Array: {
const auto &src_data = std::get<Attribute::ArrayData>(attr.data());
auto dst_data = Attribute::ArrayData::from_uninitialized(type, valid_faces_num);
array_utils::gather(GSpan(type, src_data.data, mesh.faces_num),
valid_faces,
GMutableSpan(type, dst_data.data, valid_faces_num));
attr.assign_data(std::move(dst_data));
break;
}
case AttrStorageType::Single:
break;
}
break;
}
case AttrDomain::Corner: {
switch (attr.storage_type()) {
case AttrStorageType::Array: {
const auto &src_data = std::get<Attribute::ArrayData>(attr.data());
auto dst_data = Attribute::ArrayData::from_uninitialized(type, new_faces.total_size());
bke::attribute_math::gather_group_to_group(
old_faces,
new_faces,
valid_faces,
GSpan(type, src_data.data, mesh.corners_num),
GMutableSpan(type, dst_data.data, new_faces.total_size()));
attr.assign_data(std::move(dst_data));
break;
}
case AttrStorageType::Single:
break;
}
break;
}
default:
break;
}
else if (cd_type == CD_ORIGINDEX) {
});
for (CustomDataLayer &layer : MutableSpan(mesh.face_data.layers, mesh.face_data.totlayer)) {
if (layer.type == CD_ORIGINDEX) {
const Span src(static_cast<const int *>(layer.data), mesh.edges_num);
int *dst_data = MEM_malloc_arrayN<int>(valid_faces_num, __func__);
@ -400,26 +429,13 @@ static void remove_invalid_faces(Mesh &mesh, const IndexMask &valid_faces)
}
for (CustomDataLayer &layer : MutableSpan(mesh.corner_data.layers, mesh.corner_data.totlayer)) {
const eCustomDataType cd_type = eCustomDataType(layer.type);
if (CD_TYPE_AS_MASK(cd_type) & CD_MASK_PROP_ALL) {
const CPPType &type = *bke::custom_data_type_to_cpp_type(cd_type);
const GSpan src(type, layer.data, mesh.corners_num);
void *dst_data = MEM_malloc_arrayN(new_faces.total_size(), type.size, __func__);
GMutableSpan dst(type, dst_data, new_faces.total_size());
bke::attribute_math::gather_group_to_group(old_faces, new_faces, valid_faces, src, dst);
layer.sharing_info->remove_user_and_delete_if_last();
layer.data = dst_data;
layer.sharing_info = implicit_sharing::info_for_mem_free(dst_data);
}
else if (ELEM(cd_type,
CD_NORMAL,
CD_ORIGINDEX,
CD_MDISPS,
CD_GRID_PAINT_MASK,
CD_ORIGSPACE_MLOOP))
const eCustomDataType cd_type = eCustomDataType();
if (ELEM(layer.type,
CD_NORMAL,
CD_ORIGINDEX,
CD_MDISPS,
CD_GRID_PAINT_MASK,
CD_ORIGSPACE_MLOOP))
{
const size_t elem_size = CustomData_sizeof(cd_type);
const void *src = layer.data;
@ -452,22 +468,29 @@ static void remove_invalid_faces(Mesh &mesh, const IndexMask &valid_faces)
static void remove_invalid_edges(Mesh &mesh, const IndexMask &valid_edges)
{
const int valid_edges_num = valid_edges.size();
for (CustomDataLayer &layer : MutableSpan(mesh.edge_data.layers, mesh.edge_data.totlayer)) {
const eCustomDataType cd_type = eCustomDataType(layer.type);
if (CD_TYPE_AS_MASK(cd_type) & CD_MASK_PROP_ALL) {
const CPPType &type = *bke::custom_data_type_to_cpp_type(cd_type);
const GSpan src(type, layer.data, mesh.edges_num);
void *dst_data = MEM_malloc_arrayN(valid_edges_num, type.size, __func__);
GMutableSpan dst(type, dst_data, valid_edges_num);
array_utils::gather(src, valid_edges, dst);
layer.sharing_info->remove_user_and_delete_if_last();
layer.data = dst_data;
layer.sharing_info = implicit_sharing::info_for_mem_free(dst_data);
mesh.attribute_storage.wrap().foreach([&](bke::Attribute &attr) {
if (attr.domain() != AttrDomain::Edge) {
return;
}
else if (cd_type == CD_ORIGINDEX) {
const CPPType &type = attribute_type_to_cpp_type(attr.data_type());
switch (attr.storage_type()) {
case AttrStorageType::Array: {
const auto &src_data = std::get<Attribute::ArrayData>(attr.data());
auto dst_data = Attribute::ArrayData::from_uninitialized(type, valid_edges_num);
array_utils::gather(GSpan(type, src_data.data, mesh.faces_num),
valid_edges,
GMutableSpan(type, dst_data.data, valid_edges_num));
attr.assign_data(std::move(dst_data));
break;
}
case AttrStorageType::Single:
break;
}
});
for (CustomDataLayer &layer : MutableSpan(mesh.edge_data.layers, mesh.edge_data.totlayer)) {
if (layer.type == CD_ORIGINDEX) {
const Span src(static_cast<const int *>(layer.data), mesh.edges_num);
int *dst_data = MEM_malloc_arrayN<int>(valid_edges_num, __func__);

View file

@ -24,6 +24,7 @@
#include "BLI_sys_types.h"
#include "BKE_asset.hh"
#include "BKE_attribute_legacy_convert.hh"
#include "BKE_customdata.hh"
#include "BKE_idprop.hh"
#include "BKE_lib_id.hh"
@ -679,6 +680,12 @@ void blo_do_versions_510(FileData * /*fd*/, Library * /*lib*/, Main *bmain)
FOREACH_NODETREE_END;
}
if (!MAIN_VERSION_FILE_ATLEAST(bmain, 501, 19)) {
for (Mesh &mesh : bmain->meshes) {
bke::mesh_convert_customdata_to_storage(mesh);
}
}
/**
* Always bump subversion in BKE_blender_version.h when adding versioning
* code here, and wrap it inside a MAIN_VERSION_FILE_ATLEAST check.

View file

@ -15,7 +15,11 @@
#include "BLI_listbase.h"
#include "BLI_math_vector.h"
#include "BKE_attribute.hh"
#include "BKE_attribute_legacy_convert.hh"
#include "BKE_attribute_storage.hh"
#include "BKE_customdata.hh"
#include "BKE_geometry_set.hh"
#include "DNA_mesh_types.h"
@ -454,6 +458,23 @@ static BMFace *bm_mesh_copy_new_face(BMesh *bm_new,
return f_new;
}
static CustomData &get_bmesh_custom_data(BMesh &bm, const bke::AttrDomain domain)
{
switch (domain) {
case bke::AttrDomain::Point:
return bm.vdata;
case bke::AttrDomain::Edge:
return bm.edata;
case bke::AttrDomain::Face:
return bm.pdata;
case bke::AttrDomain::Corner:
return bm.ldata;
default:
BLI_assert_unreachable();
return bm.vdata;
}
}
void BM_mesh_copy_init_customdata_from_mesh_array(BMesh *bm_dst,
const Mesh *me_src_array[],
const int me_src_array_len,
@ -464,38 +485,36 @@ void BM_mesh_copy_init_customdata_from_mesh_array(BMesh *bm_dst,
allocsize = &bm_mesh_allocsize_default;
}
bke::GeometrySet::GatheredAttributes attribute_info;
for (int i = 0; i < me_src_array_len; i++) {
const Mesh *me_src = me_src_array[i];
CustomData mesh_vdata = CustomData_shallow_copy_remove_non_bmesh_attributes(
&me_src->vert_data, CD_MASK_BMESH.vmask);
CustomData mesh_edata = CustomData_shallow_copy_remove_non_bmesh_attributes(
&me_src->edge_data, CD_MASK_BMESH.emask);
CustomData mesh_pdata = CustomData_shallow_copy_remove_non_bmesh_attributes(
&me_src->face_data, CD_MASK_BMESH.pmask);
CustomData mesh_ldata = CustomData_shallow_copy_remove_non_bmesh_attributes(
&me_src->corner_data, CD_MASK_BMESH.lmask);
me_src->attribute_storage.wrap().foreach([&](const bke::Attribute &attr) {
if (BM_attribute_stored_in_bmesh_builtin(attr.name())) {
return;
}
attribute_info.add(attr.name(), {attr.domain(), attr.data_type()});
});
}
if (i == 0) {
CustomData_init_layout_from(
&mesh_vdata, &bm_dst->vdata, CD_MASK_BMESH.vmask, CD_SET_DEFAULT, 0);
CustomData_init_layout_from(
&mesh_edata, &bm_dst->edata, CD_MASK_BMESH.emask, CD_SET_DEFAULT, 0);
CustomData_init_layout_from(
&mesh_pdata, &bm_dst->pdata, CD_MASK_BMESH.pmask, CD_SET_DEFAULT, 0);
CustomData_init_layout_from(
&mesh_ldata, &bm_dst->ldata, CD_MASK_BMESH.lmask, CD_SET_DEFAULT, 0);
}
else {
CustomData_merge_layout(&mesh_vdata, &bm_dst->vdata, CD_MASK_BMESH.vmask, CD_SET_DEFAULT, 0);
CustomData_merge_layout(&mesh_edata, &bm_dst->edata, CD_MASK_BMESH.emask, CD_SET_DEFAULT, 0);
CustomData_merge_layout(&mesh_pdata, &bm_dst->pdata, CD_MASK_BMESH.pmask, CD_SET_DEFAULT, 0);
CustomData_merge_layout(&mesh_ldata, &bm_dst->ldata, CD_MASK_BMESH.lmask, CD_SET_DEFAULT, 0);
}
for (const int i : attribute_info.names.index_range()) {
const StringRef name = attribute_info.names[i];
const bke::AttrDomain domain = attribute_info.kinds[i].domain;
const eCustomDataType data_type = *bke::attr_type_to_custom_data_type(
attribute_info.kinds[i].data_type);
CustomData &custom_data = get_bmesh_custom_data(*bm_dst, domain);
CustomData_add_layer_named(&custom_data, data_type, CD_SET_DEFAULT, 0, name);
}
MEM_SAFE_FREE(mesh_vdata.layers);
MEM_SAFE_FREE(mesh_edata.layers);
MEM_SAFE_FREE(mesh_pdata.layers);
MEM_SAFE_FREE(mesh_ldata.layers);
for (int i = 0; i < me_src_array_len; i++) {
const Mesh *me_src = me_src_array[i];
CustomData_merge_layout(
&me_src->vert_data, &bm_dst->vdata, CD_MASK_BMESH.vmask, CD_SET_DEFAULT, 0);
CustomData_merge_layout(
&me_src->edge_data, &bm_dst->edata, CD_MASK_BMESH.emask, CD_SET_DEFAULT, 0);
CustomData_merge_layout(
&me_src->face_data, &bm_dst->pdata, CD_MASK_BMESH.pmask, CD_SET_DEFAULT, 0);
CustomData_merge_layout(
&me_src->corner_data, &bm_dst->ldata, CD_MASK_BMESH.lmask, CD_SET_DEFAULT, 0);
}
CustomData_bmesh_init_pool(&bm_dst->vdata, allocsize->totvert, BM_VERT);

View file

@ -91,6 +91,7 @@
#include "BKE_attribute.h"
#include "BKE_attribute.hh"
#include "BKE_attribute_legacy_convert.hh"
#include "BKE_customdata.hh"
#include "BKE_mesh.hh"
#include "BKE_mesh_runtime.hh"
@ -152,6 +153,45 @@ static BMFace *bm_face_create_from_mpoly(BMesh &bm,
return BM_face_create(&bm, verts.data(), edges.data(), size, nullptr, BM_CREATE_SKIP_CD);
}
static const CustomData &get_bm_custom_data(const BMesh &bm, const bke::AttrDomain domain)
{
switch (domain) {
case bke::AttrDomain::Point:
return bm.vdata;
case bke::AttrDomain::Edge:
return bm.edata;
case bke::AttrDomain::Face:
return bm.pdata;
case bke::AttrDomain::Corner:
return bm.ldata;
default:
BLI_assert_unreachable();
return bm.vdata;
}
}
static const CustomData &get_mesh_custom_data(const Mesh &mesh, const bke::AttrDomain domain)
{
switch (domain) {
case bke::AttrDomain::Point:
return mesh.vert_data;
case bke::AttrDomain::Edge:
return mesh.edge_data;
case bke::AttrDomain::Face:
return mesh.face_data;
case bke::AttrDomain::Corner:
return mesh.corner_data;
default:
BLI_assert_unreachable();
return mesh.vert_data;
}
}
static CustomData &get_mesh_custom_data(Mesh &mesh, const bke::AttrDomain domain)
{
return const_cast<CustomData &>(get_mesh_custom_data(const_cast<const Mesh &>(mesh), domain));
}
struct MeshToBMeshLayerInfo {
eCustomDataType type;
/** The layer's position in the BMesh element's data block. */
@ -165,9 +205,13 @@ struct MeshToBMeshLayerInfo {
/**
* Calculate the necessary information to copy every data layer from the Mesh to the BMesh.
*/
static Vector<MeshToBMeshLayerInfo> mesh_to_bm_copy_info_calc(const CustomData &mesh_data,
static Vector<MeshToBMeshLayerInfo> mesh_to_bm_copy_info_calc(const Mesh &mesh,
const bke::AttrDomain domain,
CustomData &bm_data)
{
const bke::AttributeStorage &storage = mesh.attribute_storage.wrap();
const CustomData &mesh_data = get_mesh_custom_data(mesh, domain);
Vector<MeshToBMeshLayerInfo> infos;
std::array<int, CD_NUMTYPES> per_type_index;
per_type_index.fill(0);
@ -175,15 +219,34 @@ static Vector<MeshToBMeshLayerInfo> mesh_to_bm_copy_info_calc(const CustomData &
const CustomDataLayer &bm_layer = bm_data.layers[i];
const StringRef layer_name = bm_layer.name;
const eCustomDataType type = eCustomDataType(bm_layer.type);
const int mesh_layer_index =
bm_layer.name[0] == '\0' ?
CustomData_get_layer_index_n(&mesh_data, type, per_type_index[type]) :
CustomData_get_named_layer_index(&mesh_data, type, layer_name);
MeshToBMeshLayerInfo info{};
info.type = type;
info.bmesh_offset = bm_layer.offset;
info.mesh_data = (mesh_layer_index == -1) ? nullptr : mesh_data.layers[mesh_layer_index].data;
if (const bke::Attribute *attr = storage.lookup(layer_name)) {
switch (attr->storage_type()) {
case bke::AttrStorageType::Array: {
const auto &array_data = std::get<bke::Attribute::ArrayData>(attr->data());
info.mesh_data = array_data.data;
break;
}
case bke::AttrStorageType::Single: {
BLI_assert_unreachable();
info.mesh_data = nullptr;
break;
}
}
}
else {
const int mesh_layer_index =
layer_name.is_empty() ?
CustomData_get_layer_index_n(&mesh_data, type, per_type_index[type]) :
CustomData_get_named_layer_index(&mesh_data, type, layer_name);
if (mesh_layer_index != -1) {
BLI_assert((CD_TYPE_AS_MASK(type) & CD_MASK_PROP_ALL) == 0);
info.mesh_data = mesh_data.layers[mesh_layer_index].data;
}
}
info.elem_size = CustomData_get_elem_size(&bm_layer);
infos.append(info);
@ -210,6 +273,36 @@ static void mesh_attributes_copy_to_bmesh_block(CustomData &data,
}
}
static CustomData get_mesh_to_bm_custom_data(const Mesh &mesh,
const bke::AttrDomain domain,
const uint64_t cd_type_mask)
{
CustomData custom_data;
CustomData_reset(&custom_data);
mesh.attribute_storage.wrap().foreach([&](const bke::Attribute &attr) {
if (attr.domain() != domain) {
return;
}
if (BM_attribute_stored_in_bmesh_builtin(attr.name())) {
return;
}
const eCustomDataType data_type = *bke::attr_type_to_custom_data_type(attr.data_type());
if ((CD_TYPE_AS_MASK(data_type) & cd_type_mask) == 0) {
return;
}
CustomData_add_layer_named(&custom_data, data_type, CD_SET_DEFAULT, 0, attr.name());
});
const CustomData &mesh_data = get_mesh_custom_data(mesh, domain);
for (const CustomDataLayer &layer : Span(mesh_data.layers, mesh_data.totlayer)) {
if ((CD_TYPE_AS_MASK(eCustomDataType(layer.type)) & cd_type_mask) == 0) {
continue;
}
CustomData_add_layer_named(
&custom_data, eCustomDataType(layer.type), CD_SET_DEFAULT, 0, layer.name);
}
return custom_data;
}
void BM_mesh_bm_from_me(BMesh *bm, const Mesh *mesh, const BMeshFromMeshParams *params)
{
if (!mesh) {
@ -223,14 +316,10 @@ void BM_mesh_bm_from_me(BMesh *bm, const Mesh *mesh, const BMeshFromMeshParams *
CustomData_MeshMasks mask = CD_MASK_BMESH;
CustomData_MeshMasks_update(&mask, &params->cd_mask_extra);
CustomData mesh_vdata = CustomData_shallow_copy_remove_non_bmesh_attributes(&mesh->vert_data,
mask.vmask);
CustomData mesh_edata = CustomData_shallow_copy_remove_non_bmesh_attributes(&mesh->edge_data,
mask.emask);
CustomData mesh_pdata = CustomData_shallow_copy_remove_non_bmesh_attributes(&mesh->face_data,
mask.pmask);
CustomData mesh_ldata = CustomData_shallow_copy_remove_non_bmesh_attributes(&mesh->corner_data,
mask.lmask);
CustomData mesh_vdata = get_mesh_to_bm_custom_data(*mesh, bke::AttrDomain::Point, mask.vmask);
CustomData mesh_edata = get_mesh_to_bm_custom_data(*mesh, bke::AttrDomain::Edge, mask.emask);
CustomData mesh_pdata = get_mesh_to_bm_custom_data(*mesh, bke::AttrDomain::Face, mask.pmask);
CustomData mesh_ldata = get_mesh_to_bm_custom_data(*mesh, bke::AttrDomain::Corner, mask.lmask);
Vector<std::string> temporary_layers_to_delete;
@ -393,10 +482,14 @@ void BM_mesh_bm_from_me(BMesh *bm, const Mesh *mesh, const BMeshFromMeshParams *
}
}
const Vector<MeshToBMeshLayerInfo> vert_info = mesh_to_bm_copy_info_calc(mesh_vdata, bm->vdata);
const Vector<MeshToBMeshLayerInfo> edge_info = mesh_to_bm_copy_info_calc(mesh_edata, bm->edata);
const Vector<MeshToBMeshLayerInfo> poly_info = mesh_to_bm_copy_info_calc(mesh_pdata, bm->pdata);
const Vector<MeshToBMeshLayerInfo> loop_info = mesh_to_bm_copy_info_calc(mesh_ldata, bm->ldata);
const Vector<MeshToBMeshLayerInfo> vert_info = mesh_to_bm_copy_info_calc(
*mesh, bke::AttrDomain::Point, bm->vdata);
const Vector<MeshToBMeshLayerInfo> edge_info = mesh_to_bm_copy_info_calc(
*mesh, bke::AttrDomain::Edge, bm->edata);
const Vector<MeshToBMeshLayerInfo> poly_info = mesh_to_bm_copy_info_calc(
*mesh, bke::AttrDomain::Face, bm->pdata);
const Vector<MeshToBMeshLayerInfo> loop_info = mesh_to_bm_copy_info_calc(
*mesh, bke::AttrDomain::Corner, bm->ldata);
if (is_new) {
CustomData_bmesh_init_pool(&bm->vdata, mesh->verts_num, BM_VERT);
CustomData_bmesh_init_pool(&bm->edata, mesh->edges_num, BM_EDGE);
@ -1074,7 +1167,7 @@ struct BMeshToMeshLayerInfo {
eCustomDataType type;
/** The layer's position in the BMesh element's data block. */
int bmesh_offset;
/** The mesh's #CustomDataLayer::data. When null, the BMesh block is set to its default value. */
/** The array data in the mesh. */
void *mesh_data;
/** The size of every custom data element. */
size_t elem_size;
@ -1084,8 +1177,11 @@ struct BMeshToMeshLayerInfo {
* Calculate the necessary information to copy every data layer from the BMesh to the Mesh.
*/
static Vector<BMeshToMeshLayerInfo> bm_to_mesh_copy_info_calc(const CustomData &bm_data,
CustomData &mesh_data)
const bke::AttrDomain domain,
Mesh &mesh)
{
bke::AttributeStorage &storage = mesh.attribute_storage.wrap();
CustomData &mesh_data = get_mesh_custom_data(mesh, domain);
Vector<BMeshToMeshLayerInfo> infos;
std::array<int, CD_NUMTYPES> per_type_index;
per_type_index.fill(0);
@ -1116,6 +1212,26 @@ static Vector<BMeshToMeshLayerInfo> bm_to_mesh_copy_info_calc(const CustomData &
per_type_index[type]++;
}
storage.foreach([&](bke::Attribute &attr) {
if (attr.domain() != domain) {
return;
}
const eCustomDataType cd_type = *bke::attr_type_to_custom_data_type(attr.data_type());
const int bm_layer_index = CustomData_get_named_layer_index(&bm_data, cd_type, attr.name());
if (bm_layer_index == -1) {
return;
}
const CustomDataLayer &bm_layer = bm_data.layers[bm_layer_index];
if (bm_layer.flag & CD_FLAG_NOCOPY) {
return;
}
BMeshToMeshLayerInfo info{};
info.type = cd_type;
info.bmesh_offset = bm_layer.offset;
info.mesh_data = std::get<bke::Attribute::ArrayData>(attr.data_for_write()).data;
info.elem_size = bke::attribute_type_to_cpp_type(attr.data_type()).size;
infos.append(info);
});
return infos;
}
@ -1245,10 +1361,8 @@ static void bm_to_mesh_verts(const BMesh &bm,
MutableSpan<bool> select_vert,
MutableSpan<bool> hide_vert)
{
CustomData_free_layer_named(&mesh.vert_data, "position");
CustomData_add_layer_named(
&mesh.vert_data, CD_PROP_FLOAT3, CD_CONSTRUCT, mesh.verts_num, "position");
const Vector<BMeshToMeshLayerInfo> info = bm_to_mesh_copy_info_calc(bm.vdata, mesh.vert_data);
const Vector<BMeshToMeshLayerInfo> info = bm_to_mesh_copy_info_calc(
bm.vdata, bke::AttrDomain::Point, mesh);
MutableSpan<float3> dst_vert_positions = mesh.vert_positions_for_write();
std::atomic<bool> any_loose_vert = false;
@ -1288,10 +1402,8 @@ static void bm_to_mesh_edges(const BMesh &bm,
MutableSpan<bool> sharp_edge,
MutableSpan<bool> uv_seams)
{
CustomData_free_layer_named(&mesh.edge_data, ".edge_verts");
CustomData_add_layer_named(
&mesh.edge_data, CD_PROP_INT32_2D, CD_CONSTRUCT, mesh.edges_num, ".edge_verts");
const Vector<BMeshToMeshLayerInfo> info = bm_to_mesh_copy_info_calc(bm.edata, mesh.edge_data);
const Vector<BMeshToMeshLayerInfo> info = bm_to_mesh_copy_info_calc(
bm.edata, bke::AttrDomain::Edge, mesh);
MutableSpan<int2> dst_edges = mesh.edges_for_write();
std::atomic<bool> any_loose_edge = false;
@ -1343,7 +1455,8 @@ static void bm_to_mesh_faces(const BMesh &bm,
MutableSpan<int> material_indices)
{
BKE_mesh_face_offsets_ensure_alloc(&mesh);
const Vector<BMeshToMeshLayerInfo> info = bm_to_mesh_copy_info_calc(bm.pdata, mesh.face_data);
const Vector<BMeshToMeshLayerInfo> info = bm_to_mesh_copy_info_calc(
bm.pdata, bke::AttrDomain::Face, mesh);
MutableSpan<int> dst_face_offsets = mesh.face_offsets_for_write();
threading::parallel_for(bm_faces.index_range(), 1024, [&](const IndexRange range) {
for (const int face_i : range) {
@ -1379,19 +1492,41 @@ static void bm_to_mesh_faces(const BMesh &bm,
});
}
static void add_bm_cd_to_mesh(const BMesh &bm,
const bke::AttrDomain domain,
const uint64_t cd_type_mask,
Mesh &mesh)
{
const CustomData &bm_data = get_bm_custom_data(bm, domain);
CustomData &mesh_data = get_mesh_custom_data(mesh, domain);
bke::MutableAttributeAccessor attrs = mesh.attributes_for_write();
const int domain_size = attrs.domain_size(domain);
for (const CustomDataLayer &layer : Span(bm_data.layers, bm_data.totlayer)) {
if (layer.flag & CD_FLAG_NOCOPY) {
continue;
}
const eCustomDataType cd_type = eCustomDataType(layer.type);
if (const std::optional<bke::AttrType> attr_type = bke::custom_data_type_to_attr_type(cd_type))
{
attrs.add(layer.name, domain, *attr_type, bke::AttributeInitConstruct());
}
else {
if ((CD_TYPE_AS_MASK(cd_type) & cd_type_mask) == 0) {
continue;
}
CustomData_add_layer_named(&mesh_data, cd_type, CD_CONSTRUCT, domain_size, layer.name);
}
}
}
static void bm_to_mesh_loops(const BMesh &bm,
const Span<const BMLoop *> bm_loops,
Mesh &mesh,
MutableSpan<bool> uv_select_vert,
MutableSpan<bool> uv_select_edge)
{
CustomData_free_layer_named(&mesh.corner_data, ".corner_vert");
CustomData_free_layer_named(&mesh.corner_data, ".corner_edge");
CustomData_add_layer_named(
&mesh.corner_data, CD_PROP_INT32, CD_CONSTRUCT, mesh.corners_num, ".corner_vert");
CustomData_add_layer_named(
&mesh.corner_data, CD_PROP_INT32, CD_CONSTRUCT, mesh.corners_num, ".corner_edge");
const Vector<BMeshToMeshLayerInfo> info = bm_to_mesh_copy_info_calc(bm.ldata, mesh.corner_data);
const Vector<BMeshToMeshLayerInfo> info = bm_to_mesh_copy_info_calc(
bm.ldata, bke::AttrDomain::Corner, mesh);
MutableSpan<int> dst_corner_verts = mesh.corner_verts_for_write();
MutableSpan<int> dst_corner_edges = mesh.corner_edges_for_write();
@ -1480,14 +1615,10 @@ void BM_mesh_bm_to_me(Main *bmain, BMesh *bm, Mesh *mesh, const BMeshToMeshParam
{
CustomData_MeshMasks mask = CD_MASK_MESH;
CustomData_MeshMasks_update(&mask, &params->cd_mask_extra);
CustomData_init_layout_from(
&bm->vdata, &mesh->vert_data, mask.vmask, CD_CONSTRUCT, mesh->verts_num);
CustomData_init_layout_from(
&bm->edata, &mesh->edge_data, mask.emask, CD_CONSTRUCT, mesh->edges_num);
CustomData_init_layout_from(
&bm->ldata, &mesh->corner_data, mask.lmask, CD_CONSTRUCT, mesh->corners_num);
CustomData_init_layout_from(
&bm->pdata, &mesh->face_data, mask.pmask, CD_CONSTRUCT, mesh->faces_num);
add_bm_cd_to_mesh(*bm, bke::AttrDomain::Point, mask.vmask, *mesh);
add_bm_cd_to_mesh(*bm, bke::AttrDomain::Edge, mask.emask, *mesh);
add_bm_cd_to_mesh(*bm, bke::AttrDomain::Face, mask.pmask, *mesh);
add_bm_cd_to_mesh(*bm, bke::AttrDomain::Corner, mask.lmask, *mesh);
}
if (const char *name = CustomData_get_active_layer_name(&bm->ldata, CD_PROP_FLOAT2)) {
@ -1555,6 +1686,11 @@ void BM_mesh_bm_to_me(Main *bmain, BMesh *bm, Mesh *mesh, const BMeshToMeshParam
AttrDomain::Face);
}
attrs.add<float3>("position", bke::AttrDomain::Point, bke::AttributeInitConstruct());
attrs.add<int2>(".edge_verts", bke::AttrDomain::Edge, bke::AttributeInitConstruct());
attrs.add<int>(".corner_vert", bke::AttrDomain::Corner, bke::AttributeInitConstruct());
attrs.add<int>(".corner_edge", bke::AttrDomain::Corner, bke::AttributeInitConstruct());
/* Loop over all elements in parallel, copying attributes and building the Mesh topology. */
threading::parallel_invoke(
(mesh->faces_num + mesh->edges_num) > 1024,
@ -1733,12 +1869,12 @@ void BM_mesh_bm_to_me_compact(BMesh &bm,
});
bm.elem_index_dirty &= ~(BM_VERT | BM_EDGE | BM_FACE | BM_LOOP);
if (mask) {
CustomData_merge_layout(&bm.vdata, &mesh.vert_data, mask->vmask, CD_CONSTRUCT, mesh.verts_num);
CustomData_merge_layout(&bm.edata, &mesh.edge_data, mask->emask, CD_CONSTRUCT, mesh.edges_num);
CustomData_merge_layout(
&bm.ldata, &mesh.corner_data, mask->lmask, CD_CONSTRUCT, mesh.corners_num);
CustomData_merge_layout(&bm.pdata, &mesh.face_data, mask->pmask, CD_CONSTRUCT, mesh.faces_num);
if (add_mesh_attributes) {
const CustomData_MeshMasks &mask_final = mask ? *mask : CD_MASK_DERIVEDMESH;
add_bm_cd_to_mesh(bm, bke::AttrDomain::Point, mask_final.vmask, mesh);
add_bm_cd_to_mesh(bm, bke::AttrDomain::Edge, mask_final.emask, mesh);
add_bm_cd_to_mesh(bm, bke::AttrDomain::Face, mask_final.pmask, mesh);
add_bm_cd_to_mesh(bm, bke::AttrDomain::Corner, mask_final.lmask, mesh);
}
{
@ -1820,6 +1956,11 @@ void BM_mesh_bm_to_me_compact(BMesh &bm,
}
}
attrs.add<float3>("position", bke::AttrDomain::Point, bke::AttributeInitConstruct());
attrs.add<int2>(".edge_verts", bke::AttrDomain::Edge, bke::AttributeInitConstruct());
attrs.add<int>(".corner_vert", bke::AttrDomain::Corner, bke::AttributeInitConstruct());
attrs.add<int>(".corner_edge", bke::AttrDomain::Corner, bke::AttributeInitConstruct());
/* Loop over all elements in parallel, copying attributes and building the Mesh topology. */
threading::parallel_invoke(
use_threading,

View file

@ -336,19 +336,22 @@ static wmOperatorStatus geometry_attribute_add_exec(bContext *C, wmOperator *op)
if (owner.type() == AttributeOwnerType::Mesh) {
Mesh &mesh = *id_cast<Mesh *>(id);
CustomDataLayer *layer = BKE_attribute_new(owner, name, cd_type, domain, op->reports);
if (layer == nullptr) {
return OPERATOR_CANCELLED;
if (BMEditMesh *em = mesh.runtime->edit_mesh.get()) {
CustomDataLayer *layer = BKE_attribute_new(
mesh, *em->bm, name, cd_type, domain, op->reports);
if (layer == nullptr) {
return OPERATOR_CANCELLED;
}
const StringRefNull new_name = layer->name;
BKE_attributes_active_set(owner, new_name);
set_active_default_status_on_add(mesh, domain, type, new_name);
DEG_id_tag_update(id, ID_RECALC_GEOMETRY);
WM_main_add_notifier(NC_GEOM | ND_DATA, id);
return OPERATOR_FINISHED;
}
const StringRefNull new_name = layer->name;
BKE_attributes_active_set(owner, new_name);
set_active_default_status_on_add(mesh, domain, type, new_name);
DEG_id_tag_update(id, ID_RECALC_GEOMETRY);
WM_main_add_notifier(NC_GEOM | ND_DATA, id);
return OPERATOR_FINISHED;
}
bke::MutableAttributeAccessor accessor = *owner.get_accessor();
@ -368,6 +371,10 @@ static wmOperatorStatus geometry_attribute_add_exec(bContext *C, wmOperator *op)
BKE_attributes_active_set(owner, attr.name());
if (owner.type() == AttributeOwnerType::Mesh) {
set_active_default_status_on_add(*owner.get_mesh(), domain, type, attr.name());
}
DEG_id_tag_update(id, ID_RECALC_GEOMETRY);
WM_main_add_notifier(NC_GEOM | ND_DATA, id);
@ -498,19 +505,22 @@ static wmOperatorStatus geometry_color_attribute_add_exec(bContext *C, wmOperato
const std::string unique_name = BKE_attribute_calc_unique_name(owner, name);
if (owner.type() == AttributeOwnerType::Mesh) {
CustomDataLayer *layer = BKE_attribute_new(owner, unique_name, type, domain, op->reports);
if (layer == nullptr) {
return OPERATOR_CANCELLED;
Mesh *mesh = owner.get_mesh();
if (BMEditMesh *em = mesh->runtime->edit_mesh.get()) {
CustomDataLayer *layer = BKE_attribute_new(*mesh, *em->bm, name, type, domain, op->reports);
if (layer == nullptr) {
return OPERATOR_CANCELLED;
}
BKE_id_attributes_active_color_set(id, unique_name);
if (!BKE_id_attributes_color_find(id, BKE_id_attributes_default_color_name(id).value_or("")))
{
BKE_id_attributes_default_color_set(id, unique_name);
}
sculpt_paint::object_active_color_fill(*ob, color, false);
DEG_id_tag_update(id, ID_RECALC_GEOMETRY);
WM_main_add_notifier(NC_GEOM | ND_DATA, id);
return OPERATOR_FINISHED;
}
BKE_id_attributes_active_color_set(id, unique_name);
if (!BKE_id_attributes_color_find(id, BKE_id_attributes_default_color_name(id).value_or(""))) {
BKE_id_attributes_default_color_set(id, unique_name);
}
sculpt_paint::object_active_color_fill(*ob, color, false);
DEG_id_tag_update(id, ID_RECALC_GEOMETRY);
WM_main_add_notifier(NC_GEOM | ND_DATA, id);
return OPERATOR_FINISHED;
}
bke::MutableAttributeAccessor attributes = *owner.get_accessor();

View file

@ -23,10 +23,12 @@
#include "BLI_implicit_sharing.hh"
#include "BLI_listbase.h"
#include "BLI_math_base.h"
#include "BLI_multi_value_map.hh"
#include "BLI_string.h"
#include "BLI_task.hh"
#include "BLI_vector.hh"
#include "BKE_attribute_legacy_convert.hh"
#include "BKE_context.hh"
#include "BKE_customdata.hh"
#include "BKE_deform.hh"
@ -125,9 +127,9 @@ struct BArrayCustomData {
* Non-trivial data is just stored directly since allocated arrays cannot be easily deduplicated
* with the array-store system.
*/
Map<eCustomDataType, Array<ImplicitSharingInfoAndData>> non_trivial_arrays;
Map<eCustomDataType, Vector<ImplicitSharingInfoAndData>> non_trivial_arrays;
/** Array-store states for custom data layers of each type. */
Map<eCustomDataType, Array<BArrayState *>> trivial_arrays;
Map<eCustomDataType, Vector<BArrayState *>> trivial_arrays;
};
# ifdef USE_ARRAY_STORE_RLE
@ -219,104 +221,124 @@ static struct {
} um_arraystore = {{{nullptr}}};
static void store_layer(const eCustomDataType type,
const void *data,
const ImplicitSharingInfo *sharing_info,
const size_t data_len,
const int bs_index,
const BArrayCustomData *bcd_reference,
Map<eCustomDataType, int> &index_in_type,
BArrayCustomData &bcd)
{
int &i = index_in_type.lookup_or_add(type, 0);
BLI_SCOPED_DEFER([&]() { i++; });
/* Perform a full copy on dynamic layers.
*
* Unfortunately we can't compare dynamic layer types as they contain allocated pointers,
* which burns CPU cycles looking for duplicate data that doesn't exist.
* The array data isn't comparable once copied from the mesh,
* this bottlenecks on high poly meshes, see #84114.
*
* Ideally the data would be expanded into a format that could be de-duplicated effectively,
* this would require a flat representation of each dynamic custom-data layer.
*
* Instead, these non-trivial custom data layer are stored in the undo system using implicit
* sharing, to avoid the copy from the undo mesh.
*/
if (CustomData_layertype_is_dynamic(type)) {
ImplicitSharingInfoAndData state = {sharing_info, data};
state.sharing_info->add_user();
bcd.non_trivial_arrays.lookup_or_add_default(type).append(state);
return;
}
Vector<BArrayState *> &states = bcd.trivial_arrays.lookup_or_add_default(type);
const int stride = CustomData_sizeof(type);
BArrayStore *bs = BLI_array_store_at_size_ensure(
&um_arraystore.bs_stride[bs_index], stride, array_chunk_size_calc(stride));
const Vector<BArrayState *> *bcd_reference_current =
bcd_reference ? bcd_reference->trivial_arrays.lookup_ptr(type) : nullptr;
if (!data) {
states.append(nullptr);
return;
}
const BArrayState *state_reference = nullptr;
if (bcd_reference_current && i < bcd_reference_current->size()) {
state_reference = (*bcd_reference_current)[i];
}
const void *data_final = data;
size_t data_final_size = size_t(data_len) * stride;
# ifdef USE_ARRAY_STORE_RLE
const bool use_rle = um_customdata_layer_use_rle(type);
uint8_t *data_enc = nullptr;
if (use_rle) {
/* Store the size in the encoded data (for convenience). */
size_t data_enc_extra_size = sizeof(size_t);
size_t data_enc_len;
data_enc = BLI_array_store_rle_encode(reinterpret_cast<const uint8_t *>(data_final),
data_final_size,
data_enc_extra_size,
&data_enc_len);
memcpy(data_enc, &data_final_size, data_enc_extra_size);
data_final = data_enc;
data_final_size = data_enc_extra_size + data_enc_len;
}
# endif
states.append(BLI_array_store_state_add(bs, data_final, data_final_size, state_reference));
# ifdef USE_ARRAY_STORE_RLE
if (use_rle) {
MEM_freeN(data_enc);
}
# endif
}
static BArrayCustomData *um_arraystore_cd_create(CustomData *cdata,
Span<bke::Attribute *> attributes,
const size_t data_len,
const int bs_index,
const BArrayCustomData *bcd_reference)
{
BArrayCustomData bcd;
MutableSpan all_layers(cdata->layers, cdata->totlayer);
Map<eCustomDataType, int> index_in_type;
for (int layer_start = 0, layer_end; layer_start < cdata->totlayer; layer_start = layer_end) {
const eCustomDataType type = eCustomDataType(cdata->layers[layer_start].type);
for (CustomDataLayer &layer : MutableSpan(cdata->layers, cdata->totlayer)) {
store_layer(eCustomDataType(layer.type),
layer.data,
layer.sharing_info,
data_len,
bs_index,
bcd_reference,
index_in_type,
bcd);
layer.sharing_info->remove_user_and_delete_if_last();
layer.sharing_info = nullptr;
layer.data = nullptr;
}
layer_end = layer_start + 1;
while ((layer_end < cdata->totlayer) && (type == cdata->layers[layer_end].type)) {
layer_end++;
}
MutableSpan layers_with_type = all_layers.slice(layer_start, layer_end - layer_start);
/* Perform a full copy on dynamic layers.
*
* Unfortunately we can't compare dynamic layer types as they contain allocated pointers,
* which burns CPU cycles looking for duplicate data that doesn't exist.
* The array data isn't comparable once copied from the mesh,
* this bottlenecks on high poly meshes, see #84114.
*
* Ideally the data would be expanded into a format that could be de-duplicated effectively,
* this would require a flat representation of each dynamic custom-data layer.
*
* Instead, these non-trivial custom data layer are stored in the undo system using implicit
* sharing, to avoid the copy from the undo mesh.
*/
if (CustomData_layertype_is_dynamic(type)) {
Array<ImplicitSharingInfoAndData> states(layers_with_type.size());
for (const int i : layers_with_type.index_range()) {
CustomDataLayer &layer = layers_with_type[i];
states[i] = {layers_with_type[i].sharing_info, layers_with_type[i].data};
layer.sharing_info = nullptr;
layer.data = nullptr;
}
bcd.non_trivial_arrays.add_new(type, std::move(states));
continue;
}
Array<BArrayState *> states(layers_with_type.size());
const int stride = CustomData_sizeof(type);
BArrayStore *bs = BLI_array_store_at_size_ensure(
&um_arraystore.bs_stride[bs_index], stride, array_chunk_size_calc(stride));
const Array<BArrayState *> *bcd_reference_current =
bcd_reference ? bcd_reference->trivial_arrays.lookup_ptr(type) : nullptr;
for (const int i : layers_with_type.index_range()) {
CustomDataLayer &layer = layers_with_type[i];
if (!layer.data) {
states[i] = nullptr;
continue;
}
const BArrayState *state_reference = nullptr;
if (bcd_reference_current && i < bcd_reference_current->size()) {
state_reference = (*bcd_reference_current)[i];
}
void *data_final = layer.data;
size_t data_final_size = size_t(data_len) * stride;
# ifdef USE_ARRAY_STORE_RLE
const bool use_rle = um_customdata_layer_use_rle(type);
uint8_t *data_enc = nullptr;
if (use_rle) {
/* Store the size in the encoded data (for convenience). */
size_t data_enc_extra_size = sizeof(size_t);
size_t data_enc_len;
data_enc = BLI_array_store_rle_encode(reinterpret_cast<const uint8_t *>(data_final),
data_final_size,
data_enc_extra_size,
&data_enc_len);
memcpy(data_enc, &data_final_size, data_enc_extra_size);
data_final = data_enc;
data_final_size = data_enc_extra_size + data_enc_len;
}
# endif
states[i] = BLI_array_store_state_add(bs, data_final, data_final_size, state_reference);
# ifdef USE_ARRAY_STORE_RLE
if (use_rle) {
MEM_freeN(data_enc);
}
# endif
layer.sharing_info->remove_user_and_delete_if_last();
layer.sharing_info = nullptr;
layer.data = nullptr;
}
bcd.trivial_arrays.add_new(type, std::move(states));
for (bke::Attribute *attribute : attributes) {
const eCustomDataType type = *bke::attr_type_to_custom_data_type(attribute->data_type());
const bke::Attribute::DataVariant &data = attribute->data();
BLI_assert(std::holds_alternative<bke::Attribute::ArrayData>(data));
const bke::Attribute::ArrayData &array_data = std::get<bke::Attribute::ArrayData>(data);
store_layer(type,
array_data.data,
array_data.sharing_info.get(),
data_len,
bs_index,
bcd_reference,
index_in_type,
bcd);
attribute->assign_data({});
}
if (bcd.trivial_arrays.is_empty() && bcd.non_trivial_arrays.is_empty()) {
@ -337,6 +359,39 @@ static void um_arraystore_cd_clear(CustomData *cdata)
}
}
static void *get_arraystore_data(const BArrayState *state,
const size_t data_len,
const eCustomDataType type)
{
size_t state_len;
void *data = BLI_array_store_state_data_get_alloc(state, &state_len);
const int stride = CustomData_sizeof(type);
# ifdef USE_ARRAY_STORE_RLE
const bool use_rle = um_customdata_layer_use_rle(type);
if (use_rle) {
/* Store the size in the encoded data (for convenience). */
size_t data_enc_extra_size = sizeof(size_t);
const uint8_t *data_enc = reinterpret_cast<uint8_t *>(data);
size_t data_dec_len;
memcpy(&data_dec_len, data_enc, sizeof(size_t));
uint8_t *data_dec = MEM_malloc_arrayN<uint8_t>(data_dec_len, __func__);
BLI_array_store_rle_decode(
data_enc + data_enc_extra_size, state_len - data_enc_extra_size, data_dec, data_dec_len);
MEM_freeN(data);
data = static_cast<void *>(data_dec);
/* Just for the assert to succeed. */
state_len = data_dec_len;
}
# endif
BLI_assert(stride * data_len == state_len);
UNUSED_VARS_NDEBUG(stride, data_len);
return data;
}
/**
* \note There is no room for data going out of sync here.
* The layers and the states are stored together so this can be kept working.
@ -345,73 +400,69 @@ static void um_arraystore_cd_clear(CustomData *cdata)
*/
static void um_arraystore_cd_expand(const BArrayCustomData *bcd,
CustomData *cdata,
Span<bke::Attribute *> attributes,
const size_t data_len)
{
if (bcd == nullptr) {
return;
}
MutableSpan all_layers(cdata->layers, cdata->totlayer);
for (const auto &item : bcd->non_trivial_arrays.items()) {
const eCustomDataType type = item.key;
const Span<ImplicitSharingInfoAndData> states = item.value;
MutableSpan layers_with_type = all_layers.slice(CustomData_get_layer_index(cdata, type),
CustomData_number_of_layers(cdata, type));
for (const int i : layers_with_type.index_range()) {
CustomDataLayer &layer = layers_with_type[i];
if (!states[i].data) {
Map<eCustomDataType, int> index_in_type;
for (CustomDataLayer &layer : MutableSpan(cdata->layers, cdata->totlayer)) {
const eCustomDataType type = eCustomDataType(layer.type);
int &i = index_in_type.lookup_or_add(type, 0);
BLI_SCOPED_DEFER([&]() { i++; });
if (bcd->non_trivial_arrays.contains(type)) {
const ImplicitSharingInfoAndData &state = bcd->non_trivial_arrays.lookup(type)[i];
if (!state.data) {
BLI_assert(data_len == 0);
layer.data = nullptr;
layer.sharing_info = nullptr;
continue;
}
layer.data = const_cast<void *>(states[i].data);
layer.sharing_info = states[i].sharing_info;
layer.data = const_cast<void *>(state.data);
layer.sharing_info = state.sharing_info;
layer.sharing_info->add_user();
continue;
}
const BArrayState *state = bcd->trivial_arrays.lookup(type)[i];
if (!state) {
layer.data = nullptr;
layer.sharing_info = nullptr;
continue;
}
void *data = get_arraystore_data(state, data_len, type);
layer.data = data;
layer.sharing_info = implicit_sharing::info_for_mem_free(data);
}
for (const auto &item : bcd->trivial_arrays.items()) {
const eCustomDataType type = item.key;
const Span<BArrayState *> states = item.value;
MutableSpan layers_with_type = all_layers.slice(CustomData_get_layer_index(cdata, type),
CustomData_number_of_layers(cdata, type));
const int stride = CustomData_sizeof(type);
for (const int i : layers_with_type.index_range()) {
CustomDataLayer &layer = layers_with_type[i];
if (!states[i]) {
layer.data = nullptr;
continue;
}
for (bke::Attribute *attribute : attributes) {
const eCustomDataType type = *bke::attr_type_to_custom_data_type(attribute->data_type());
size_t state_len;
void *data = BLI_array_store_state_data_get_alloc(states[i], &state_len);
int &i = index_in_type.lookup_or_add(type, 0);
BLI_SCOPED_DEFER([&]() { i++; });
# ifdef USE_ARRAY_STORE_RLE
const bool use_rle = um_customdata_layer_use_rle(type);
if (use_rle) {
/* Store the size in the encoded data (for convenience). */
size_t data_enc_extra_size = sizeof(size_t);
const uint8_t *data_enc = reinterpret_cast<uint8_t *>(data);
size_t data_dec_len;
memcpy(&data_dec_len, data_enc, sizeof(size_t));
uint8_t *data_dec = MEM_malloc_arrayN<uint8_t>(data_dec_len, __func__);
BLI_array_store_rle_decode(data_enc + data_enc_extra_size,
state_len - data_enc_extra_size,
data_dec,
data_dec_len);
MEM_freeN(data);
data = static_cast<void *>(data_dec);
/* Just for the assert to succeed. */
state_len = data_dec_len;
}
# endif
layer.data = data;
layer.sharing_info = implicit_sharing::info_for_mem_free(layer.data);
BLI_assert(stride * data_len == state_len);
UNUSED_VARS_NDEBUG(stride, data_len);
bke::Attribute::ArrayData array_data{};
if (bcd->non_trivial_arrays.contains(type)) {
const ImplicitSharingInfoAndData &state = bcd->non_trivial_arrays.lookup(type)[i];
array_data.data = const_cast<void *>(state.data);
array_data.size = data_len;
array_data.sharing_info = ImplicitSharingPtr<>(state.sharing_info);
array_data.sharing_info->add_user();
}
else {
const BArrayState *state = bcd->trivial_arrays.lookup(type)[i];
array_data.data = get_arraystore_data(state, data_len, type);
array_data.size = data_len;
array_data.sharing_info = ImplicitSharingPtr<>(
implicit_sharing::info_for_mem_free(array_data.data));
}
attribute->assign_data(std::move(array_data));
}
}
@ -421,7 +472,7 @@ static void um_arraystore_cd_free(BArrayCustomData *bcd, const int bs_index)
return;
}
for (Array<ImplicitSharingInfoAndData> &states : bcd->non_trivial_arrays.values()) {
for (Vector<ImplicitSharingInfoAndData> &states : bcd->non_trivial_arrays.values()) {
for (ImplicitSharingInfoAndData &state : states) {
if (state.data) {
state.sharing_info->remove_user_and_delete_if_last();
@ -441,6 +492,14 @@ static void um_arraystore_cd_free(BArrayCustomData *bcd, const int bs_index)
MEM_delete(bcd);
}
static MultiValueMap<bke::AttrDomain, bke::Attribute *> get_attributes_by_domain(Mesh &mesh)
{
MultiValueMap<bke::AttrDomain, bke::Attribute *> result;
mesh.attribute_storage.wrap().foreach(
[&](bke::Attribute &attr) { result.add(attr.domain(), &attr); });
return result;
}
/**
* Move data from allocated arrays to de-duplicated states and clear arrays.
*/
@ -463,28 +522,34 @@ static void um_arraystore_compact(UndoMesh *um, const UndoMesh *um_ref)
const bool use_threading = false;
# endif
MultiValueMap<bke::AttrDomain, bke::Attribute *> attributes = get_attributes_by_domain(*mesh);
threading::parallel_invoke(
use_threading,
[&]() {
um->store.vdata = um_arraystore_cd_create(&mesh->vert_data,
attributes.lookup(bke::AttrDomain::Point),
mesh->verts_num,
ARRAY_STORE_INDEX_VERT,
um_ref ? um_ref->store.vdata : nullptr);
},
[&]() {
um->store.edata = um_arraystore_cd_create(&mesh->edge_data,
attributes.lookup(bke::AttrDomain::Edge),
mesh->edges_num,
ARRAY_STORE_INDEX_EDGE,
um_ref ? um_ref->store.edata : nullptr);
},
[&]() {
um->store.ldata = um_arraystore_cd_create(&mesh->corner_data,
attributes.lookup(bke::AttrDomain::Corner),
mesh->corners_num,
ARRAY_STORE_INDEX_LOOP,
um_ref ? um_ref->store.ldata : nullptr);
},
[&]() {
um->store.pdata = um_arraystore_cd_create(&mesh->face_data,
attributes.lookup(bke::AttrDomain::Face),
mesh->faces_num,
ARRAY_STORE_INDEX_POLY,
um_ref ? um_ref->store.pdata : nullptr);
@ -563,6 +628,7 @@ static void um_arraystore_expand_clear(UndoMesh *um)
um_arraystore_cd_clear(&mesh->edge_data);
um_arraystore_cd_clear(&mesh->corner_data);
um_arraystore_cd_clear(&mesh->face_data);
mesh->attribute_storage.wrap().foreach([&](bke::Attribute &attr) { attr.assign_data({}); });
if (mesh->face_offset_indices) {
implicit_sharing::free_shared_data(&mesh->face_offset_indices,
&mesh->runtime->face_offsets_sharing_info);
@ -654,10 +720,24 @@ static void um_arraystore_expand(UndoMesh *um)
{
Mesh *mesh = um->mesh;
um_arraystore_cd_expand(um->store.vdata, &mesh->vert_data, mesh->verts_num);
um_arraystore_cd_expand(um->store.edata, &mesh->edge_data, mesh->edges_num);
um_arraystore_cd_expand(um->store.ldata, &mesh->corner_data, mesh->corners_num);
um_arraystore_cd_expand(um->store.pdata, &mesh->face_data, mesh->faces_num);
MultiValueMap<bke::AttrDomain, bke::Attribute *> attributes = get_attributes_by_domain(*mesh);
um_arraystore_cd_expand(um->store.vdata,
&mesh->vert_data,
attributes.lookup(bke::AttrDomain::Point),
mesh->verts_num);
um_arraystore_cd_expand(um->store.edata,
&mesh->edge_data,
attributes.lookup(bke::AttrDomain::Edge),
mesh->edges_num);
um_arraystore_cd_expand(um->store.ldata,
&mesh->corner_data,
attributes.lookup(bke::AttrDomain::Corner),
mesh->corners_num);
um_arraystore_cd_expand(um->store.pdata,
&mesh->face_data,
attributes.lookup(bke::AttrDomain::Face),
mesh->faces_num);
if (um->store.keyblocks) {
const size_t stride = mesh->key->elemsize;

View file

@ -687,17 +687,8 @@ static void mesh_add_verts(Mesh *mesh, int len)
}
int totvert = mesh->verts_num + len;
CustomData vert_data;
CustomData_init_layout_from(
&mesh->vert_data, &vert_data, CD_MASK_MESH.vmask, CD_SET_DEFAULT, totvert);
CustomData_copy_data(&mesh->vert_data, &vert_data, 0, 0, mesh->verts_num);
if (!CustomData_has_layer_named(&vert_data, CD_PROP_FLOAT3, "position")) {
CustomData_add_layer_named(&vert_data, CD_PROP_FLOAT3, CD_SET_DEFAULT, totvert, "position");
}
CustomData_free(&mesh->vert_data);
mesh->vert_data = vert_data;
mesh->attribute_storage.wrap().resize(bke::AttrDomain::Point, totvert);
CustomData_realloc(&mesh->vert_data, mesh->verts_num, totvert, CD_SET_DEFAULT);
BKE_mesh_runtime_clear_cache(mesh);
@ -708,11 +699,11 @@ static void mesh_add_verts(Mesh *mesh, int len)
".select_vert", bke::AttrDomain::Point);
select_vert.span.take_back(len).fill(true);
select_vert.finish();
attributes.add<float3>("position", bke::AttrDomain::Point, bke::AttributeInitDefaultValue());
}
static void mesh_add_edges(Mesh *mesh, int len)
{
CustomData edge_data;
int totedge;
if (len == 0) {
@ -721,18 +712,8 @@ static void mesh_add_edges(Mesh *mesh, int len)
totedge = mesh->edges_num + len;
/* Update custom-data. */
CustomData_init_layout_from(
&mesh->edge_data, &edge_data, CD_MASK_MESH.emask, CD_SET_DEFAULT, totedge);
CustomData_copy_data(&mesh->edge_data, &edge_data, 0, 0, mesh->edges_num);
if (!CustomData_has_layer_named(&edge_data, CD_PROP_INT32_2D, ".edge_verts")) {
CustomData_add_layer_named(
&edge_data, CD_PROP_INT32_2D, CD_SET_DEFAULT, totedge, ".edge_verts");
}
CustomData_free(&mesh->edge_data);
mesh->edge_data = edge_data;
mesh->attribute_storage.wrap().resize(bke::AttrDomain::Edge, totedge);
CustomData_realloc(&mesh->edge_data, mesh->edges_num, totedge, CD_SET_DEFAULT);
BKE_mesh_runtime_clear_cache(mesh);
@ -743,11 +724,11 @@ static void mesh_add_edges(Mesh *mesh, int len)
".select_edge", bke::AttrDomain::Edge);
select_edge.span.take_back(len).fill(true);
select_edge.finish();
attributes.add<float3>(".edge_verts", bke::AttrDomain::Edge, bke::AttributeInitDefaultValue());
}
static void mesh_add_loops(Mesh *mesh, int len)
{
CustomData ldata;
int totloop;
if (len == 0) {
@ -756,25 +737,17 @@ static void mesh_add_loops(Mesh *mesh, int len)
totloop = mesh->corners_num + len; /* new face count */
/* update customdata */
CustomData_init_layout_from(
&mesh->corner_data, &ldata, CD_MASK_MESH.lmask, CD_SET_DEFAULT, totloop);
CustomData_copy_data(&mesh->corner_data, &ldata, 0, 0, mesh->corners_num);
if (!CustomData_has_layer_named(&ldata, CD_PROP_INT32, ".corner_vert")) {
CustomData_add_layer_named(&ldata, CD_PROP_INT32, CD_SET_DEFAULT, totloop, ".corner_vert");
}
if (!CustomData_has_layer_named(&ldata, CD_PROP_INT32, ".corner_edge")) {
CustomData_add_layer_named(&ldata, CD_PROP_INT32, CD_SET_DEFAULT, totloop, ".corner_edge");
}
mesh->attribute_storage.wrap().resize(bke::AttrDomain::Corner, totloop);
CustomData_realloc(&mesh->corner_data, mesh->corners_num, totloop, CD_SET_DEFAULT);
BKE_mesh_runtime_clear_cache(mesh);
CustomData_free(&mesh->corner_data);
mesh->corner_data = ldata;
mesh->corners_num = totloop;
bke::MutableAttributeAccessor attributes = mesh->attributes_for_write();
attributes.add<int>(".corner_vert", bke::AttrDomain::Corner, bke::AttributeInitDefaultValue());
attributes.add<int>(".corner_edge", bke::AttrDomain::Corner, bke::AttributeInitDefaultValue());
/* Keep the last face offset up to date with the corner total (they must be the same). We have
* to be careful here though, since the mesh may not be in a valid state at this point. */
if (mesh->face_offset_indices) {
@ -784,7 +757,6 @@ static void mesh_add_loops(Mesh *mesh, int len)
static void mesh_add_faces(Mesh *mesh, int len)
{
CustomData face_data;
int faces_num;
if (len == 0) {
@ -793,10 +765,8 @@ static void mesh_add_faces(Mesh *mesh, int len)
faces_num = mesh->faces_num + len; /* new face count */
/* update customdata */
CustomData_init_layout_from(
&mesh->face_data, &face_data, CD_MASK_MESH.pmask, CD_SET_DEFAULT, faces_num);
CustomData_copy_data(&mesh->face_data, &face_data, 0, 0, mesh->faces_num);
mesh->attribute_storage.wrap().resize(bke::AttrDomain::Face, faces_num);
CustomData_realloc(&mesh->face_data, mesh->faces_num, faces_num, CD_SET_DEFAULT);
implicit_sharing::resize_trivial_array(&mesh->face_offset_indices,
&mesh->runtime->face_offsets_sharing_info,
@ -806,9 +776,6 @@ static void mesh_add_faces(Mesh *mesh, int len)
mesh->face_offset_indices[0] = 0;
mesh->face_offset_indices[faces_num] = mesh->corners_num;
CustomData_free(&mesh->face_data);
mesh->face_data = face_data;
BKE_mesh_runtime_clear_cache(mesh);
mesh->faces_num = faces_num;

View file

@ -5356,20 +5356,18 @@ void store_mesh_from_eval(const wmOperator &op,
undo::push_begin(scene, object, &op);
undo::push_nodes(depsgraph, object, leaf_nodes, undo::Type::Position);
undo::push_end(object);
CustomData_free_layer_named(&mesh.vert_data, "position");
mesh.attributes_for_write().remove("position");
mesh.attribute_storage.wrap().remove("position");
const bke::AttributeReader position = new_mesh->attributes().lookup<float3>("position");
if (position.sharing_info) {
/* Use lower level API to add the position attribute to avoid copying the array and to
* allow using #tag_positions_changed_no_normals instead of #tag_positions_changed (which
* would be called by the attribute API). */
CustomData_add_layer_named_with_data(
&mesh.vert_data,
CD_PROP_FLOAT3,
const_cast<float3 *>(position.varray.get_internal_span().data()),
mesh.verts_num,
"position",
position.sharing_info);
bke::Attribute::ArrayData data{};
data.data = const_cast<float3 *>(position.varray.get_internal_span().data());
data.size = position.varray.size();
data.sharing_info = ImplicitSharingPtr<>(position.sharing_info);
mesh.attribute_storage.wrap().add(
"position", bke::AttrDomain::Point, bke::AttrType::Float3, std::move(data));
}
else {
mesh.vert_positions_for_write().copy_from(VArraySpan(*position));

View file

@ -180,6 +180,7 @@ struct NodeGeometry {
* geometry pushes happened in the undo stack. */
bool is_initialized;
bke::AttributeStorage attribute_storage;
CustomData vert_data;
CustomData edge_data;
CustomData corner_data;
@ -916,6 +917,7 @@ static void store_geometry_data(NodeGeometry *geometry, const Object &object)
BLI_assert(!geometry->is_initialized);
geometry->is_initialized = true;
geometry->attribute_storage = mesh->attribute_storage.wrap();
CustomData_init_from(
&mesh->vert_data, &geometry->vert_data, CD_MASK_MESH.vmask, mesh->verts_num);
CustomData_init_from(
@ -947,6 +949,7 @@ static void restore_geometry_data(const NodeGeometry *geometry, Mesh *mesh)
mesh->faces_num = geometry->faces_num;
mesh->totface_legacy = 0;
mesh->attribute_storage = geometry->attribute_storage.wrap();
CustomData_init_from(
&geometry->vert_data, &mesh->vert_data, CD_MASK_MESH.vmask, geometry->verts_num);
CustomData_init_from(

View file

@ -27,6 +27,7 @@ static void propagate_vert_attributes(Mesh &mesh, const Span<int> new_to_old_ver
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();
mesh.attribute_storage.wrap().resize(bke::AttrDomain::Point, mesh.verts_num);
bke::MutableAttributeAccessor attributes = mesh.attributes_for_write();
for (const StringRef id : attributes.all_ids()) {
@ -67,6 +68,7 @@ static void propagate_edge_attributes(Mesh &mesh, const Span<int> new_to_old_edg
{
CustomData_realloc(&mesh.edge_data, mesh.edges_num, mesh.edges_num + new_to_old_edge_map.size());
mesh.edges_num += new_to_old_edge_map.size();
mesh.attribute_storage.wrap().resize(bke::AttrDomain::Edge, mesh.edges_num);
bke::MutableAttributeAccessor attributes = mesh.attributes_for_write();
for (const StringRef id : attributes.all_ids()) {

View file

@ -611,11 +611,16 @@ std::optional<Mesh *> mesh_triangulate(const Mesh &src_mesh,
mesh->corners_num = faces.total_size();
/* Vertex attributes are totally unaffected and can be shared with implicit sharing.
* Use the #CustomData API for simpler support for vertex groups. */
* Use the #CustomData API for simpler support for vertex groups. Edge attributes are the same
* for original edges. New edges will be generated by #bke::mesh_calc_edges later.*/
CustomData_merge(&src_mesh.vert_data, &mesh->vert_data, CD_MASK_MESH.vmask, mesh->verts_num);
/* Edge attributes are the same for original edges. New edges will be generated by
* #bke::mesh_calc_edges later. */
CustomData_merge(&src_mesh.edge_data, &mesh->edge_data, CD_MASK_MESH.emask, mesh->edges_num);
src_mesh.attribute_storage.wrap().foreach([&](const bke::Attribute &attr) {
if (!ELEM(attr.domain(), bke::AttrDomain::Point, bke::AttrDomain::Edge)) {
return;
}
mesh->attribute_storage.wrap().add(attr.name(), attr.domain(), attr.data_type(), attr.data());
});
bke::MutableAttributeAccessor attributes = mesh->attributes_for_write();

View file

@ -117,6 +117,7 @@ void debug_randomize_vert_order(Mesh *mesh)
const Array<int> new_by_old_map = get_permutation(mesh->verts_num, seed);
reorder_customdata(mesh->vert_data, new_by_old_map);
reorder_attribute_domain(mesh->attribute_storage.wrap(), bke::AttrDomain::Point, new_by_old_map);
for (int &v : mesh->edges_for_write().cast<int>()) {
v = new_by_old_map[v];
@ -138,6 +139,7 @@ void debug_randomize_edge_order(Mesh *mesh)
const Array<int> new_by_old_map = get_permutation(mesh->edges_num, seed);
reorder_customdata(mesh->edge_data, new_by_old_map);
reorder_attribute_domain(mesh->attribute_storage.wrap(), bke::AttrDomain::Edge, new_by_old_map);
for (int &e : mesh->corner_edges_for_write()) {
e = new_by_old_map[e];
@ -227,12 +229,18 @@ void debug_randomize_face_order(Mesh *mesh)
const Array<int> old_by_new_map = invert_permutation(new_by_old_map);
reorder_customdata(mesh->face_data, new_by_old_map);
reorder_attribute_domain(mesh->attribute_storage.wrap(), bke::AttrDomain::Face, new_by_old_map);
const OffsetIndices old_faces = mesh->faces();
Array<int> new_face_offsets = make_new_offset_indices(old_faces, old_by_new_map);
const OffsetIndices<int> new_faces = new_face_offsets.as_span();
reorder_customdata_groups(mesh->corner_data, old_faces, new_faces, new_by_old_map);
reorder_attribute_groups(mesh->attribute_storage.wrap(),
bke::AttrDomain::Corner,
old_faces,
new_faces,
new_by_old_map);
mesh->face_offsets_for_write().copy_from(new_face_offsets);

View file

@ -173,11 +173,11 @@ struct Mesh {
int *face_offset_indices = nullptr;
/**
* Vertex, edge, face, and corner generic attributes. Currently unused at runtime, but used for
* forward compatibility when reading files (see #122398).
* Vertex, edge, face, and corner generic attributes.
*/
struct AttributeStorage attribute_storage;
/** Store for non-generic layer data on each domain. */
CustomData vert_data;
CustomData edge_data;
CustomData face_data;

View file

@ -338,8 +338,11 @@ static StructRNA *srna_by_custom_data_layer_type(const eCustomDataType type)
static StructRNA *rna_Attribute_refine(PointerRNA *ptr)
{
if (GS(ptr->owner_id->name) == ID_ME) {
CustomDataLayer *layer = static_cast<CustomDataLayer *>(ptr->data);
return srna_by_custom_data_layer_type(eCustomDataType(layer->type));
const Mesh *mesh = blender::id_cast<const Mesh *>(ptr->owner_id);
if (mesh->runtime->edit_mesh) {
CustomDataLayer *layer = static_cast<CustomDataLayer *>(ptr->data);
return srna_by_custom_data_layer_type(eCustomDataType(layer->type));
}
}
bke::Attribute *attr = ptr->data_as<bke::Attribute>();
const eCustomDataType data_type = *bke::attr_type_to_custom_data_type(attr->data_type());
@ -354,8 +357,11 @@ StringRefNull rna_Attribute_name_get(const PointerRNA &ptr)
ID *owner_id = ptr.owner_id;
const AttributeOwner owner = AttributeOwner::from_id(owner_id);
if (owner.type() == AttributeOwnerType::Mesh) {
const CustomDataLayer *layer = ptr.data_as<CustomDataLayer>();
return layer->name;
const Mesh *mesh = owner.get_mesh();
if (mesh->runtime->edit_mesh) {
const CustomDataLayer *layer = ptr.data_as<CustomDataLayer>();
return layer->name;
}
}
const bke::Attribute *attr = ptr.data_as<bke::Attribute>();
return attr->name();
@ -375,9 +381,12 @@ void rna_Attribute_name_set(PointerRNA *ptr, const char *value)
{
AttributeOwner owner = owner_from_attribute_pointer_rna(ptr);
if (owner.type() == AttributeOwnerType::Mesh) {
const CustomDataLayer *layer = static_cast<const CustomDataLayer *>(ptr->data);
BKE_attribute_rename(owner, layer->name, value, nullptr);
return;
const Mesh *mesh = owner.get_mesh();
if (mesh->runtime->edit_mesh) {
const CustomDataLayer *layer = static_cast<const CustomDataLayer *>(ptr->data);
BKE_attribute_rename(owner, layer->name, value, nullptr);
return;
}
}
const bke::Attribute *attr = ptr->data_as<bke::Attribute>();
@ -388,12 +397,15 @@ static int rna_Attribute_name_editable(const PointerRNA *ptr, const char **r_inf
{
AttributeOwner owner = owner_from_attribute_pointer_rna(const_cast<PointerRNA *>(ptr));
if (owner.type() == AttributeOwnerType::Mesh) {
CustomDataLayer *layer = static_cast<CustomDataLayer *>(ptr->data);
if (BKE_attribute_required(owner, layer->name)) {
*r_info = N_("Cannot modify name of required geometry attribute");
return false;
const Mesh *mesh = owner.get_mesh();
if (mesh->runtime->edit_mesh) {
CustomDataLayer *layer = static_cast<CustomDataLayer *>(ptr->data);
if (BKE_attribute_required(owner, layer->name)) {
*r_info = N_("Cannot modify name of required geometry attribute");
return false;
}
return true;
}
return true;
}
bke::Attribute *attr = ptr->data_as<bke::Attribute>();
@ -406,9 +418,13 @@ static int rna_Attribute_name_editable(const PointerRNA *ptr, const char **r_inf
static int rna_Attribute_type_get(PointerRNA *ptr)
{
if (GS(ptr->owner_id->name) == ID_ME) {
CustomDataLayer *layer = static_cast<CustomDataLayer *>(ptr->data);
return layer->type;
AttributeOwner owner = owner_from_attribute_pointer_rna(ptr);
if (owner.type() == AttributeOwnerType::Mesh) {
const Mesh *mesh = owner.get_mesh();
if (mesh->runtime->edit_mesh) {
CustomDataLayer *layer = static_cast<CustomDataLayer *>(ptr->data);
return layer->type;
}
}
const bke::Attribute *attr = static_cast<const bke::Attribute *>(ptr->data);
return *bke::attr_type_to_custom_data_type(attr->data_type());
@ -416,8 +432,12 @@ static int rna_Attribute_type_get(PointerRNA *ptr)
static int rna_Attribute_storage_type_get(PointerRNA *ptr)
{
if (GS(ptr->owner_id->name) == ID_ME) {
return int(bke::AttrStorageType::Array);
AttributeOwner owner = owner_from_attribute_pointer_rna(ptr);
if (owner.type() == AttributeOwnerType::Mesh) {
const Mesh *mesh = owner.get_mesh();
if (mesh->runtime->edit_mesh) {
return int(bke::AttrStorageType::Array);
}
}
const bke::Attribute *attr = static_cast<const bke::Attribute *>(ptr->data);
return int(attr->storage_type());
@ -490,7 +510,11 @@ static int rna_Attribute_domain_get(PointerRNA *ptr)
{
AttributeOwner owner = owner_from_attribute_pointer_rna(ptr);
if (owner.type() == AttributeOwnerType::Mesh) {
return int(BKE_attribute_domain(owner, static_cast<const CustomDataLayer *>(ptr->data)));
const Mesh *mesh = owner.get_mesh();
if (BMEditMesh *em = mesh->runtime->edit_mesh.get()) {
return int(
BKE_attribute_domain(*mesh, *em->bm, static_cast<const CustomDataLayer *>(ptr->data)));
}
}
const bke::Attribute *attr = static_cast<const bke::Attribute *>(ptr->data);
return int(attr->domain());
@ -511,17 +535,11 @@ void rna_Attribute_data_begin(CollectionPropertyIterator *iter, PointerRNA *ptr)
{
AttributeOwner owner = owner_from_attribute_pointer_rna(ptr);
if (owner.type() == AttributeOwnerType::Mesh) {
CustomDataLayer *layer = static_cast<CustomDataLayer *>(ptr->data);
if (!(CD_TYPE_AS_MASK(eCustomDataType(layer->type)) & CD_MASK_PROP_ALL)) {
const Mesh *mesh = owner.get_mesh();
if (mesh->runtime->edit_mesh) {
iter->valid = false;
return;
}
const int length = BKE_attribute_data_length(owner, layer);
const size_t struct_size = CustomData_get_elem_size(layer);
CustomData_ensure_data_is_mutable(layer, length);
rna_iterator_array_begin(iter, ptr, layer->data, struct_size, length, 0, nullptr);
return;
}
bke::MutableAttributeAccessor accessor = *owner.get_accessor();
@ -550,8 +568,10 @@ int rna_Attribute_data_length(PointerRNA *ptr)
}
AttributeOwner owner = owner_from_attribute_pointer_rna(ptr);
if (owner.type() == AttributeOwnerType::Mesh) {
CustomDataLayer *layer = static_cast<CustomDataLayer *>(ptr->data);
return BKE_attribute_data_length(owner, layer);
const Mesh *mesh = owner.get_mesh();
if (mesh->runtime->edit_mesh) {
return 0;
}
}
const bke::Attribute *attr = ptr->data_as<bke::Attribute>();
@ -696,37 +716,40 @@ static PointerRNA rna_AttributeGroupID_new(
{
AttributeOwner owner = AttributeOwner::from_id(id);
if (owner.type() == AttributeOwnerType::Mesh) {
CustomDataLayer *layer = BKE_attribute_new(
owner, name, eCustomDataType(type), AttrDomain(domain), reports);
if (!layer) {
return PointerRNA_NULL;
}
Mesh *mesh = owner.get_mesh();
if (BMEditMesh *em = mesh->runtime->edit_mesh.get()) {
CustomDataLayer *layer = BKE_attribute_new(
*mesh, *em->bm, name, eCustomDataType(type), AttrDomain(domain), reports);
if (!layer) {
return PointerRNA_NULL;
}
if ((GS(id->name) == ID_ME)) {
Mesh *mesh = id_cast<Mesh *>(id);
if (ELEM(layer->type, CD_PROP_COLOR, CD_PROP_BYTE_COLOR)) {
if (!mesh->active_color_attribute) {
mesh->active_color_attribute = BLI_strdup(layer->name);
if ((GS(id->name) == ID_ME)) {
Mesh *mesh = id_cast<Mesh *>(id);
if (ELEM(layer->type, CD_PROP_COLOR, CD_PROP_BYTE_COLOR)) {
if (!mesh->active_color_attribute) {
mesh->active_color_attribute = BLI_strdup(layer->name);
}
if (!mesh->default_color_attribute) {
mesh->default_color_attribute = BLI_strdup(layer->name);
}
}
if (!mesh->default_color_attribute) {
mesh->default_color_attribute = BLI_strdup(layer->name);
}
}
if (ELEM(layer->type, CD_PROP_FLOAT2)) {
if (mesh->active_uv_map_name().is_empty()) {
mesh->uv_maps_active_set(layer->name);
}
if (mesh->default_uv_map_name().is_empty()) {
mesh->uv_maps_default_set(layer->name);
if (ELEM(layer->type, CD_PROP_FLOAT2)) {
if (mesh->active_uv_map_name().is_empty()) {
mesh->uv_maps_active_set(layer->name);
}
if (mesh->default_uv_map_name().is_empty()) {
mesh->uv_maps_default_set(layer->name);
}
}
}
DEG_id_tag_update(id, ID_RECALC_GEOMETRY);
WM_main_add_notifier(NC_GEOM | ND_DATA, id);
PointerRNA ptr = RNA_pointer_create_discrete(id, RNA_Attribute, layer);
return ptr;
}
DEG_id_tag_update(id, ID_RECALC_GEOMETRY);
WM_main_add_notifier(NC_GEOM | ND_DATA, id);
PointerRNA ptr = RNA_pointer_create_discrete(id, RNA_Attribute, layer);
return ptr;
}
const bke::AttributeAccessor accessor = *owner.get_accessor();
@ -754,13 +777,16 @@ static void rna_AttributeGroupID_remove(ID *id, ReportList *reports, PointerRNA
{
AttributeOwner owner = AttributeOwner::from_id(id);
if (owner.type() == AttributeOwnerType::Mesh) {
const CustomDataLayer *layer = static_cast<const CustomDataLayer *>(attribute_ptr->data);
BKE_attribute_remove(owner, layer->name, reports);
attribute_ptr->invalidate();
const Mesh *mesh = owner.get_mesh();
if (mesh->runtime->edit_mesh) {
const CustomDataLayer *layer = static_cast<const CustomDataLayer *>(attribute_ptr->data);
BKE_attribute_remove(owner, layer->name, reports);
attribute_ptr->invalidate();
DEG_id_tag_update(id, ID_RECALC_GEOMETRY);
WM_main_add_notifier(NC_GEOM | ND_DATA, id);
return;
DEG_id_tag_update(id, ID_RECALC_GEOMETRY);
WM_main_add_notifier(NC_GEOM | ND_DATA, id);
return;
}
}
const bke::Attribute *attr = static_cast<const bke::Attribute *>(attribute_ptr->data);
@ -787,19 +813,19 @@ void rna_AttributeGroup_iterator_begin(CollectionPropertyIterator *iter,
AttributeOwner owner = owner_from_pointer_rna(ptr);
if (owner.type() == AttributeOwnerType::Mesh) {
Mesh *mesh = owner.get_mesh();
Vector<CustomDataLayer *, 16> layers;
const auto add_layers = [&](CustomData &data) {
for (CustomDataLayer &layer : MutableSpan(data.layers, data.totlayer)) {
if (!(CD_TYPE_AS_MASK(eCustomDataType(layer.type)) & cd_type_mask)) {
continue;
}
if (!include_anonymous && bke::attribute_name_is_anonymous(layer.name)) {
continue;
}
layers.append(&layer);
}
};
if (BMEditMesh *em = mesh->runtime->edit_mesh.get()) {
Vector<CustomDataLayer *, 16> layers;
const auto add_layers = [&](CustomData &data) {
for (CustomDataLayer &layer : MutableSpan(data.layers, data.totlayer)) {
if (!(CD_TYPE_AS_MASK(eCustomDataType(layer.type)) & cd_type_mask)) {
continue;
}
if (!include_anonymous && bke::attribute_name_is_anonymous(layer.name)) {
continue;
}
layers.append(&layer);
}
};
if (domain_mask & ATTR_DOMAIN_MASK_POINT) {
add_layers(em->bm->vdata);
}
@ -812,25 +838,11 @@ void rna_AttributeGroup_iterator_begin(CollectionPropertyIterator *iter,
if (domain_mask & ATTR_DOMAIN_MASK_CORNER) {
add_layers(em->bm->ldata);
}
VectorData data = layers.release();
rna_iterator_array_begin(
iter, ptr, data.data, sizeof(CustomDataLayer *), data.size, true, nullptr);
return;
}
else {
if (domain_mask & ATTR_DOMAIN_MASK_POINT) {
add_layers(mesh->vert_data);
}
if (domain_mask & ATTR_DOMAIN_MASK_EDGE) {
add_layers(mesh->edge_data);
}
if (domain_mask & ATTR_DOMAIN_MASK_FACE) {
add_layers(mesh->face_data);
}
if (domain_mask & ATTR_DOMAIN_MASK_CORNER) {
add_layers(mesh->corner_data);
}
}
VectorData data = layers.release();
rna_iterator_array_begin(
iter, ptr, data.data, sizeof(CustomDataLayer *), data.size, true, nullptr);
return;
}
bke::AttributeStorage &storage = *owner.get_storage();
@ -861,12 +873,15 @@ PointerRNA rna_AttributeGroup_iterator_get(CollectionPropertyIterator *iter)
{
AttributeOwner owner = owner_from_pointer_rna(&iter->parent);
if (owner.type() == AttributeOwnerType::Mesh) {
CustomDataLayer *layer = *static_cast<CustomDataLayer **>(rna_iterator_array_get(iter));
StructRNA *type = srna_by_custom_data_layer_type(eCustomDataType(layer->type));
if (type == nullptr) {
return PointerRNA_NULL;
Mesh *mesh = owner.get_mesh();
if (mesh->runtime->edit_mesh) {
CustomDataLayer *layer = *static_cast<CustomDataLayer **>(rna_iterator_array_get(iter));
StructRNA *type = srna_by_custom_data_layer_type(eCustomDataType(layer->type));
if (type == nullptr) {
return PointerRNA_NULL;
}
return RNA_pointer_create_with_parent(iter->parent, type, layer);
}
return RNA_pointer_create_with_parent(iter->parent, type, layer);
}
bke::Attribute *attr = *static_cast<bke::Attribute **>(rna_iterator_array_get(iter));
@ -916,14 +931,6 @@ PointerRNA rna_AttributeGroup_lookup_string(const PointerRNA &ptr,
ptr, RNA_Attribute, const_cast<CustomDataLayer *>(attr.layer), result);
return result;
}
else if (CustomDataLayer *layer = BKE_attribute_search_for_write(
owner, key, CD_MASK_PROP_ALL, ATTR_DOMAIN_MASK_ALL))
{
PointerRNA result;
rna_pointer_create_with_ancestors(ptr, RNA_Attribute, layer, result);
return result;
}
return PointerRNA_NULL;
}
bke::AttributeStorage &storage = *owner.get_storage();
@ -970,14 +977,17 @@ static void rna_AttributeGroupID_active_set(PointerRNA *ptr,
{
AttributeOwner owner = AttributeOwner::from_id(ptr->owner_id);
if (owner.type() == AttributeOwnerType::Mesh) {
CustomDataLayer *layer = static_cast<CustomDataLayer *>(attribute_ptr.data);
if (layer) {
BKE_attributes_active_set(owner, layer->name);
const Mesh *mesh = owner.get_mesh();
if (mesh->runtime->edit_mesh) {
CustomDataLayer *layer = static_cast<CustomDataLayer *>(attribute_ptr.data);
if (layer) {
BKE_attributes_active_set(owner, layer->name);
}
else {
BKE_attributes_active_clear(owner);
}
return;
}
else {
BKE_attributes_active_clear(owner);
}
return;
}
bke::Attribute *attr = attribute_ptr.data_as<bke::Attribute>();
@ -1022,7 +1032,10 @@ static int rna_AttributeGroupID_domain_size(ID *id, const int domain)
{
AttributeOwner owner = AttributeOwner::from_id(id);
if (owner.type() == AttributeOwnerType::Mesh) {
return BKE_attribute_domain_size(owner, domain);
const Mesh *mesh = owner.get_mesh();
if (mesh->runtime->edit_mesh) {
return BKE_attribute_domain_size(owner, domain);
}
}
bke::AttributeAccessor attributes = *owner.get_accessor();

View file

@ -133,6 +133,7 @@ static void expand_mesh(Mesh &mesh,
const int old_verts_num = mesh.verts_num;
mesh.verts_num += vert_expand;
CustomData_realloc(&mesh.vert_data, old_verts_num, mesh.verts_num);
mesh.attribute_storage.wrap().resize(AttrDomain::Point, mesh.verts_num);
}
if (edge_expand != 0) {
if (mesh.edges_num == 0) {
@ -142,11 +143,13 @@ static void expand_mesh(Mesh &mesh,
const int old_edges_num = mesh.edges_num;
mesh.edges_num += edge_expand;
CustomData_realloc(&mesh.edge_data, old_edges_num, mesh.edges_num);
mesh.attribute_storage.wrap().resize(AttrDomain::Edge, mesh.edges_num);
}
if (face_expand != 0) {
const int old_faces_num = mesh.faces_num;
mesh.faces_num += face_expand;
CustomData_realloc(&mesh.face_data, old_faces_num, mesh.faces_num);
mesh.attribute_storage.wrap().resize(AttrDomain::Face, mesh.faces_num);
implicit_sharing::resize_trivial_array(&mesh.face_offset_indices,
&mesh.runtime->face_offsets_sharing_info,
old_faces_num == 0 ? 0 : (old_faces_num + 1),
@ -165,6 +168,7 @@ static void expand_mesh(Mesh &mesh,
const int old_loops_num = mesh.corners_num;
mesh.corners_num += loop_expand;
CustomData_realloc(&mesh.corner_data, old_loops_num, mesh.corners_num);
mesh.attribute_storage.wrap().resize(AttrDomain::Corner, mesh.corners_num);
}
}

View file

@ -56,7 +56,7 @@ static void geometry_set_points_to_vertices(GeometrySet &geometry_set,
if (selection.size() == points->totpoint) {
/* Create a mesh without positions so the attribute can be shared. */
mesh = BKE_mesh_new_nomain(0, 0, 0, 0);
CustomData_free_layer_named(&mesh->vert_data, "position");
mesh->attribute_storage.wrap().remove("position");
mesh->verts_num = selection.size();
}
else {