Files
test2/source/blender/blenkernel/intern/attribute_access.cc
2025-10-17 17:22:16 +02:00

1226 lines
39 KiB
C++

/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include <utility>
#include "BKE_anonymous_attribute_id.hh"
#include "BKE_attribute_legacy_convert.hh"
#include "BKE_attribute_math.hh"
#include "BKE_curves.hh"
#include "BKE_customdata.hh"
#include "BKE_geometry_set.hh"
#include "BKE_type_conversions.hh"
#include "DNA_ID.h"
#include "DNA_grease_pencil_types.h"
#include "DNA_mesh_types.h"
#include "DNA_meshdata_types.h"
#include "DNA_pointcloud_types.h"
#include "BLI_array_utils.hh"
#include "BLI_color.hh"
#include "BLI_math_vector_types.hh"
#include "BLI_span.hh"
#include "BLT_translation.hh"
#include "FN_field.hh"
#include "attribute_access_intern.hh"
#ifndef NDEBUG
# include <iostream>
#endif
namespace blender::bke {
const CPPType &attribute_type_to_cpp_type(const AttrType type)
{
switch (type) {
case AttrType::Bool:
return CPPType::get<bool>();
case AttrType::Int8:
return CPPType::get<int8_t>();
case AttrType::Int16_2D:
return CPPType::get<short2>();
case AttrType::Int32:
return CPPType::get<int>();
case AttrType::Int32_2D:
return CPPType::get<int2>();
case AttrType::Float:
return CPPType::get<float>();
case AttrType::Float2:
return CPPType::get<float2>();
case AttrType::Float3:
return CPPType::get<float3>();
case AttrType::Float4x4:
return CPPType::get<float4x4>();
case AttrType::ColorByte:
return CPPType::get<ColorGeometry4b>();
case AttrType::ColorFloat:
return CPPType::get<ColorGeometry4f>();
case AttrType::Quaternion:
return CPPType::get<math::Quaternion>();
case AttrType::String:
return CPPType::get<MStringProperty>();
}
BLI_assert_unreachable();
return CPPType::get<bool>();
}
AttrType cpp_type_to_attribute_type(const CPPType &type)
{
if (type.is<float>()) {
return AttrType::Float;
}
if (type.is<float2>()) {
return AttrType::Float2;
}
if (type.is<float3>()) {
return AttrType::Float3;
}
if (type.is<int>()) {
return AttrType::Int32;
}
if (type.is<int2>()) {
return AttrType::Int32_2D;
}
if (type.is<ColorGeometry4f>()) {
return AttrType::ColorFloat;
}
if (type.is<bool>()) {
return AttrType::Bool;
}
if (type.is<int8_t>()) {
return AttrType::Int8;
}
if (type.is<ColorGeometry4b>()) {
return AttrType::ColorByte;
}
if (type.is<math::Quaternion>()) {
return AttrType::Quaternion;
}
if (type.is<float4x4>()) {
return AttrType::Float4x4;
}
if (type.is<short2>()) {
return AttrType::Int16_2D;
}
if (type.is<MStringProperty>()) {
return AttrType::String;
}
BLI_assert_unreachable();
return AttrType::Bool;
}
const blender::CPPType *custom_data_type_to_cpp_type(const eCustomDataType type)
{
switch (type) {
case CD_PROP_FLOAT:
return &CPPType::get<float>();
case CD_PROP_FLOAT2:
return &CPPType::get<float2>();
case CD_PROP_FLOAT3:
return &CPPType::get<float3>();
case CD_PROP_INT32:
return &CPPType::get<int>();
case CD_PROP_INT32_2D:
return &CPPType::get<int2>();
case CD_PROP_COLOR:
return &CPPType::get<ColorGeometry4f>();
case CD_PROP_BOOL:
return &CPPType::get<bool>();
case CD_PROP_INT8:
return &CPPType::get<int8_t>();
case CD_PROP_BYTE_COLOR:
return &CPPType::get<ColorGeometry4b>();
case CD_PROP_QUATERNION:
return &CPPType::get<math::Quaternion>();
case CD_PROP_FLOAT4X4:
return &CPPType::get<float4x4>();
case CD_PROP_INT16_2D:
return &CPPType::get<short2>();
case CD_PROP_STRING:
return &CPPType::get<MStringProperty>();
default:
return nullptr;
}
}
eCustomDataType cpp_type_to_custom_data_type(const blender::CPPType &type)
{
if (type.is<float>()) {
return CD_PROP_FLOAT;
}
if (type.is<float2>()) {
return CD_PROP_FLOAT2;
}
if (type.is<float3>()) {
return CD_PROP_FLOAT3;
}
if (type.is<int>()) {
return CD_PROP_INT32;
}
if (type.is<int2>()) {
return CD_PROP_INT32_2D;
}
if (type.is<ColorGeometry4f>()) {
return CD_PROP_COLOR;
}
if (type.is<bool>()) {
return CD_PROP_BOOL;
}
if (type.is<int8_t>()) {
return CD_PROP_INT8;
}
if (type.is<ColorGeometry4b>()) {
return CD_PROP_BYTE_COLOR;
}
if (type.is<math::Quaternion>()) {
return CD_PROP_QUATERNION;
}
if (type.is<float4x4>()) {
return CD_PROP_FLOAT4X4;
}
if (type.is<short2>()) {
return CD_PROP_INT16_2D;
}
if (type.is<MStringProperty>()) {
return CD_PROP_STRING;
}
BLI_assert_unreachable();
return CD_PROP_FLOAT;
}
const char *no_procedural_access_message = N_(
"This attribute cannot be accessed in a procedural context");
bool allow_procedural_attribute_access(StringRef attribute_name)
{
if (attribute_name.startswith(".corner")) {
return false;
}
if (attribute_name.startswith(".edge")) {
return false;
}
if (attribute_name.startswith(".select")) {
return false;
}
if (attribute_name.startswith(".sculpt")) {
return false;
}
if (attribute_name.startswith(".hide")) {
return false;
}
if (attribute_name.startswith(".uv")) {
return false;
}
if (attribute_name == ".reference_index") {
return false;
}
if (attribute_name.startswith("." UV_PINNED_NAME ".")) {
return false;
}
return true;
}
static int attribute_data_type_complexity(const AttrType data_type)
{
switch (data_type) {
case AttrType::Bool:
return 0;
case AttrType::Int8:
return 1;
case AttrType::Int32:
return 2;
case AttrType::Float:
return 3;
case AttrType::Int16_2D:
return 4;
case AttrType::Int32_2D:
return 5;
case AttrType::Float2:
return 6;
case AttrType::Float3:
return 7;
case AttrType::ColorByte:
return 8;
case AttrType::Quaternion:
return 9;
case AttrType::ColorFloat:
return 10;
case AttrType::Float4x4:
return 11;
#if 0 /* These attribute types are not supported yet. */
case AttrType::String:
return 12;
#endif
default:
/* Only accept "generic" custom data types used by the attribute system. */
BLI_assert_unreachable();
return 0;
}
}
AttrType attribute_data_type_highest_complexity(Span<AttrType> data_types)
{
int highest_complexity = INT_MIN;
AttrType most_complex_type = AttrType::ColorFloat;
for (const AttrType data_type : data_types) {
const int complexity = attribute_data_type_complexity(data_type);
if (complexity > highest_complexity) {
highest_complexity = complexity;
most_complex_type = data_type;
}
}
return most_complex_type;
}
/**
* \note Generally the order should mirror the order of the domains
* established in each component's GeometryAttributeProviders.
*/
static int attribute_domain_priority(const AttrDomain domain)
{
switch (domain) {
case AttrDomain::Instance:
return 0;
case AttrDomain::Layer:
return 1;
case AttrDomain::Curve:
return 2;
case AttrDomain::Face:
return 3;
case AttrDomain::Edge:
return 4;
case AttrDomain::Point:
return 5;
case AttrDomain::Corner:
return 6;
default:
/* Domain not supported in nodes yet. */
BLI_assert_unreachable();
return 0;
}
}
AttrDomain attribute_domain_highest_priority(Span<AttrDomain> domains)
{
int highest_priority = INT_MIN;
AttrDomain highest_priority_domain = AttrDomain::Corner;
for (const AttrDomain domain : domains) {
const int priority = attribute_domain_priority(domain);
if (priority > highest_priority) {
highest_priority = priority;
highest_priority_domain = domain;
}
}
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
* \{ */
static GVArray try_adapt_data_type(GVArray varray, const CPPType &to_type)
{
const DataTypeConversions &conversions = get_implicit_type_conversions();
return conversions.try_convert(std::move(varray), to_type);
}
std::optional<AttributeAccessor> AttributeAccessor::from_id(const ID &id)
{
switch (GS(id.name)) {
case ID_ME:
return reinterpret_cast<const Mesh &>(id).attributes();
case ID_PT:
return reinterpret_cast<const PointCloud &>(id).attributes();
case ID_CV:
return reinterpret_cast<const Curves &>(id).geometry.wrap().attributes();
case ID_GP:
return reinterpret_cast<const GreasePencil &>(id).attributes();
default:
return {};
}
return {};
}
static GAttributeReader adapt_domain_and_type_if_necessary(GAttributeReader attribute,
const std::optional<AttrDomain> domain,
const std::optional<AttrType> data_type,
const AttributeAccessor &accessor)
{
if (!attribute) {
return {};
}
if (domain.has_value()) {
if (attribute.domain != domain) {
attribute.varray = accessor.adapt_domain(attribute.varray, attribute.domain, *domain);
attribute.domain = *domain;
attribute.sharing_info = nullptr;
if (!attribute.varray) {
return {};
}
}
}
if (data_type.has_value()) {
const CPPType &type = attribute_type_to_cpp_type(*data_type);
if (attribute.varray.type() != type) {
attribute.varray = try_adapt_data_type(std::move(attribute.varray), type);
attribute.sharing_info = nullptr;
if (!attribute.varray) {
return {};
}
}
}
return attribute;
}
GAttributeReader AttributeAccessor::lookup(const StringRef attribute_id,
const std::optional<AttrDomain> domain,
const std::optional<AttrType> data_type) const
{
return adapt_domain_and_type_if_necessary(this->lookup(attribute_id), domain, data_type, *this);
}
GAttributeReader AttributeIter::get(std::optional<AttrDomain> domain,
std::optional<AttrType> data_type) const
{
BLI_assert(this->accessor != nullptr);
return adapt_domain_and_type_if_necessary(this->get(), domain, data_type, *accessor);
}
GAttributeReader AttributeAccessor::lookup_or_default(const StringRef attribute_id,
const AttrDomain domain,
const AttrType data_type,
const void *default_value) const
{
GAttributeReader attribute = this->lookup(attribute_id, domain, data_type);
if (attribute) {
return attribute;
}
const CPPType &type = attribute_type_to_cpp_type(data_type);
const int64_t domain_size = this->domain_size(domain);
if (default_value == nullptr) {
return {GVArray::from_single_ref(type, domain_size, type.default_value()), domain, nullptr};
}
return {GVArray::from_single(type, domain_size, default_value), domain, nullptr};
}
bool AttributeAccessor::contains(const StringRef attribute_id) const
{
bool found = false;
this->foreach_attribute([&](const AttributeIter &iter) {
if (attribute_id == iter.name) {
found = true;
iter.stop();
}
});
return found;
}
std::optional<AttributeMetaData> AttributeAccessor::lookup_meta_data(
const StringRef attribute_id) const
{
std::optional<AttributeMetaData> meta_data;
this->foreach_attribute([&](const AttributeIter &iter) {
if (attribute_id == iter.name) {
meta_data = AttributeMetaData{iter.domain, iter.data_type};
iter.stop();
}
});
return meta_data;
}
Set<StringRefNull> AttributeAccessor::all_ids() const
{
Set<StringRefNull> ids;
this->foreach_attribute([&](const AttributeIter &iter) { ids.add(iter.name); });
return ids;
}
void MutableAttributeAccessor::remove_anonymous()
{
Vector<std::string> anonymous_ids;
for (const StringRef id : this->all_ids()) {
if (attribute_name_is_anonymous(id)) {
anonymous_ids.append(id);
}
}
while (!anonymous_ids.is_empty()) {
this->remove(anonymous_ids.pop_last());
}
}
/**
* Debug utility that checks whether the #finish function of an #AttributeWriter has been called.
*/
#ifndef NDEBUG
struct FinishCallChecker {
std::string name;
bool finish_called = false;
std::function<void()> real_finish_fn;
~FinishCallChecker()
{
if (!this->finish_called) {
std::cerr << "Forgot to call `finish()` for '" << this->name << "'.\n";
}
}
};
#endif
GAttributeWriter MutableAttributeAccessor::lookup_for_write(const StringRef attribute_id)
{
GAttributeWriter attribute = fn_->lookup_for_write(owner_, attribute_id);
/* Check that the #finish method is called in debug builds. */
#ifndef NDEBUG
if (attribute) {
auto checker = std::make_shared<FinishCallChecker>();
checker->name = attribute_id;
checker->real_finish_fn = attribute.tag_modified_fn;
attribute.tag_modified_fn = [checker]() {
if (checker->real_finish_fn) {
checker->real_finish_fn();
}
checker->finish_called = true;
};
}
#endif
return attribute;
}
GSpanAttributeWriter MutableAttributeAccessor::lookup_for_write_span(const StringRef attribute_id)
{
GAttributeWriter attribute = this->lookup_for_write(attribute_id);
if (attribute) {
return GSpanAttributeWriter{std::move(attribute), true};
}
return {};
}
GAttributeWriter MutableAttributeAccessor::lookup_or_add_for_write(
const StringRef attribute_id,
const AttrDomain domain,
const AttrType data_type,
const AttributeInit &initializer)
{
std::optional<AttributeMetaData> meta_data = this->lookup_meta_data(attribute_id);
if (meta_data.has_value()) {
if (meta_data->domain == domain && meta_data->data_type == data_type) {
return this->lookup_for_write(attribute_id);
}
return {};
}
if (this->add(attribute_id, domain, data_type, initializer)) {
return this->lookup_for_write(attribute_id);
}
return {};
}
GSpanAttributeWriter MutableAttributeAccessor::lookup_or_add_for_write_span(
const StringRef attribute_id,
const AttrDomain domain,
const AttrType data_type,
const AttributeInit &initializer)
{
GAttributeWriter attribute = this->lookup_or_add_for_write(
attribute_id, domain, data_type, initializer);
if (attribute) {
return GSpanAttributeWriter{std::move(attribute), true};
}
return {};
}
GSpanAttributeWriter MutableAttributeAccessor::lookup_or_add_for_write_only_span(
const StringRef attribute_id, const AttrDomain domain, const AttrType data_type)
{
GAttributeWriter attribute = this->lookup_or_add_for_write(
attribute_id, domain, data_type, AttributeInitConstruct());
if (attribute) {
return GSpanAttributeWriter{std::move(attribute), false};
}
return {};
}
bool MutableAttributeAccessor::rename(const StringRef old_attribute_id,
const StringRef new_attribute_id)
{
if (old_attribute_id == new_attribute_id) {
return true;
}
if (this->contains(new_attribute_id)) {
return false;
}
const GAttributeReader old_attribute = this->lookup(old_attribute_id);
if (!old_attribute) {
return false;
}
const AttrType type = cpp_type_to_attribute_type(old_attribute.varray.type());
if (old_attribute.sharing_info != nullptr && old_attribute.varray.is_span()) {
if (!this->add(new_attribute_id,
old_attribute.domain,
type,
AttributeInitShared{old_attribute.varray.get_internal_span().data(),
*old_attribute.sharing_info}))
{
return false;
}
}
else {
if (!this->add(new_attribute_id,
old_attribute.domain,
type,
AttributeInitVArray{old_attribute.varray}))
{
return false;
}
}
this->remove(old_attribute_id);
return true;
}
fn::GField AttributeValidator::validate_field_if_necessary(const fn::GField &field) const
{
if (function) {
auto validate_op = fn::FieldOperation::from(*function, {field});
return fn::GField(validate_op);
}
return field;
}
Vector<AttributeTransferData> retrieve_attributes_for_transfer(
const AttributeAccessor src_attributes,
MutableAttributeAccessor dst_attributes,
Span<AttrDomain> domains,
const bke::AttributeFilter &attribute_filter)
{
Vector<AttributeTransferData> attributes;
src_attributes.foreach_attribute([&](const AttributeIter &iter) {
if (!domains.contains(iter.domain)) {
return;
}
if (iter.data_type == AttrType::String) {
return;
}
if (attribute_filter.allow_skip(iter.name)) {
return;
}
GVArray src = *iter.get();
GSpanAttributeWriter dst = dst_attributes.lookup_or_add_for_write_only_span(
iter.name, iter.domain, iter.data_type);
/* Skip unsupported attributes. */
if (!dst) {
return;
}
attributes.append({std::move(src), iter.name, {iter.domain, iter.data_type}, std::move(dst)});
});
return attributes;
}
/** \} */
void gather_attributes(const AttributeAccessor src_attributes,
const AttrDomain src_domain,
const AttrDomain dst_domain,
const AttributeFilter &attribute_filter,
const IndexMask &selection,
MutableAttributeAccessor dst_attributes)
{
const int src_size = src_attributes.domain_size(src_domain);
src_attributes.foreach_attribute([&](const AttributeIter &iter) {
if (iter.domain != src_domain) {
return;
}
if (iter.data_type == AttrType::String) {
return;
}
if (attribute_filter.allow_skip(iter.name)) {
return;
}
const GAttributeReader src = iter.get(src_domain);
if (selection.size() == src_size && src.sharing_info && src.varray.is_span()) {
const AttributeInitShared init(src.varray.get_internal_span().data(), *src.sharing_info);
if (dst_attributes.add(iter.name, dst_domain, iter.data_type, init)) {
return;
}
}
GSpanAttributeWriter dst = dst_attributes.lookup_or_add_for_write_only_span(
iter.name, dst_domain, iter.data_type);
if (!dst) {
return;
}
array_utils::gather(src.varray, selection, dst.span);
dst.finish();
});
}
void gather_attributes(const AttributeAccessor src_attributes,
const AttrDomain src_domain,
const AttrDomain dst_domain,
const AttributeFilter &attribute_filter,
const Span<int> indices,
MutableAttributeAccessor dst_attributes)
{
if (array_utils::indices_are_range(indices, IndexRange(src_attributes.domain_size(src_domain))))
{
copy_attributes(src_attributes, src_domain, dst_domain, attribute_filter, dst_attributes);
}
else {
src_attributes.foreach_attribute([&](const AttributeIter &iter) {
if (iter.domain != src_domain) {
return;
}
if (iter.data_type == AttrType::String) {
return;
}
if (attribute_filter.allow_skip(iter.name)) {
return;
}
const GAttributeReader src = iter.get(src_domain);
GSpanAttributeWriter dst = dst_attributes.lookup_or_add_for_write_only_span(
iter.name, dst_domain, iter.data_type);
if (!dst) {
return;
}
attribute_math::gather(src.varray, indices, dst.span);
dst.finish();
});
}
}
void gather_attributes_group_to_group(const AttributeAccessor src_attributes,
const AttrDomain src_domain,
const AttrDomain dst_domain,
const AttributeFilter &attribute_filter,
const OffsetIndices<int> src_offsets,
const OffsetIndices<int> dst_offsets,
const IndexMask &selection,
MutableAttributeAccessor dst_attributes)
{
if (selection.size() == src_offsets.size()) {
if (src_attributes.domain_size(src_domain) == dst_attributes.domain_size(src_domain)) {
/* When all groups are selected and the domains are the same size, all values are copied,
* because corresponding groups are required to be the same size. */
copy_attributes(src_attributes, src_domain, dst_domain, attribute_filter, dst_attributes);
return;
}
}
src_attributes.foreach_attribute([&](const AttributeIter &iter) {
if (iter.domain != src_domain) {
return;
}
if (iter.data_type == AttrType::String) {
return;
}
if (attribute_filter.allow_skip(iter.name)) {
return;
}
const GVArraySpan src = *iter.get(src_domain);
GSpanAttributeWriter dst = dst_attributes.lookup_or_add_for_write_only_span(
iter.name, dst_domain, iter.data_type);
if (!dst) {
return;
}
attribute_math::gather_group_to_group(src_offsets, dst_offsets, selection, src, dst.span);
dst.finish();
});
}
void gather_attributes_to_groups(const AttributeAccessor src_attributes,
const AttrDomain src_domain,
const AttrDomain dst_domain,
const AttributeFilter &attribute_filter,
const OffsetIndices<int> dst_offsets,
const IndexMask &src_selection,
MutableAttributeAccessor dst_attributes)
{
src_attributes.foreach_attribute([&](const AttributeIter &iter) {
if (iter.domain != src_domain) {
return;
}
if (iter.data_type == AttrType::String) {
return;
}
if (attribute_filter.allow_skip(iter.name)) {
return;
}
const GVArraySpan src = *iter.get(src_domain);
GSpanAttributeWriter dst = dst_attributes.lookup_or_add_for_write_only_span(
iter.name, dst_domain, iter.data_type);
if (!dst) {
return;
}
attribute_math::gather_to_groups(dst_offsets, src_selection, src, dst.span);
dst.finish();
});
}
void copy_attributes(const AttributeAccessor src_attributes,
const AttrDomain src_domain,
const AttrDomain dst_domain,
const AttributeFilter &attribute_filter,
MutableAttributeAccessor dst_attributes)
{
BLI_assert(src_attributes.domain_size(src_domain) == dst_attributes.domain_size(dst_domain));
gather_attributes(src_attributes,
src_domain,
dst_domain,
attribute_filter,
IndexMask(src_attributes.domain_size(src_domain)),
dst_attributes);
}
void copy_attributes_group_to_group(const AttributeAccessor src_attributes,
const AttrDomain src_domain,
const AttrDomain dst_domain,
const AttributeFilter &attribute_filter,
const OffsetIndices<int> src_offsets,
const OffsetIndices<int> dst_offsets,
const IndexMask &selection,
MutableAttributeAccessor dst_attributes)
{
if (selection.is_empty()) {
return;
}
src_attributes.foreach_attribute([&](const AttributeIter &iter) {
if (iter.domain != src_domain) {
return;
}
if (iter.data_type == AttrType::String) {
return;
}
if (attribute_filter.allow_skip(iter.name)) {
return;
}
const GVArraySpan src = *iter.get(src_domain);
const bool dst_already_exists = dst_attributes.contains(iter.name);
GSpanAttributeWriter dst = dst_attributes.lookup_or_add_for_write_only_span(
iter.name, dst_domain, iter.data_type);
if (!dst) {
return;
}
if (!dst_already_exists) {
/* Skip filling with the default value if all of the data is going to be filled. */
if (!(dst_offsets.total_size() == dst.span.size() && selection.size() == dst_offsets.size()))
{
const CPPType &type = dst.span.type();
if (dst_attributes.is_builtin(iter.name)) {
if (const GPointer value = dst_attributes.get_builtin_default(iter.name)) {
type.fill_construct_n(value.get(), dst.span.data(), dst.span.size());
}
else {
type.fill_construct_n(type.default_value(), dst.span.data(), dst.span.size());
}
}
else {
type.fill_construct_n(type.default_value(), dst.span.data(), dst.span.size());
}
}
}
array_utils::copy_group_to_group(src_offsets, dst_offsets, selection, src, dst.span);
dst.finish();
});
}
void fill_attribute_range_default(MutableAttributeAccessor attributes,
const AttrDomain domain,
const AttributeFilter &attribute_filter,
const IndexRange range)
{
/* While it is valid to call this function for any valid range which can be placed in target
* domain, it is computationally costly to perform this loop. This check is COW elision and not
* just loop skip. */
if (range.is_empty()) {
return;
}
attributes.foreach_attribute([&](const AttributeIter &iter) {
if (iter.domain != domain) {
return;
}
if (attribute_filter.allow_skip(iter.name)) {
return;
}
if (iter.data_type == AttrType::String) {
return;
}
GSpanAttributeWriter attribute = attributes.lookup_for_write_span(iter.name);
const CPPType &type = attribute.span.type();
GMutableSpan data = attribute.span.slice(range);
type.fill_assign_n(type.default_value(), data.data(), data.size());
attribute.finish();
});
}
void transform_custom_normal_attribute(const float4x4 &transform,
MutableAttributeAccessor &attributes)
{
const GAttributeReader normals = attributes.lookup("custom_normal");
if (!normals) {
return;
}
if (!normals.varray.type().is<float3>()) {
return;
}
if (normals.sharing_info->is_mutable()) {
SpanAttributeWriter<float3> normals = attributes.lookup_for_write_span<float3>(
"custom_normal");
math::transform_normals(float3x3(transform), normals.span);
normals.finish();
}
else {
/* It's a bit faster to combine transforming and copying the attribute if it's shared. */
float3 *new_data = MEM_malloc_arrayN<float3>(size_t(normals.varray.size()), __func__);
math::transform_normals(VArraySpan(normals.varray.typed<float3>()),
float3x3(transform),
{new_data, normals.varray.size()});
const AttrDomain domain = normals.domain;
attributes.remove("custom_normal");
attributes.add<float3>("custom_normal", domain, AttributeInitMoveArray(new_data));
}
}
} // namespace blender::bke