This repository has been archived on 2023-10-09. You can view files and clone it, but cannot push or open issues or pull requests.
Files
blender-archive/source/blender/blenkernel/intern/attribute_access.cc
Hans Goudey 93fd07e19c Geometry Nodes: Copy spline attributes in the curve resample node
Previously only point domain attributes were copied to the result curve.
2021-06-09 15:54:26 -05:00

1230 lines
40 KiB
C++

/*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include <utility>
#include "BKE_attribute_access.hh"
#include "BKE_attribute_math.hh"
#include "BKE_customdata.h"
#include "BKE_deform.h"
#include "BKE_geometry_set.hh"
#include "BKE_mesh.h"
#include "BKE_pointcloud.h"
#include "DNA_mesh_types.h"
#include "DNA_meshdata_types.h"
#include "DNA_pointcloud_types.h"
#include "BLI_color.hh"
#include "BLI_float2.hh"
#include "BLI_span.hh"
#include "CLG_log.h"
#include "NOD_type_conversions.hh"
#include "attribute_access_intern.hh"
static CLG_LogRef LOG = {"bke.attribute_access"};
using blender::float3;
using blender::Set;
using blender::StringRef;
using blender::StringRefNull;
using blender::fn::GMutableSpan;
using blender::fn::GSpan;
using blender::fn::GVArray_For_GSpan;
using blender::fn::GVArray_For_SingleValue;
namespace blender::bke {
const blender::fn::CPPType *custom_data_type_to_cpp_type(const CustomDataType 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_COLOR:
return &CPPType::get<ColorGeometry4f>();
case CD_PROP_BOOL:
return &CPPType::get<bool>();
default:
return nullptr;
}
return nullptr;
}
CustomDataType cpp_type_to_custom_data_type(const blender::fn::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<ColorGeometry4f>()) {
return CD_PROP_COLOR;
}
if (type.is<bool>()) {
return CD_PROP_BOOL;
}
return static_cast<CustomDataType>(-1);
}
static int attribute_data_type_complexity(const CustomDataType data_type)
{
switch (data_type) {
case CD_PROP_BOOL:
return 0;
case CD_PROP_INT32:
return 1;
case CD_PROP_FLOAT:
return 2;
case CD_PROP_FLOAT2:
return 3;
case CD_PROP_FLOAT3:
return 4;
case CD_PROP_COLOR:
return 5;
#if 0 /* These attribute types are not supported yet. */
case CD_MLOOPCOL:
return 3;
case CD_PROP_STRING:
return 6;
#endif
default:
/* Only accept "generic" custom data types used by the attribute system. */
BLI_assert_unreachable();
return 0;
}
}
CustomDataType attribute_data_type_highest_complexity(Span<CustomDataType> data_types)
{
int highest_complexity = INT_MIN;
CustomDataType most_complex_type = CD_PROP_COLOR;
for (const CustomDataType 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 ComponentAttributeProviders.
*/
static int attribute_domain_priority(const AttributeDomain domain)
{
switch (domain) {
case ATTR_DOMAIN_CURVE:
return 0;
case ATTR_DOMAIN_FACE:
return 1;
case ATTR_DOMAIN_EDGE:
return 2;
case ATTR_DOMAIN_POINT:
return 3;
case ATTR_DOMAIN_CORNER:
return 4;
default:
/* Domain not supported in nodes yet. */
BLI_assert_unreachable();
return 0;
}
}
/**
* Domains with a higher "information density" have a higher priority, in order
* to choose a domain that will not lose data through domain conversion.
*/
AttributeDomain attribute_domain_highest_priority(Span<AttributeDomain> domains)
{
int highest_priority = INT_MIN;
AttributeDomain highest_priority_domain = ATTR_DOMAIN_CORNER;
for (const AttributeDomain domain : domains) {
const int priority = attribute_domain_priority(domain);
if (priority > highest_priority) {
highest_priority = priority;
highest_priority_domain = domain;
}
}
return highest_priority_domain;
}
void OutputAttribute::save()
{
save_has_been_called_ = true;
if (optional_span_varray_.has_value()) {
optional_span_varray_->save();
}
if (save_) {
save_(*this);
}
}
OutputAttribute::~OutputAttribute()
{
if (!save_has_been_called_) {
if (varray_) {
std::cout << "Warning: Call `save()` to make sure that changes persist in all cases.\n";
}
}
}
GVArrayPtr BuiltinCustomDataLayerProvider::try_get_for_read(
const GeometryComponent &component) const
{
const CustomData *custom_data = custom_data_access_.get_const_custom_data(component);
if (custom_data == nullptr) {
return {};
}
const int domain_size = component.attribute_domain_size(domain_);
const void *data = CustomData_get_layer(custom_data, stored_type_);
if (data == nullptr) {
return {};
}
return as_read_attribute_(data, domain_size);
}
GVMutableArrayPtr BuiltinCustomDataLayerProvider::try_get_for_write(
GeometryComponent &component) const
{
if (writable_ != Writable) {
return {};
}
CustomData *custom_data = custom_data_access_.get_custom_data(component);
if (custom_data == nullptr) {
return {};
}
const int domain_size = component.attribute_domain_size(domain_);
void *data = CustomData_get_layer(custom_data, stored_type_);
if (data == nullptr) {
return {};
}
void *new_data = CustomData_duplicate_referenced_layer(custom_data, stored_type_, domain_size);
if (data != new_data) {
custom_data_access_.update_custom_data_pointers(component);
data = new_data;
}
if (update_on_write_ != nullptr) {
update_on_write_(component);
}
return as_write_attribute_(data, domain_size);
}
bool BuiltinCustomDataLayerProvider::try_delete(GeometryComponent &component) const
{
if (deletable_ != Deletable) {
return false;
}
CustomData *custom_data = custom_data_access_.get_custom_data(component);
if (custom_data == nullptr) {
return {};
}
const int domain_size = component.attribute_domain_size(domain_);
const int layer_index = CustomData_get_layer_index(custom_data, stored_type_);
const bool delete_success = CustomData_free_layer(
custom_data, stored_type_, domain_size, layer_index);
if (delete_success) {
custom_data_access_.update_custom_data_pointers(component);
}
return delete_success;
}
static bool add_custom_data_layer_from_attribute_init(CustomData &custom_data,
const CustomDataType data_type,
const int domain_size,
const AttributeInit &initializer)
{
switch (initializer.type) {
case AttributeInit::Type::Default: {
void *data = CustomData_add_layer(&custom_data, data_type, CD_DEFAULT, nullptr, domain_size);
return data != nullptr;
}
case AttributeInit::Type::VArray: {
void *data = CustomData_add_layer(&custom_data, data_type, CD_DEFAULT, nullptr, domain_size);
if (data == nullptr) {
return false;
}
const GVArray *varray = static_cast<const AttributeInitVArray &>(initializer).varray;
varray->materialize_to_uninitialized(IndexRange(varray->size()), data);
return true;
}
case AttributeInit::Type::MoveArray: {
void *source_data = static_cast<const AttributeInitMove &>(initializer).data;
void *data = CustomData_add_layer(
&custom_data, data_type, CD_ASSIGN, source_data, domain_size);
if (data == nullptr) {
MEM_freeN(source_data);
return false;
}
return true;
}
}
BLI_assert_unreachable();
return false;
}
bool BuiltinCustomDataLayerProvider::try_create(GeometryComponent &component,
const AttributeInit &initializer) const
{
if (createable_ != Creatable) {
return false;
}
CustomData *custom_data = custom_data_access_.get_custom_data(component);
if (custom_data == nullptr) {
return false;
}
if (CustomData_get_layer(custom_data, stored_type_) != nullptr) {
/* Exists already. */
return false;
}
const int domain_size = component.attribute_domain_size(domain_);
const bool success = add_custom_data_layer_from_attribute_init(
*custom_data, stored_type_, domain_size, initializer);
if (success) {
custom_data_access_.update_custom_data_pointers(component);
}
return success;
}
bool BuiltinCustomDataLayerProvider::exists(const GeometryComponent &component) const
{
const CustomData *custom_data = custom_data_access_.get_const_custom_data(component);
if (custom_data == nullptr) {
return false;
}
const void *data = CustomData_get_layer(custom_data, stored_type_);
return data != nullptr;
}
ReadAttributeLookup CustomDataAttributeProvider::try_get_for_read(
const GeometryComponent &component, const StringRef attribute_name) const
{
const CustomData *custom_data = custom_data_access_.get_const_custom_data(component);
if (custom_data == nullptr) {
return {};
}
const int domain_size = component.attribute_domain_size(domain_);
for (const CustomDataLayer &layer : Span(custom_data->layers, custom_data->totlayer)) {
if (layer.name != attribute_name) {
continue;
}
const CustomDataType data_type = (CustomDataType)layer.type;
switch (data_type) {
case CD_PROP_FLOAT:
return this->layer_to_read_attribute<float>(layer, domain_size);
case CD_PROP_FLOAT2:
return this->layer_to_read_attribute<float2>(layer, domain_size);
case CD_PROP_FLOAT3:
return this->layer_to_read_attribute<float3>(layer, domain_size);
case CD_PROP_INT32:
return this->layer_to_read_attribute<int>(layer, domain_size);
case CD_PROP_COLOR:
return this->layer_to_read_attribute<ColorGeometry4f>(layer, domain_size);
case CD_PROP_BOOL:
return this->layer_to_read_attribute<bool>(layer, domain_size);
default:
break;
}
}
return {};
}
WriteAttributeLookup CustomDataAttributeProvider::try_get_for_write(
GeometryComponent &component, const StringRef attribute_name) const
{
CustomData *custom_data = custom_data_access_.get_custom_data(component);
if (custom_data == nullptr) {
return {};
}
const int domain_size = component.attribute_domain_size(domain_);
for (CustomDataLayer &layer : MutableSpan(custom_data->layers, custom_data->totlayer)) {
if (layer.name != attribute_name) {
continue;
}
CustomData_duplicate_referenced_layer_named(custom_data, layer.type, layer.name, domain_size);
const CustomDataType data_type = (CustomDataType)layer.type;
switch (data_type) {
case CD_PROP_FLOAT:
return this->layer_to_write_attribute<float>(layer, domain_size);
case CD_PROP_FLOAT2:
return this->layer_to_write_attribute<float2>(layer, domain_size);
case CD_PROP_FLOAT3:
return this->layer_to_write_attribute<float3>(layer, domain_size);
case CD_PROP_INT32:
return this->layer_to_write_attribute<int>(layer, domain_size);
case CD_PROP_COLOR:
return this->layer_to_write_attribute<ColorGeometry4f>(layer, domain_size);
case CD_PROP_BOOL:
return this->layer_to_write_attribute<bool>(layer, domain_size);
default:
break;
}
}
return {};
}
bool CustomDataAttributeProvider::try_delete(GeometryComponent &component,
const StringRef attribute_name) const
{
CustomData *custom_data = custom_data_access_.get_custom_data(component);
if (custom_data == nullptr) {
return false;
}
const int domain_size = component.attribute_domain_size(domain_);
for (const int i : IndexRange(custom_data->totlayer)) {
const CustomDataLayer &layer = custom_data->layers[i];
if (this->type_is_supported((CustomDataType)layer.type) && layer.name == attribute_name) {
CustomData_free_layer(custom_data, layer.type, domain_size, i);
return true;
}
}
return false;
}
static bool add_named_custom_data_layer_from_attribute_init(const StringRef attribute_name,
CustomData &custom_data,
const CustomDataType data_type,
const int domain_size,
const AttributeInit &initializer)
{
char attribute_name_c[MAX_NAME];
attribute_name.copy(attribute_name_c);
switch (initializer.type) {
case AttributeInit::Type::Default: {
void *data = CustomData_add_layer_named(
&custom_data, data_type, CD_DEFAULT, nullptr, domain_size, attribute_name_c);
return data != nullptr;
}
case AttributeInit::Type::VArray: {
void *data = CustomData_add_layer_named(
&custom_data, data_type, CD_DEFAULT, nullptr, domain_size, attribute_name_c);
if (data == nullptr) {
return false;
}
const GVArray *varray = static_cast<const AttributeInitVArray &>(initializer).varray;
varray->materialize_to_uninitialized(IndexRange(varray->size()), data);
return true;
}
case AttributeInit::Type::MoveArray: {
void *source_data = static_cast<const AttributeInitMove &>(initializer).data;
void *data = CustomData_add_layer_named(
&custom_data, data_type, CD_ASSIGN, source_data, domain_size, attribute_name_c);
if (data == nullptr) {
MEM_freeN(source_data);
return false;
}
return true;
}
}
BLI_assert_unreachable();
return false;
}
bool CustomDataAttributeProvider::try_create(GeometryComponent &component,
const StringRef attribute_name,
const AttributeDomain domain,
const CustomDataType 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(component);
if (custom_data == nullptr) {
return false;
}
for (const CustomDataLayer &layer : Span(custom_data->layers, custom_data->totlayer)) {
if (layer.name == attribute_name) {
return false;
}
}
const int domain_size = component.attribute_domain_size(domain_);
add_named_custom_data_layer_from_attribute_init(
attribute_name, *custom_data, data_type, domain_size, initializer);
return true;
}
bool CustomDataAttributeProvider::foreach_attribute(const GeometryComponent &component,
const AttributeForeachCallback callback) const
{
const CustomData *custom_data = custom_data_access_.get_const_custom_data(component);
if (custom_data == nullptr) {
return true;
}
for (const CustomDataLayer &layer : Span(custom_data->layers, custom_data->totlayer)) {
const CustomDataType data_type = (CustomDataType)layer.type;
if (this->type_is_supported(data_type)) {
AttributeMetaData meta_data{domain_, data_type};
if (!callback(layer.name, meta_data)) {
return false;
}
}
}
return true;
}
ReadAttributeLookup NamedLegacyCustomDataProvider::try_get_for_read(
const GeometryComponent &component, const StringRef attribute_name) const
{
const CustomData *custom_data = custom_data_access_.get_const_custom_data(component);
if (custom_data == nullptr) {
return {};
}
for (const CustomDataLayer &layer : Span(custom_data->layers, custom_data->totlayer)) {
if (layer.type == stored_type_) {
if (layer.name == attribute_name) {
const int domain_size = component.attribute_domain_size(domain_);
return {as_read_attribute_(layer.data, domain_size), domain_};
}
}
}
return {};
}
WriteAttributeLookup NamedLegacyCustomDataProvider::try_get_for_write(
GeometryComponent &component, const StringRef attribute_name) const
{
CustomData *custom_data = custom_data_access_.get_custom_data(component);
if (custom_data == nullptr) {
return {};
}
for (CustomDataLayer &layer : MutableSpan(custom_data->layers, custom_data->totlayer)) {
if (layer.type == stored_type_) {
if (layer.name == attribute_name) {
const int domain_size = component.attribute_domain_size(domain_);
void *data_old = layer.data;
void *data_new = CustomData_duplicate_referenced_layer_named(
custom_data, stored_type_, layer.name, domain_size);
if (data_old != data_new) {
custom_data_access_.update_custom_data_pointers(component);
}
return {as_write_attribute_(layer.data, domain_size), domain_};
}
}
}
return {};
}
bool NamedLegacyCustomDataProvider::try_delete(GeometryComponent &component,
const StringRef attribute_name) const
{
CustomData *custom_data = custom_data_access_.get_custom_data(component);
if (custom_data == nullptr) {
return false;
}
for (const int i : IndexRange(custom_data->totlayer)) {
const CustomDataLayer &layer = custom_data->layers[i];
if (layer.type == stored_type_) {
if (layer.name == attribute_name) {
const int domain_size = component.attribute_domain_size(domain_);
CustomData_free_layer(custom_data, stored_type_, domain_size, i);
custom_data_access_.update_custom_data_pointers(component);
return true;
}
}
}
return false;
}
bool NamedLegacyCustomDataProvider::foreach_attribute(
const GeometryComponent &component, const AttributeForeachCallback callback) const
{
const CustomData *custom_data = custom_data_access_.get_const_custom_data(component);
if (custom_data == nullptr) {
return true;
}
for (const CustomDataLayer &layer : Span(custom_data->layers, custom_data->totlayer)) {
if (layer.type == stored_type_) {
AttributeMetaData meta_data{domain_, attribute_type_};
if (!callback(layer.name, meta_data)) {
return false;
}
}
}
return true;
}
void NamedLegacyCustomDataProvider::foreach_domain(
const FunctionRef<void(AttributeDomain)> callback) const
{
callback(domain_);
}
CustomDataAttributes::CustomDataAttributes()
{
CustomData_reset(&data);
size_ = 0;
}
CustomDataAttributes::~CustomDataAttributes()
{
CustomData_free(&data, size_);
}
CustomDataAttributes::CustomDataAttributes(const CustomDataAttributes &other)
{
size_ = other.size_;
CustomData_copy(&other.data, &data, CD_MASK_ALL, CD_DUPLICATE, size_);
}
CustomDataAttributes::CustomDataAttributes(CustomDataAttributes &&other)
{
size_ = other.size_;
data = other.data;
CustomData_reset(&other.data);
}
CustomDataAttributes &CustomDataAttributes::operator=(const CustomDataAttributes &other)
{
if (this != &other) {
CustomData_copy(&other.data, &data, CD_MASK_ALL, CD_DUPLICATE, other.size_);
size_ = other.size_;
}
return *this;
}
std::optional<GSpan> CustomDataAttributes::get_for_read(const StringRef name) const
{
BLI_assert(size_ != 0);
for (const CustomDataLayer &layer : Span(data.layers, data.totlayer)) {
if (layer.name == name) {
const CPPType *cpp_type = custom_data_type_to_cpp_type((CustomDataType)layer.type);
BLI_assert(cpp_type != nullptr);
return GSpan(*cpp_type, layer.data, size_);
}
}
return {};
}
/**
* Return a virtual array for a stored attribute, or a single value virtual array with the default
* value if the attribute doesn't exist. If no default value is provided, the default value for the
* type will be used.
*/
GVArrayPtr CustomDataAttributes::get_for_read(const StringRef name,
const CustomDataType data_type,
const void *default_value) const
{
const CPPType *type = blender::bke::custom_data_type_to_cpp_type(data_type);
std::optional<GSpan> attribute = this->get_for_read(name);
if (!attribute) {
const int domain_size = this->size_;
return std::make_unique<GVArray_For_SingleValue>(
*type, domain_size, (default_value == nullptr) ? type->default_value() : default_value);
}
if (attribute->type() == *type) {
return std::make_unique<GVArray_For_GSpan>(*attribute);
}
const blender::nodes::DataTypeConversions &conversions =
blender::nodes::get_implicit_type_conversions();
return conversions.try_convert(std::make_unique<GVArray_For_GSpan>(*attribute), *type);
}
std::optional<GMutableSpan> CustomDataAttributes::get_for_write(const StringRef name)
{
/* If this assert hits, it most likely means that #reallocate was not called at some point. */
BLI_assert(size_ != 0);
for (CustomDataLayer &layer : MutableSpan(data.layers, data.totlayer)) {
if (layer.name == name) {
const CPPType *cpp_type = custom_data_type_to_cpp_type((CustomDataType)layer.type);
BLI_assert(cpp_type != nullptr);
return GMutableSpan(*cpp_type, layer.data, size_);
}
}
return {};
}
bool CustomDataAttributes::create(const StringRef name, const CustomDataType data_type)
{
char name_c[MAX_NAME];
name.copy(name_c);
void *result = CustomData_add_layer_named(&data, data_type, CD_DEFAULT, nullptr, size_, name_c);
return result != nullptr;
}
bool CustomDataAttributes::create_by_move(const blender::StringRef name,
const CustomDataType data_type,
void *buffer)
{
char name_c[MAX_NAME];
name.copy(name_c);
void *result = CustomData_add_layer_named(&data, data_type, CD_ASSIGN, buffer, size_, name_c);
return result != nullptr;
}
bool CustomDataAttributes::remove(const blender::StringRef name)
{
bool result = false;
for (const int i : IndexRange(data.totlayer)) {
const CustomDataLayer &layer = data.layers[i];
if (layer.name == name) {
CustomData_free_layer(&data, layer.type, size_, i);
result = true;
}
}
return result;
}
void CustomDataAttributes::reallocate(const int size)
{
size_ = size;
CustomData_realloc(&data, size);
}
bool CustomDataAttributes::foreach_attribute(const AttributeForeachCallback callback,
const AttributeDomain domain) const
{
for (const CustomDataLayer &layer : Span(data.layers, data.totlayer)) {
AttributeMetaData meta_data{domain, (CustomDataType)layer.type};
if (!callback(layer.name, meta_data)) {
return false;
}
}
return true;
}
} // namespace blender::bke
/* -------------------------------------------------------------------- */
/** \name Geometry Component
* \{ */
const blender::bke::ComponentAttributeProviders *GeometryComponent::get_attribute_providers() const
{
return nullptr;
}
bool GeometryComponent::attribute_domain_supported(const AttributeDomain domain) const
{
using namespace blender::bke;
const ComponentAttributeProviders *providers = this->get_attribute_providers();
if (providers == nullptr) {
return false;
}
return providers->supported_domains().contains(domain);
}
int GeometryComponent::attribute_domain_size(const AttributeDomain UNUSED(domain)) const
{
return 0;
}
bool GeometryComponent::attribute_is_builtin(const blender::StringRef attribute_name) const
{
using namespace blender::bke;
const ComponentAttributeProviders *providers = this->get_attribute_providers();
if (providers == nullptr) {
return false;
}
return providers->builtin_attribute_providers().contains_as(attribute_name);
}
blender::bke::ReadAttributeLookup GeometryComponent::attribute_try_get_for_read(
const StringRef attribute_name) const
{
using namespace blender::bke;
const ComponentAttributeProviders *providers = this->get_attribute_providers();
if (providers == nullptr) {
return {};
}
const BuiltinAttributeProvider *builtin_provider =
providers->builtin_attribute_providers().lookup_default_as(attribute_name, nullptr);
if (builtin_provider != nullptr) {
return {builtin_provider->try_get_for_read(*this), builtin_provider->domain()};
}
for (const DynamicAttributesProvider *dynamic_provider :
providers->dynamic_attribute_providers()) {
ReadAttributeLookup attribute = dynamic_provider->try_get_for_read(*this, attribute_name);
if (attribute) {
return attribute;
}
}
return {};
}
std::unique_ptr<blender::fn::GVArray> GeometryComponent::attribute_try_adapt_domain(
std::unique_ptr<blender::fn::GVArray> varray,
const AttributeDomain from_domain,
const AttributeDomain to_domain) const
{
if (from_domain == to_domain) {
return varray;
}
return {};
}
blender::bke::WriteAttributeLookup GeometryComponent::attribute_try_get_for_write(
const StringRef attribute_name)
{
using namespace blender::bke;
const ComponentAttributeProviders *providers = this->get_attribute_providers();
if (providers == nullptr) {
return {};
}
const BuiltinAttributeProvider *builtin_provider =
providers->builtin_attribute_providers().lookup_default_as(attribute_name, nullptr);
if (builtin_provider != nullptr) {
return {builtin_provider->try_get_for_write(*this), builtin_provider->domain()};
}
for (const DynamicAttributesProvider *dynamic_provider :
providers->dynamic_attribute_providers()) {
WriteAttributeLookup attribute = dynamic_provider->try_get_for_write(*this, attribute_name);
if (attribute) {
return attribute;
}
}
return {};
}
bool GeometryComponent::attribute_try_delete(const StringRef attribute_name)
{
using namespace blender::bke;
const ComponentAttributeProviders *providers = this->get_attribute_providers();
if (providers == nullptr) {
return {};
}
const BuiltinAttributeProvider *builtin_provider =
providers->builtin_attribute_providers().lookup_default_as(attribute_name, nullptr);
if (builtin_provider != nullptr) {
return builtin_provider->try_delete(*this);
}
bool success = false;
for (const DynamicAttributesProvider *dynamic_provider :
providers->dynamic_attribute_providers()) {
success = dynamic_provider->try_delete(*this, attribute_name) || success;
}
return success;
}
bool GeometryComponent::attribute_try_create(const StringRef attribute_name,
const AttributeDomain domain,
const CustomDataType data_type,
const AttributeInit &initializer)
{
using namespace blender::bke;
if (attribute_name.is_empty()) {
return false;
}
const ComponentAttributeProviders *providers = this->get_attribute_providers();
if (providers == nullptr) {
return false;
}
const BuiltinAttributeProvider *builtin_provider =
providers->builtin_attribute_providers().lookup_default_as(attribute_name, nullptr);
if (builtin_provider != nullptr) {
if (builtin_provider->domain() != domain) {
return false;
}
if (builtin_provider->data_type() != data_type) {
return false;
}
return builtin_provider->try_create(*this, initializer);
}
for (const DynamicAttributesProvider *dynamic_provider :
providers->dynamic_attribute_providers()) {
if (dynamic_provider->try_create(*this, attribute_name, domain, data_type, initializer)) {
return true;
}
}
return false;
}
bool GeometryComponent::attribute_try_create_builtin(const blender::StringRef attribute_name,
const AttributeInit &initializer)
{
using namespace blender::bke;
if (attribute_name.is_empty()) {
return false;
}
const ComponentAttributeProviders *providers = this->get_attribute_providers();
if (providers == nullptr) {
return false;
}
const BuiltinAttributeProvider *builtin_provider =
providers->builtin_attribute_providers().lookup_default_as(attribute_name, nullptr);
if (builtin_provider == nullptr) {
return false;
}
return builtin_provider->try_create(*this, initializer);
}
Set<std::string> GeometryComponent::attribute_names() const
{
Set<std::string> attributes;
this->attribute_foreach([&](StringRefNull name, const AttributeMetaData &UNUSED(meta_data)) {
attributes.add(name);
return true;
});
return attributes;
}
/**
* \return False if the callback explicitly returned false at any point, otherwise true,
* meaning the callback made it all the way through.
*/
bool GeometryComponent::attribute_foreach(const AttributeForeachCallback callback) const
{
using namespace blender::bke;
const ComponentAttributeProviders *providers = this->get_attribute_providers();
if (providers == nullptr) {
return true;
}
/* Keep track handled attribute names to make sure that we do not return the same name twice. */
Set<std::string> handled_attribute_names;
for (const BuiltinAttributeProvider *provider :
providers->builtin_attribute_providers().values()) {
if (provider->exists(*this)) {
AttributeMetaData meta_data{provider->domain(), provider->data_type()};
if (!callback(provider->name(), meta_data)) {
return false;
}
handled_attribute_names.add_new(provider->name());
}
}
for (const DynamicAttributesProvider *provider : providers->dynamic_attribute_providers()) {
const bool continue_loop = provider->foreach_attribute(
*this, [&](StringRefNull name, const AttributeMetaData &meta_data) {
if (handled_attribute_names.add(name)) {
return callback(name, meta_data);
}
return true;
});
if (!continue_loop) {
return false;
}
}
return true;
}
bool GeometryComponent::attribute_exists(const blender::StringRef attribute_name) const
{
blender::bke::ReadAttributeLookup attribute = this->attribute_try_get_for_read(attribute_name);
if (attribute) {
return true;
}
return false;
}
std::optional<AttributeMetaData> GeometryComponent::attribute_get_meta_data(
const StringRef attribute_name) const
{
std::optional<AttributeMetaData> result{std::nullopt};
this->attribute_foreach([&](StringRefNull name, const AttributeMetaData &meta_data) {
if (attribute_name == name) {
result = meta_data;
return false;
}
return true;
});
return result;
}
static std::unique_ptr<blender::fn::GVArray> try_adapt_data_type(
std::unique_ptr<blender::fn::GVArray> varray, const blender::fn::CPPType &to_type)
{
const blender::nodes::DataTypeConversions &conversions =
blender::nodes::get_implicit_type_conversions();
return conversions.try_convert(std::move(varray), to_type);
}
std::unique_ptr<blender::fn::GVArray> GeometryComponent::attribute_try_get_for_read(
const StringRef attribute_name,
const AttributeDomain domain,
const CustomDataType data_type) const
{
blender::bke::ReadAttributeLookup attribute = this->attribute_try_get_for_read(attribute_name);
if (!attribute) {
return {};
}
std::unique_ptr<blender::fn::GVArray> varray = std::move(attribute.varray);
if (domain != ATTR_DOMAIN_AUTO && attribute.domain != domain) {
varray = this->attribute_try_adapt_domain(std::move(varray), attribute.domain, domain);
if (!varray) {
return {};
}
}
const blender::fn::CPPType *cpp_type = blender::bke::custom_data_type_to_cpp_type(data_type);
BLI_assert(cpp_type != nullptr);
if (varray->type() != *cpp_type) {
varray = try_adapt_data_type(std::move(varray), *cpp_type);
if (!varray) {
return {};
}
}
return varray;
}
std::unique_ptr<blender::bke::GVArray> GeometryComponent::attribute_try_get_for_read(
const StringRef attribute_name, const AttributeDomain domain) const
{
if (!this->attribute_domain_supported(domain)) {
return {};
}
blender::bke::ReadAttributeLookup attribute = this->attribute_try_get_for_read(attribute_name);
if (!attribute) {
return {};
}
if (attribute.domain != domain) {
return this->attribute_try_adapt_domain(std::move(attribute.varray), attribute.domain, domain);
}
return std::move(attribute.varray);
}
blender::bke::ReadAttributeLookup GeometryComponent::attribute_try_get_for_read(
const blender::StringRef attribute_name, const CustomDataType data_type) const
{
blender::bke::ReadAttributeLookup attribute = this->attribute_try_get_for_read(attribute_name);
if (!attribute) {
return {};
}
const blender::fn::CPPType *type = blender::bke::custom_data_type_to_cpp_type(data_type);
BLI_assert(type != nullptr);
if (attribute.varray->type() == *type) {
return attribute;
}
const blender::nodes::DataTypeConversions &conversions =
blender::nodes::get_implicit_type_conversions();
return {conversions.try_convert(std::move(attribute.varray), *type), attribute.domain};
}
std::unique_ptr<blender::bke::GVArray> GeometryComponent::attribute_get_for_read(
const StringRef attribute_name,
const AttributeDomain domain,
const CustomDataType data_type,
const void *default_value) const
{
std::unique_ptr<blender::bke::GVArray> varray = this->attribute_try_get_for_read(
attribute_name, domain, data_type);
if (varray) {
return varray;
}
const blender::fn::CPPType *type = blender::bke::custom_data_type_to_cpp_type(data_type);
if (default_value == nullptr) {
default_value = type->default_value();
}
const int domain_size = this->attribute_domain_size(domain);
return std::make_unique<blender::fn::GVArray_For_SingleValue>(*type, domain_size, default_value);
}
class GVMutableAttribute_For_OutputAttribute
: public blender::fn::GVMutableArray_For_GMutableSpan {
public:
GeometryComponent *component;
std::string final_name;
GVMutableAttribute_For_OutputAttribute(GMutableSpan data,
GeometryComponent &component,
std::string final_name)
: blender::fn::GVMutableArray_For_GMutableSpan(data),
component(&component),
final_name(std::move(final_name))
{
}
~GVMutableAttribute_For_OutputAttribute() override
{
type_->destruct_n(data_, size_);
MEM_freeN(data_);
}
};
static void save_output_attribute(blender::bke::OutputAttribute &output_attribute)
{
using namespace blender;
using namespace blender::fn;
using namespace blender::bke;
GVMutableAttribute_For_OutputAttribute &varray =
dynamic_cast<GVMutableAttribute_For_OutputAttribute &>(output_attribute.varray());
GeometryComponent &component = *varray.component;
const StringRefNull name = varray.final_name;
const AttributeDomain domain = output_attribute.domain();
const CustomDataType data_type = output_attribute.custom_data_type();
const CPPType &cpp_type = output_attribute.cpp_type();
component.attribute_try_delete(name);
if (!component.attribute_try_create(
varray.final_name, domain, data_type, AttributeInitDefault())) {
CLOG_WARN(&LOG,
"Could not create the '%s' attribute with type '%s'.",
name.c_str(),
cpp_type.name().c_str());
return;
}
WriteAttributeLookup write_attribute = component.attribute_try_get_for_write(name);
BUFFER_FOR_CPP_TYPE_VALUE(varray.type(), buffer);
for (const int i : IndexRange(varray.size())) {
varray.get(i, buffer);
write_attribute.varray->set_by_relocate(i, buffer);
}
}
static blender::bke::OutputAttribute create_output_attribute(
GeometryComponent &component,
const blender::StringRef attribute_name,
const AttributeDomain domain,
const CustomDataType data_type,
const bool ignore_old_values,
const void *default_value)
{
using namespace blender;
using namespace blender::fn;
using namespace blender::bke;
if (attribute_name.is_empty()) {
return {};
}
const CPPType *cpp_type = custom_data_type_to_cpp_type(data_type);
BLI_assert(cpp_type != nullptr);
const nodes::DataTypeConversions &conversions = nodes::get_implicit_type_conversions();
if (component.attribute_is_builtin(attribute_name)) {
WriteAttributeLookup attribute = component.attribute_try_get_for_write(attribute_name);
if (!attribute) {
if (default_value) {
const int64_t domain_size = component.attribute_domain_size(domain);
const GVArray_For_SingleValueRef default_varray{*cpp_type, domain_size, default_value};
component.attribute_try_create_builtin(attribute_name,
AttributeInitVArray(&default_varray));
}
else {
component.attribute_try_create_builtin(attribute_name, AttributeInitDefault());
}
attribute = component.attribute_try_get_for_write(attribute_name);
if (!attribute) {
/* Builtin attribute does not exist and can't be created. */
return {};
}
}
if (attribute.domain != domain) {
/* Builtin attribute is on different domain. */
return {};
}
GVMutableArrayPtr varray = std::move(attribute.varray);
if (varray->type() == *cpp_type) {
/* Builtin attribute matches exactly. */
return OutputAttribute(std::move(varray), domain, {}, ignore_old_values);
}
/* Builtin attribute is on the same domain but has a different data type. */
varray = conversions.try_convert(std::move(varray), *cpp_type);
return OutputAttribute(std::move(varray), domain, {}, ignore_old_values);
}
const int domain_size = component.attribute_domain_size(domain);
WriteAttributeLookup attribute = component.attribute_try_get_for_write(attribute_name);
if (!attribute) {
if (default_value) {
const GVArray_For_SingleValueRef default_varray{*cpp_type, domain_size, default_value};
component.attribute_try_create(
attribute_name, domain, data_type, AttributeInitVArray(&default_varray));
}
else {
component.attribute_try_create(attribute_name, domain, data_type, AttributeInitDefault());
}
attribute = component.attribute_try_get_for_write(attribute_name);
if (!attribute) {
/* Can't create the attribute. */
return {};
}
}
if (attribute.domain == domain && attribute.varray->type() == *cpp_type) {
/* Existing generic attribute matches exactly. */
return OutputAttribute(std::move(attribute.varray), domain, {}, ignore_old_values);
}
/* Allocate a new array that lives next to the existing attribute. It will overwrite the existing
* attribute after processing is done. */
void *data = MEM_mallocN_aligned(
cpp_type->size() * domain_size, cpp_type->alignment(), __func__);
if (ignore_old_values) {
/* This does nothing for trivially constructible types, but is necessary for correctness. */
cpp_type->construct_default_n(data, domain);
}
else {
/* Fill the temporary array with values from the existing attribute. */
GVArrayPtr old_varray = component.attribute_get_for_read(
attribute_name, domain, data_type, default_value);
old_varray->materialize_to_uninitialized(IndexRange(domain_size), data);
}
GVMutableArrayPtr varray = std::make_unique<GVMutableAttribute_For_OutputAttribute>(
GMutableSpan{*cpp_type, data, domain_size}, component, attribute_name);
return OutputAttribute(std::move(varray), domain, save_output_attribute, true);
}
blender::bke::OutputAttribute GeometryComponent::attribute_try_get_for_output(
const StringRef attribute_name,
const AttributeDomain domain,
const CustomDataType data_type,
const void *default_value)
{
return create_output_attribute(*this, attribute_name, domain, data_type, false, default_value);
}
blender::bke::OutputAttribute GeometryComponent::attribute_try_get_for_output_only(
const blender::StringRef attribute_name,
const AttributeDomain domain,
const CustomDataType data_type)
{
return create_output_attribute(*this, attribute_name, domain, data_type, true, nullptr);
}