Files
2026-08-12 04:47:48 -04:00

1014 lines
34 KiB
C++

/* SPDX-FileCopyrightText: 2006 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bke
* Implementation of generic geometry attributes management. This is built
* on top of CustomData, which manages individual domains.
*/
#include <cstring>
#include <optional>
#include "BKE_anonymous_attribute_id.hh"
#include "MEM_guardedalloc.h"
#include "DNA_ID.h"
#include "DNA_curves_types.h"
#include "DNA_customdata_types.h"
#include "DNA_mesh_types.h"
#include "DNA_pointcloud_types.h"
#include "BLI_index_range.hh"
#include "BLI_string.h"
#include "BLI_string_utils.hh"
#include "BLT_translation.hh"
#include "BKE_attribute.hh"
#include "BKE_attribute_legacy_convert.hh"
#include "BKE_curves.hh"
#include "BKE_customdata.hh"
#include "BKE_deform.hh"
#include "BKE_editmesh.hh"
#include "BKE_grease_pencil.hh"
#include "BKE_mesh.hh"
#include "BKE_pointcloud.hh"
#include "BKE_report.hh"
#include <fmt/format.h>
namespace blender {
using bke::AttrDomain;
AttributeOwner AttributeOwner::from_id(ID *id)
{
if (id == nullptr) {
return {};
}
switch (GS(id->name)) {
case ID_ME:
return AttributeOwner(AttributeOwnerType::Mesh, id);
case ID_PT:
return AttributeOwner(AttributeOwnerType::PointCloud, id);
case ID_CV:
return AttributeOwner(AttributeOwnerType::Curves, id);
case ID_GP:
return AttributeOwner(AttributeOwnerType::GreasePencil, id);
default:
return {};
}
}
AttributeOwnerType AttributeOwner::type() const
{
return type_;
}
bool AttributeOwner::is_valid() const
{
return ptr_ != nullptr;
}
Mesh *AttributeOwner::get_mesh() const
{
BLI_assert(this->is_valid());
BLI_assert(type_ == AttributeOwnerType::Mesh);
return reinterpret_cast<Mesh *>(ptr_);
}
PointCloud *AttributeOwner::get_pointcloud() const
{
BLI_assert(this->is_valid());
BLI_assert(type_ == AttributeOwnerType::PointCloud);
return reinterpret_cast<PointCloud *>(ptr_);
}
Curves *AttributeOwner::get_curves() const
{
BLI_assert(this->is_valid());
BLI_assert(type_ == AttributeOwnerType::Curves);
return reinterpret_cast<Curves *>(ptr_);
}
GreasePencil *AttributeOwner::get_grease_pencil() const
{
BLI_assert(this->is_valid());
BLI_assert(type_ == AttributeOwnerType::GreasePencil);
return reinterpret_cast<GreasePencil *>(ptr_);
}
GreasePencilDrawing *AttributeOwner::get_grease_pencil_drawing() const
{
BLI_assert(this->is_valid());
BLI_assert(type_ == AttributeOwnerType::GreasePencilDrawing);
return reinterpret_cast<GreasePencilDrawing *>(ptr_);
}
bke::AttributeStorage *AttributeOwner::get_storage() const
{
switch (type_) {
case AttributeOwnerType::Mesh:
return &this->get_mesh()->attribute_storage.wrap();
case AttributeOwnerType::PointCloud:
return &this->get_pointcloud()->attribute_storage.wrap();
case AttributeOwnerType::Curves:
return &this->get_curves()->geometry.attribute_storage.wrap();
case AttributeOwnerType::GreasePencil:
return &this->get_grease_pencil()->attribute_storage.wrap();
case AttributeOwnerType::GreasePencilDrawing:
return &this->get_grease_pencil_drawing()->geometry.attribute_storage.wrap();
}
BLI_assert(false);
return nullptr;
}
std::optional<bke::MutableAttributeAccessor> AttributeOwner::get_accessor() const
{
switch (type_) {
case AttributeOwnerType::Mesh:
/* The attribute API isn't implemented for BMesh, so edit mode meshes are not supported. */
BLI_assert(this->get_mesh()->runtime->edit_mesh == nullptr);
return this->get_mesh()->attributes_for_write();
case AttributeOwnerType::PointCloud:
return this->get_pointcloud()->attributes_for_write();
case AttributeOwnerType::Curves:
return this->get_curves()->geometry.wrap().attributes_for_write();
case AttributeOwnerType::GreasePencil:
return this->get_grease_pencil()->attributes_for_write();
case AttributeOwnerType::GreasePencilDrawing:
return this->get_grease_pencil_drawing()->geometry.wrap().attributes_for_write();
}
BLI_assert(false);
return std::nullopt;
}
struct DomainInfo {
CustomData *customdata = nullptr;
int length = 0;
};
static std::array<DomainInfo, ATTR_DOMAIN_NUM> get_domains(BMesh *bm)
{
std::array<DomainInfo, ATTR_DOMAIN_NUM> info;
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;
}
static bool bke_attribute_rename_if_exists(AttributeOwner &owner,
const StringRef old_name,
const StringRef new_name,
ReportList *reports)
{
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);
}
static bool name_valid_for_builtin_domain_and_type(const bke::AttributeAccessor attributes,
const StringRef name,
const AttrDomain domain,
const bke::AttrType data_type,
ReportList *reports)
{
if (const std::optional metadata = attributes.get_builtin_domain_and_type(name)) {
if (domain != metadata->domain) {
BKE_reportf(reports,
RPT_ERROR,
"Domain unsupported for \"%s\" attribute",
std::string(name).c_str());
return false;
}
if (data_type != metadata->data_type) {
BKE_reportf(
reports, RPT_ERROR, "Type unsupported for \"%s\" attribute", std::string(name).c_str());
return false;
}
}
return true;
}
static bool mesh_attribute_valid(const Mesh &mesh,
const StringRef name,
const AttrDomain domain,
const bke::AttrType data_type,
ReportList *reports)
{
if (mesh.runtime->edit_mesh) {
if (BM_attribute_stored_in_bmesh_builtin(name)) {
BKE_report(reports, RPT_ERROR, "Unable to create attribute in edit mode");
return false;
}
}
if (!name_valid_for_builtin_domain_and_type(mesh.attributes(), name, domain, data_type, reports))
{
return false;
}
return true;
}
bool BKE_attribute_rename(AttributeOwner &owner,
const StringRef old_name,
const StringRef new_name,
ReportList *reports)
{
if (BKE_attribute_required(owner, old_name)) {
BLI_assert_msg(0, "Required attribute name is not editable");
return false;
}
if (new_name.is_empty()) {
BKE_report(reports, RPT_ERROR, "Attribute name cannot be empty");
return false;
}
if (owner.type() == AttributeOwnerType::Mesh) {
Mesh *mesh = owner.get_mesh();
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;
}
}
bke::AttributeStorage &attributes = *owner.get_storage();
bke::Attribute *attr = attributes.lookup(old_name);
if (!attr) {
BKE_report(reports, RPT_ERROR, "Attribute is not part of this geometry");
return false;
}
if (owner.type() == AttributeOwnerType::Curves) {
Curves *curves = owner.get_curves();
if (!name_valid_for_builtin_domain_and_type(curves->geometry.wrap().attributes(),
new_name,
attr->domain(),
attr->data_type(),
reports))
{
return false;
}
}
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);
}
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);
}
if (old_name == mesh->active_uv_map_name()) {
mesh->uv_maps_active_set(new_name);
}
if (old_name == mesh->default_uv_map_name()) {
mesh->uv_maps_default_set(new_name);
}
}
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) {
const Mesh &mesh = *owner.get_mesh();
/* While attribute names may collide with vertex group names,
* it's important not to allow this when requesting a unique name
* because #MutableAttributeAccessor::add will consider the layer as "existing",
* and not add the new attribute as expected. See: #160029. */
const auto is_used_vertex_group = [&](const StringRef check_name) {
return BKE_defgroup_name_index(&mesh.vertex_group_names, check_name) != -1;
};
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) || BM_attribute_stored_in_bmesh_builtin(new_name) ||
is_used_vertex_group(new_name);
},
'.',
name_final);
}
const bke::AttributeStorage &storage = *owner.get_storage();
return BLI_uniquename_cb(
[&](const StringRef check_name) {
return storage.lookup(check_name) != nullptr || is_used_vertex_group(check_name);
},
'.',
name_final);
}
bke::AttributeStorage &storage = *owner.get_storage();
return storage.unique_name_calc(name_final);
}
CustomDataLayer *BKE_attribute_new(Mesh &mesh,
BMesh &bm,
const StringRef name,
const eCustomDataType type,
const AttrDomain domain,
ReportList *reports)
{
using namespace blender::bke;
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) {
BKE_report(reports, RPT_ERROR, "Attribute domain not supported by this geometry type");
return nullptr;
}
std::string uniquename = BKE_attribute_calc_unique_name(AttributeOwner::from_id(&mesh.id), name);
if (!mesh_attribute_valid(mesh, name, domain, *custom_data_type_to_attr_type(type), reports)) {
return nullptr;
}
BM_data_layer_add_named(&bm, customdata, type, uniquename.c_str());
const int index = CustomData_get_named_layer_index(customdata, type, uniquename);
return (index == -1) ? nullptr : &(customdata->layers[index]);
}
static int color_name_to_index(AttributeOwner &owner, const StringRef name)
{
return BKE_attribute_to_index(owner, name, ATTR_DOMAIN_MASK_COLOR, CD_MASK_COLOR_ALL);
}
static int color_clamp_index(AttributeOwner &owner, int index)
{
const int length = BKE_attributes_length(owner, ATTR_DOMAIN_MASK_COLOR, CD_MASK_COLOR_ALL);
return min_ii(index, length - 1);
}
static StringRef color_name_from_index(AttributeOwner &owner, int index)
{
return BKE_attribute_from_index(owner, index, ATTR_DOMAIN_MASK_COLOR, CD_MASK_COLOR_ALL)
.value_or("");
}
static int uv_name_to_index(AttributeOwner &owner, const StringRef name)
{
return BKE_attribute_to_index(owner, name, ATTR_DOMAIN_MASK_CORNER, CD_MASK_PROP_FLOAT2);
}
static int uv_clamp_index(AttributeOwner &owner, int index)
{
const int length = BKE_attributes_length(owner, ATTR_DOMAIN_MASK_CORNER, CD_MASK_PROP_FLOAT2);
return min_ii(index, length - 1);
}
static StringRef uv_name_from_index(AttributeOwner &owner, int index)
{
return BKE_attribute_from_index(owner, index, ATTR_DOMAIN_MASK_CORNER, CD_MASK_PROP_FLOAT2)
.value_or("");
}
bool BKE_attribute_remove(AttributeOwner &owner, const StringRef name, ReportList *reports)
{
using namespace blender::bke;
if (name.is_empty()) {
BKE_report(reports, RPT_ERROR, "The attribute name must not be empty");
return false;
}
if (BKE_attribute_required(owner, name)) {
BKE_reportf(reports,
RPT_ERROR,
"Attribute '%s' is required and cannot be removed",
std::string(name).c_str());
return false;
}
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(em->bm);
for (const int domain : IndexRange(ATTR_DOMAIN_NUM)) {
if (CustomData *data = info[domain].customdata) {
const std::string name_copy = name;
const int layer_index = CustomData_get_named_layer_index_notype(data, name_copy);
if (layer_index == -1) {
continue;
}
const eCustomDataType type = eCustomDataType(data->layers[layer_index].type);
const bool is_active_uv_attribute = name_copy == mesh->active_uv_map_name();
const bool is_default_uv_attribute = name_copy == mesh->default_uv_map_name();
const bool is_active_color_attribute = name_copy.c_str() ==
StringRef(mesh->active_color_attribute);
const bool is_default_color_attribute = name_copy.c_str() ==
StringRef(mesh->default_color_attribute);
const int active_color_index = color_name_to_index(owner, mesh->active_color_attribute);
const int default_color_index = color_name_to_index(owner,
mesh->default_color_attribute);
const int active_uv_index = uv_name_to_index(owner, mesh->active_uv_map_name());
const int default_uv_index = uv_name_to_index(owner, mesh->default_uv_map_name());
if (!BM_data_layer_free_named(em->bm, data, name_copy.c_str())) {
BLI_assert_unreachable();
}
if (is_active_color_attribute) {
BKE_id_attributes_active_color_set(
&mesh->id,
color_name_from_index(owner, color_clamp_index(owner, active_color_index)));
}
if (is_default_color_attribute) {
BKE_id_attributes_default_color_set(
&mesh->id,
color_name_from_index(owner, color_clamp_index(owner, default_color_index)));
}
if (is_active_uv_attribute) {
mesh->uv_maps_active_set(
uv_name_from_index(owner, uv_clamp_index(owner, active_uv_index)));
}
if (is_default_uv_attribute) {
mesh->uv_maps_default_set(
uv_name_from_index(owner, uv_clamp_index(owner, default_uv_index)));
}
if (type == CD_PROP_FLOAT2 && domain == int(AttrDomain::Corner)) {
char buffer[MAX_CUSTOMDATA_LAYER_NAME];
BM_data_layer_free_named(em->bm, data, BKE_uv_map_pin_name_get(name_copy, buffer));
}
return true;
}
}
return false;
}
}
std::optional<MutableAttributeAccessor> attributes = owner.get_accessor();
if (!attributes) {
return false;
}
if (owner.type() == AttributeOwnerType::Mesh) {
const std::string name_copy = name;
std::optional<bke::AttributeMetaData> metadata = attributes->lookup_meta_data(name_copy);
if (!metadata) {
return false;
}
/* Update active and default color attributes. */
Mesh *mesh = owner.get_mesh();
const bool is_active_color_attribute = name_copy == StringRef(mesh->active_color_attribute);
const bool is_default_color_attribute = name_copy == StringRef(mesh->default_color_attribute);
const bool is_active_uv_attribute = name_copy == mesh->active_uv_map_name();
const bool is_default_uv_attribute = name_copy == mesh->default_uv_map_name();
const int active_color_index = color_name_to_index(owner, mesh->active_color_attribute);
const int default_color_index = color_name_to_index(owner, mesh->default_color_attribute);
const int active_uv_index = uv_name_to_index(owner, mesh->active_uv_map_name());
const int default_uv_index = uv_name_to_index(owner, mesh->default_uv_map_name());
if (!attributes->remove(name_copy)) {
BLI_assert_unreachable();
}
if (is_active_color_attribute) {
BKE_id_attributes_active_color_set(
&mesh->id, color_name_from_index(owner, color_clamp_index(owner, active_color_index)));
}
if (is_default_color_attribute) {
BKE_id_attributes_default_color_set(
&mesh->id, color_name_from_index(owner, color_clamp_index(owner, default_color_index)));
}
if (is_active_uv_attribute) {
mesh->uv_maps_active_set(uv_name_from_index(owner, uv_clamp_index(owner, active_uv_index)));
}
if (is_default_uv_attribute) {
mesh->uv_maps_default_set(
uv_name_from_index(owner, uv_clamp_index(owner, default_uv_index)));
}
if (bke::mesh::is_uv_map(metadata)) {
char buffer[MAX_CUSTOMDATA_LAYER_NAME];
attributes->remove(BKE_uv_map_pin_name_get(name_copy, buffer));
}
return true;
}
return attributes->remove(name);
}
int BKE_attributes_length(const AttributeOwner &owner,
const AttrDomainMask domain_mask,
const eCustomDataMask mask,
const bool include_anonymous)
{
if (owner.type() == AttributeOwnerType::Mesh) {
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 (!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)))) {
continue;
}
if (!include_anonymous && bke::attribute_name_is_anonymous(layer.name)) {
continue;
}
length++;
}
}
return length;
}
}
const bke::AttributeStorage &storage = *owner.get_storage();
if (include_anonymous && domain_mask == ATTR_DOMAIN_MASK_ALL && mask == CD_MASK_PROP_ALL) {
return storage.count();
}
return std::count_if(storage.begin(), storage.end(), [&](const bke::Attribute &attr) {
if (!(ATTR_DOMAIN_AS_MASK(attr.domain()) & domain_mask)) {
return false;
}
if (!(CD_TYPE_AS_MASK(*bke::attr_type_to_custom_data_type(attr.data_type())) & mask)) {
return false;
}
if (!include_anonymous && bke::attribute_name_is_anonymous(attr.name())) {
return false;
}
return true;
});
}
AttrDomain BKE_attribute_domain(const Mesh &mesh, const BMesh &bm, const CustomDataLayer *layer)
{
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;
if (customdata == nullptr) {
continue;
}
if (Span(customdata->layers, customdata->totlayer).contains_ptr(layer)) {
return AttrDomain(domain);
}
}
BLI_assert_msg(0, "Custom data layer not found in geometry");
return AttrDomain(AttrDomain::Point);
}
int BKE_attribute_domain_size(const AttributeOwner &owner, const int domain)
{
if (owner.type() == AttributeOwnerType::Mesh) {
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;
}
}
const bke::AttributeAccessor attributes = *owner.get_accessor();
return attributes.domain_size(bke::AttrDomain(domain));
}
bool BKE_attribute_required(const AttributeOwner &owner, const StringRef name)
{
switch (owner.type()) {
case AttributeOwnerType::PointCloud:
return BKE_pointcloud_attribute_required(owner.get_pointcloud(), name);
case AttributeOwnerType::Curves:
return BKE_curves_attribute_required(owner.get_curves(), name);
case AttributeOwnerType::Mesh:
return BKE_mesh_attribute_required(name);
case AttributeOwnerType::GreasePencil:
return false;
case AttributeOwnerType::GreasePencilDrawing:
return BKE_grease_pencil_drawing_attribute_required(owner.get_grease_pencil_drawing(), name);
}
return false;
}
std::optional<StringRefNull> BKE_attributes_active_name_get(AttributeOwner &owner)
{
using namespace blender::bke;
int active_index = *BKE_attributes_active_index_p(owner);
if (active_index == -1) {
return std::nullopt;
}
if (owner.type() == AttributeOwnerType::Mesh) {
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;
}
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;
}
index++;
}
}
}
return std::nullopt;
}
}
bke::AttributeStorage &storage = *owner.get_storage();
if (!IndexRange(storage.count()).contains(active_index)) {
return std::nullopt;
}
return storage.at_index(active_index).name();
}
void BKE_attributes_active_set(AttributeOwner &owner, const StringRef name)
{
BLI_assert(bke::allow_procedural_attribute_access(name));
if (owner.type() == AttributeOwnerType::Mesh) {
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();
*BKE_attributes_active_index_p(owner) = attributes.index_of(name);
}
void BKE_attributes_active_clear(AttributeOwner &owner)
{
*BKE_attributes_active_index_p(owner) = -1;
}
int *BKE_attributes_active_index_p(AttributeOwner &owner)
{
switch (owner.type()) {
case AttributeOwnerType::PointCloud: {
return &owner.get_pointcloud()->attributes_active_index;
}
case AttributeOwnerType::Mesh: {
return &owner.get_mesh()->attributes_active_index;
}
case AttributeOwnerType::Curves: {
return &owner.get_curves()->geometry.attributes_active_index;
}
case AttributeOwnerType::GreasePencil: {
return &owner.get_grease_pencil()->attributes_active_index;
}
case AttributeOwnerType::GreasePencilDrawing: {
return &owner.get_grease_pencil_drawing()->geometry.attributes_active_index;
}
}
return nullptr;
}
void BKE_attributes_active_index_validate(AttributeOwner &owner)
{
int *active_index = BKE_attributes_active_index_p(owner);
if (*active_index < 0) {
/* Mark none as active. */
*active_index = -1;
return;
}
const int attributes_num = BKE_attributes_length(owner, ATTR_DOMAIN_MASK_ALL, CD_MASK_PROP_ALL);
if (attributes_num == 0) {
/* Mark none as active. */
*active_index = -1;
return;
}
/* First try downwards. */
int index_check = *active_index;
while (index_check >= 0) {
if (index_check < attributes_num) {
std::optional<StringRef> attribute_name_check = BKE_attribute_from_index(
owner, index_check, ATTR_DOMAIN_MASK_ALL, CD_MASK_PROP_ALL);
if (attribute_name_check.has_value() &&
bke::allow_procedural_attribute_access(attribute_name_check.value()))
{
*active_index = index_check;
return;
}
}
index_check--;
}
/* Still not found? Try upwards. */
index_check = *active_index + 1;
while (index_check < attributes_num) {
std::optional<StringRef> attribute_name_check = BKE_attribute_from_index(
owner, index_check, ATTR_DOMAIN_MASK_ALL, CD_MASK_PROP_ALL);
if (attribute_name_check.has_value() &&
bke::allow_procedural_attribute_access(attribute_name_check.value()))
{
*active_index = index_check;
return;
}
index_check++;
}
/* Still not found? Mark none as active. */
*active_index = -1;
}
std::optional<StringRef> BKE_attribute_from_index(AttributeOwner &owner,
const int lookup_index,
const AttrDomainMask domain_mask,
const eCustomDataMask layer_mask,
const bool include_anonymous)
{
if (owner.type() == AttributeOwnerType::Mesh) {
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)))) {
continue;
}
if (!include_anonymous && bke::attribute_name_is_anonymous(layer.name)) {
continue;
}
if (index == lookup_index) {
return layer.name;
}
index++;
}
}
return std::nullopt;
}
}
bke::AttributeStorage &storage = *owner.get_storage();
if (domain_mask == ATTR_DOMAIN_MASK_ALL && layer_mask == CD_MASK_PROP_ALL) {
return storage.at_index(lookup_index).name();
}
int index = 0;
for (const bke::Attribute &attr : storage) {
if (!(ATTR_DOMAIN_AS_MASK(attr.domain()) & domain_mask)) {
continue;
}
if (!(CD_TYPE_AS_MASK(*bke::attr_type_to_custom_data_type(attr.data_type())) & layer_mask)) {
continue;
}
if (!include_anonymous && bke::attribute_name_is_anonymous(attr.name())) {
continue;
}
if (index == lookup_index) {
return attr.name();
}
index++;
}
return std::nullopt;
}
int BKE_attribute_to_index(const AttributeOwner &owner,
const StringRef name,
AttrDomainMask domain_mask,
eCustomDataMask layer_mask,
const bool include_anonymous)
{
if (owner.type() == AttributeOwnerType::Mesh) {
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;
}
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++;
}
}
return -1;
}
}
const bke::AttributeStorage &storage = *owner.get_storage();
if (domain_mask == ATTR_DOMAIN_MASK_ALL && layer_mask == CD_MASK_PROP_ALL) {
return storage.index_of(name);
}
int index = 0;
for (const bke::Attribute &attr : storage) {
if (!(ATTR_DOMAIN_AS_MASK(attr.domain()) & domain_mask)) {
continue;
}
if (!(CD_TYPE_AS_MASK(*bke::attr_type_to_custom_data_type(attr.data_type())) & layer_mask)) {
continue;
}
if (!include_anonymous && bke::attribute_name_is_anonymous(attr.name())) {
continue;
}
if (attr.name() == name) {
return index;
}
index++;
}
return -1;
}
std::optional<StringRef> BKE_id_attributes_active_color_name(const ID *id)
{
if (GS(id->name) == ID_ME) {
return reinterpret_cast<const Mesh *>(id)->active_color_attribute;
}
return std::nullopt;
}
std::optional<StringRef> BKE_id_attributes_default_color_name(const ID *id)
{
if (GS(id->name) == ID_ME) {
return reinterpret_cast<const Mesh *>(id)->default_color_attribute;
}
return std::nullopt;
}
void BKE_id_attributes_active_color_set(ID *id, const std::optional<StringRef> name)
{
switch (GS(id->name)) {
case ID_ME: {
Mesh *mesh = reinterpret_cast<Mesh *>(id);
MEM_SAFE_DELETE(mesh->active_color_attribute);
if (name) {
mesh->active_color_attribute = BLI_strdupn(name->data(), name->size());
}
break;
}
default:
break;
}
}
void BKE_id_attributes_active_color_clear(ID *id)
{
switch (GS(id->name)) {
case ID_ME: {
Mesh *mesh = reinterpret_cast<Mesh *>(id);
MEM_SAFE_DELETE(mesh->active_color_attribute);
break;
}
default:
break;
}
}
void BKE_id_attributes_default_color_set(ID *id, const std::optional<StringRef> name)
{
switch (GS(id->name)) {
case ID_ME: {
Mesh *mesh = reinterpret_cast<Mesh *>(id);
MEM_SAFE_DELETE(mesh->default_color_attribute);
if (name) {
mesh->default_color_attribute = BLI_strdupn(name->data(), name->size());
}
break;
}
default:
break;
}
}
bool BKE_id_attributes_color_find(const ID *id, const StringRef name)
{
AttributeOwner owner = AttributeOwner::from_id(const_cast<ID *>(id));
return BKE_attribute_to_index(owner, name, ATTR_DOMAIN_MASK_COLOR, CD_MASK_COLOR_ALL) != -1;
}
StringRef BKE_uv_map_pin_name_get(const StringRef uv_map_name, char *buffer)
{
BLI_assert(strlen(UV_PINNED_NAME) == 2);
BLI_assert(uv_map_name.size() < MAX_CUSTOMDATA_LAYER_NAME - 4);
const auto result = fmt::format_to_n(
buffer, MAX_CUSTOMDATA_LAYER_NAME, ".{}.{}", UV_PINNED_NAME, uv_map_name);
return StringRef(buffer, result.size);
}
} // namespace blender