1014 lines
34 KiB
C++
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
|