/* * 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 #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_node_tree_multi_function.hh" static CLG_LogRef LOG = {"bke.attribute_access"}; using blender::float3; using blender::Set; using blender::StringRef; using blender::StringRefNull; using blender::bke::ReadAttributePtr; using blender::bke::WriteAttributePtr; using blender::fn::GMutableSpan; /* Can't include BKE_object_deform.h right now, due to an enum forward declaration. */ extern "C" MDeformVert *BKE_object_defgroup_data_create(ID *id); namespace blender::bke { /* -------------------------------------------------------------------- */ /** \name Attribute Accessor implementations * \{ */ ReadAttribute::~ReadAttribute() { if (array_is_temporary_ && array_buffer_ != nullptr) { cpp_type_.destruct_n(array_buffer_, size_); MEM_freeN(array_buffer_); } } fn::GSpan ReadAttribute::get_span() const { if (size_ == 0) { return fn::GSpan(cpp_type_); } if (array_buffer_ == nullptr) { std::lock_guard lock{span_mutex_}; if (array_buffer_ == nullptr) { this->initialize_span(); } } return fn::GSpan(cpp_type_, array_buffer_, size_); } void ReadAttribute::initialize_span() const { const int element_size = cpp_type_.size(); array_buffer_ = MEM_mallocN_aligned(size_ * element_size, cpp_type_.alignment(), __func__); array_is_temporary_ = true; for (const int i : IndexRange(size_)) { this->get_internal(i, POINTER_OFFSET(array_buffer_, i * element_size)); } } WriteAttribute::~WriteAttribute() { if (array_should_be_applied_) { CLOG_ERROR(&LOG, "Forgot to call apply_span."); } if (array_is_temporary_ && array_buffer_ != nullptr) { cpp_type_.destruct_n(array_buffer_, size_); MEM_freeN(array_buffer_); } } /** * Get a mutable span that can be modified. When all modifications to the attribute are done, * #apply_span should be called. */ fn::GMutableSpan WriteAttribute::get_span() { if (size_ == 0) { return fn::GMutableSpan(cpp_type_); } if (array_buffer_ == nullptr) { this->initialize_span(false); } array_should_be_applied_ = true; return fn::GMutableSpan(cpp_type_, array_buffer_, size_); } fn::GMutableSpan WriteAttribute::get_span_for_write_only() { if (size_ == 0) { return fn::GMutableSpan(cpp_type_); } if (array_buffer_ == nullptr) { this->initialize_span(true); } array_should_be_applied_ = true; return fn::GMutableSpan(cpp_type_, array_buffer_, size_); } void WriteAttribute::initialize_span(const bool write_only) { const int element_size = cpp_type_.size(); array_buffer_ = MEM_mallocN_aligned(element_size * size_, cpp_type_.alignment(), __func__); array_is_temporary_ = true; if (write_only) { /* This does nothing for trivial types, but is necessary for general correctness. */ cpp_type_.construct_default_n(array_buffer_, size_); } else { for (const int i : IndexRange(size_)) { this->get(i, POINTER_OFFSET(array_buffer_, i * element_size)); } } } void WriteAttribute::apply_span() { this->apply_span_if_necessary(); array_should_be_applied_ = false; } void WriteAttribute::apply_span_if_necessary() { /* Only works when the span has been initialized beforehand. */ BLI_assert(array_buffer_ != nullptr); const int element_size = cpp_type_.size(); for (const int i : IndexRange(size_)) { this->set_internal(i, POINTER_OFFSET(array_buffer_, i * element_size)); } } class VertexWeightWriteAttribute final : public WriteAttribute { private: MDeformVert *dverts_; const int dvert_index_; public: VertexWeightWriteAttribute(MDeformVert *dverts, const int totvert, const int dvert_index) : WriteAttribute(ATTR_DOMAIN_POINT, CPPType::get(), totvert), dverts_(dverts), dvert_index_(dvert_index) { } void get_internal(const int64_t index, void *r_value) const override { get_internal(dverts_, dvert_index_, index, r_value); } void set_internal(const int64_t index, const void *value) override { MDeformWeight *weight = BKE_defvert_ensure_index(&dverts_[index], dvert_index_); weight->weight = *reinterpret_cast(value); } static void get_internal(const MDeformVert *dverts, const int dvert_index, const int64_t index, void *r_value) { if (dverts == nullptr) { *(float *)r_value = 0.0f; return; } const MDeformVert &dvert = dverts[index]; for (const MDeformWeight &weight : Span(dvert.dw, dvert.totweight)) { if (weight.def_nr == dvert_index) { *(float *)r_value = weight.weight; return; } } *(float *)r_value = 0.0f; } }; class VertexWeightReadAttribute final : public ReadAttribute { private: const MDeformVert *dverts_; const int dvert_index_; public: VertexWeightReadAttribute(const MDeformVert *dverts, const int totvert, const int dvert_index) : ReadAttribute(ATTR_DOMAIN_POINT, CPPType::get(), totvert), dverts_(dverts), dvert_index_(dvert_index) { } void get_internal(const int64_t index, void *r_value) const override { VertexWeightWriteAttribute::get_internal(dverts_, dvert_index_, index, r_value); } }; template class ArrayWriteAttribute final : public WriteAttribute { private: MutableSpan data_; public: ArrayWriteAttribute(AttributeDomain domain, MutableSpan data) : WriteAttribute(domain, CPPType::get(), data.size()), data_(data) { } void get_internal(const int64_t index, void *r_value) const override { new (r_value) T(data_[index]); } void set_internal(const int64_t index, const void *value) override { data_[index] = *reinterpret_cast(value); } void initialize_span(const bool UNUSED(write_only)) override { array_buffer_ = data_.data(); array_is_temporary_ = false; } void apply_span_if_necessary() override { /* Do nothing, because the span contains the attribute itself already. */ } }; /* This is used by the #OutputAttributePtr class. */ class TemporaryWriteAttribute final : public WriteAttribute { public: GMutableSpan data; GeometryComponent &component; std::string final_name; TemporaryWriteAttribute(AttributeDomain domain, GMutableSpan data, GeometryComponent &component, std::string final_name) : WriteAttribute(domain, data.type(), data.size()), data(data), component(component), final_name(std::move(final_name)) { } ~TemporaryWriteAttribute() override { if (data.data() != nullptr) { cpp_type_.destruct_n(data.data(), data.size()); MEM_freeN(data.data()); } } void get_internal(const int64_t index, void *r_value) const override { data.type().copy_to_uninitialized(data[index], r_value); } void set_internal(const int64_t index, const void *value) override { data.type().copy_to_initialized(value, data[index]); } void initialize_span(const bool UNUSED(write_only)) override { array_buffer_ = data.data(); array_is_temporary_ = false; } void apply_span_if_necessary() override { /* Do nothing, because the span contains the attribute itself already. */ } }; template class ArrayReadAttribute final : public ReadAttribute { private: Span data_; public: ArrayReadAttribute(AttributeDomain domain, Span data) : ReadAttribute(domain, CPPType::get(), data.size()), data_(data) { } void get_internal(const int64_t index, void *r_value) const override { new (r_value) T(data_[index]); } void initialize_span() const override { /* The data will not be modified, so this const_cast is fine. */ array_buffer_ = const_cast(data_.data()); array_is_temporary_ = false; } }; template class OwnedArrayReadAttribute final : public ReadAttribute { private: Array data_; public: OwnedArrayReadAttribute(AttributeDomain domain, Array data) : ReadAttribute(domain, CPPType::get(), data.size()), data_(std::move(data)) { } void get_internal(const int64_t index, void *r_value) const override { new (r_value) T(data_[index]); } void initialize_span() const override { /* The data will not be modified, so this const_cast is fine. */ array_buffer_ = const_cast(data_.data()); array_is_temporary_ = false; } }; template class DerivedArrayWriteAttribute final : public WriteAttribute { private: MutableSpan data_; public: DerivedArrayWriteAttribute(AttributeDomain domain, MutableSpan data) : WriteAttribute(domain, CPPType::get(), data.size()), data_(data) { } void get_internal(const int64_t index, void *r_value) const override { const StructT &struct_value = data_[index]; const ElemT value = GetFunc(struct_value); new (r_value) ElemT(value); } void set_internal(const int64_t index, const void *value) override { StructT &struct_value = data_[index]; const ElemT &typed_value = *reinterpret_cast(value); SetFunc(struct_value, typed_value); } }; template class DerivedArrayReadAttribute final : public ReadAttribute { private: Span data_; public: DerivedArrayReadAttribute(AttributeDomain domain, Span data) : ReadAttribute(domain, CPPType::get(), data.size()), data_(data) { } void get_internal(const int64_t index, void *r_value) const override { const StructT &struct_value = data_[index]; const ElemT value = GetFunc(struct_value); new (r_value) ElemT(value); } }; class ConstantReadAttribute final : public ReadAttribute { private: void *value_; public: ConstantReadAttribute(AttributeDomain domain, const int64_t size, const CPPType &type, const void *value) : ReadAttribute(domain, type, size) { value_ = MEM_mallocN_aligned(type.size(), type.alignment(), __func__); type.copy_to_uninitialized(value, value_); } ~ConstantReadAttribute() override { this->cpp_type_.destruct(value_); MEM_freeN(value_); } void get_internal(const int64_t UNUSED(index), void *r_value) const override { this->cpp_type_.copy_to_uninitialized(value_, r_value); } void initialize_span() const override { const int element_size = cpp_type_.size(); array_buffer_ = MEM_mallocN_aligned(size_ * element_size, cpp_type_.alignment(), __func__); array_is_temporary_ = true; cpp_type_.fill_uninitialized(value_, array_buffer_, size_); } }; class ConvertedReadAttribute final : public ReadAttribute { private: const CPPType &from_type_; const CPPType &to_type_; ReadAttributePtr base_attribute_; const nodes::DataTypeConversions &conversions_; static constexpr int MaxValueSize = 64; static constexpr int MaxValueAlignment = 64; public: ConvertedReadAttribute(ReadAttributePtr base_attribute, const CPPType &to_type) : ReadAttribute(base_attribute->domain(), to_type, base_attribute->size()), from_type_(base_attribute->cpp_type()), to_type_(to_type), base_attribute_(std::move(base_attribute)), conversions_(nodes::get_implicit_type_conversions()) { if (from_type_.size() > MaxValueSize || from_type_.alignment() > MaxValueAlignment) { throw std::runtime_error( "type is larger than expected, the buffer size has to be increased"); } } void get_internal(const int64_t index, void *r_value) const override { AlignedBuffer buffer; base_attribute_->get(index, buffer.ptr()); conversions_.convert(from_type_, to_type_, buffer.ptr(), r_value); } }; /** \} */ const blender::fn::CPPType *custom_data_type_to_cpp_type(const CustomDataType type) { switch (type) { case CD_PROP_FLOAT: return &CPPType::get(); case CD_PROP_FLOAT2: return &CPPType::get(); case CD_PROP_FLOAT3: return &CPPType::get(); case CD_PROP_INT32: return &CPPType::get(); case CD_PROP_COLOR: return &CPPType::get(); case CD_PROP_BOOL: return &CPPType::get(); default: return nullptr; } return nullptr; } CustomDataType cpp_type_to_custom_data_type(const blender::fn::CPPType &type) { if (type.is()) { return CD_PROP_FLOAT; } if (type.is()) { return CD_PROP_FLOAT2; } if (type.is()) { return CD_PROP_FLOAT3; } if (type.is()) { return CD_PROP_INT32; } if (type.is()) { return CD_PROP_COLOR; } if (type.is()) { return CD_PROP_BOOL; } return static_cast(-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(false); return 0; } } CustomDataType attribute_data_type_highest_complexity(Span 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) { #if 0 case ATTR_DOMAIN_CURVE: return 0; #endif case ATTR_DOMAIN_POLYGON: 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(false); 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 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; } /** * A #BuiltinAttributeProvider is responsible for exactly one attribute on a geometry component. * The attribute is identified by its name and has a fixed domain and type. Builtin attributes do * not follow the same loose rules as other attributes, because they are mapped to internal * "legacy" data structures. For example, some builtin attributes cannot be deleted. */ class BuiltinAttributeProvider { public: /* Some utility enums to avoid hard to read booleans in function calls. */ enum CreatableEnum { Creatable, NonCreatable, }; enum WritableEnum { Writable, Readonly, }; enum DeletableEnum { Deletable, NonDeletable, }; protected: const std::string name_; const AttributeDomain domain_; const CustomDataType data_type_; const CreatableEnum createable_; const WritableEnum writable_; const DeletableEnum deletable_; public: BuiltinAttributeProvider(std::string name, const AttributeDomain domain, const CustomDataType data_type, const CreatableEnum createable, const WritableEnum writable, const DeletableEnum deletable) : name_(std::move(name)), domain_(domain), data_type_(data_type), createable_(createable), writable_(writable), deletable_(deletable) { } virtual ReadAttributePtr try_get_for_read(const GeometryComponent &component) const = 0; virtual WriteAttributePtr try_get_for_write(GeometryComponent &component) const = 0; virtual bool try_delete(GeometryComponent &component) const = 0; virtual bool try_create(GeometryComponent &UNUSED(component)) const = 0; virtual bool exists(const GeometryComponent &component) const = 0; StringRefNull name() const { return name_; } AttributeDomain domain() const { return domain_; } CustomDataType data_type() const { return data_type_; } }; /** * A #DynamicAttributesProvider manages a set of named attributes on a geometry component. Each * attribute has a name, domain and type. */ class DynamicAttributesProvider { public: virtual ReadAttributePtr try_get_for_read(const GeometryComponent &component, const StringRef attribute_name) const = 0; virtual WriteAttributePtr try_get_for_write(GeometryComponent &component, const StringRef attribute_name) const = 0; virtual bool try_delete(GeometryComponent &component, const StringRef attribute_name) const = 0; virtual bool try_create(GeometryComponent &UNUSED(component), const StringRef UNUSED(attribute_name), const AttributeDomain UNUSED(domain), const CustomDataType UNUSED(data_type)) const { /* Some providers should not create new attributes. */ return false; }; virtual bool foreach_attribute(const GeometryComponent &component, const AttributeForeachCallback callback) const = 0; virtual void supported_domains(Vector &r_domains) const = 0; }; /** * Utility to group together multiple functions that are used to access custom data on geometry * components in a generic way. */ struct CustomDataAccessInfo { using CustomDataGetter = CustomData *(*)(GeometryComponent &component); using ConstCustomDataGetter = const CustomData *(*)(const GeometryComponent &component); using UpdateCustomDataPointers = void (*)(GeometryComponent &component); CustomDataGetter get_custom_data; ConstCustomDataGetter get_const_custom_data; UpdateCustomDataPointers update_custom_data_pointers; }; /** * This provider is used to provide access to builtin attributes. It supports making internal types * available as different types. For example, the vertex position attribute is stored as part of * the #MVert struct, but is exposed as float3 attribute. */ class BuiltinCustomDataLayerProvider final : public BuiltinAttributeProvider { using AsReadAttribute = ReadAttributePtr (*)(const void *data, const int domain_size); using AsWriteAttribute = WriteAttributePtr (*)(void *data, const int domain_size); using UpdateOnWrite = void (*)(GeometryComponent &component); const CustomDataType stored_type_; const CustomDataAccessInfo custom_data_access_; const AsReadAttribute as_read_attribute_; const AsWriteAttribute as_write_attribute_; const UpdateOnWrite update_on_write_; public: BuiltinCustomDataLayerProvider(std::string attribute_name, const AttributeDomain domain, const CustomDataType attribute_type, const CustomDataType stored_type, const CreatableEnum creatable, const WritableEnum writable, const DeletableEnum deletable, const CustomDataAccessInfo custom_data_access, const AsReadAttribute as_read_attribute, const AsWriteAttribute as_write_attribute, const UpdateOnWrite update_on_write) : BuiltinAttributeProvider( std::move(attribute_name), domain, attribute_type, creatable, writable, deletable), stored_type_(stored_type), custom_data_access_(custom_data_access), as_read_attribute_(as_read_attribute), as_write_attribute_(as_write_attribute), update_on_write_(update_on_write) { } ReadAttributePtr try_get_for_read(const GeometryComponent &component) const final { 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); } WriteAttributePtr try_get_for_write(GeometryComponent &component) const final { 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 try_delete(GeometryComponent &component) const final { 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; } bool try_create(GeometryComponent &component) const final { 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 void *data = CustomData_add_layer( custom_data, stored_type_, CD_DEFAULT, nullptr, domain_size); const bool success = data != nullptr; if (success) { custom_data_access_.update_custom_data_pointers(component); } return success; } bool exists(const GeometryComponent &component) const final { 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; } }; /** * This is the attribute provider for most user generated attributes. */ class CustomDataAttributeProvider final : public DynamicAttributesProvider { private: static constexpr uint64_t supported_types_mask = CD_MASK_PROP_FLOAT | CD_MASK_PROP_FLOAT2 | CD_MASK_PROP_FLOAT3 | CD_MASK_PROP_INT32 | CD_MASK_PROP_COLOR | CD_MASK_PROP_BOOL; const AttributeDomain domain_; const CustomDataAccessInfo custom_data_access_; public: CustomDataAttributeProvider(const AttributeDomain domain, const CustomDataAccessInfo custom_data_access) : domain_(domain), custom_data_access_(custom_data_access) { } ReadAttributePtr try_get_for_read(const GeometryComponent &component, const StringRef attribute_name) const final { 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(layer, domain_size); case CD_PROP_FLOAT2: return this->layer_to_read_attribute(layer, domain_size); case CD_PROP_FLOAT3: return this->layer_to_read_attribute(layer, domain_size); case CD_PROP_INT32: return this->layer_to_read_attribute(layer, domain_size); case CD_PROP_COLOR: return this->layer_to_read_attribute(layer, domain_size); case CD_PROP_BOOL: return this->layer_to_read_attribute(layer, domain_size); default: break; } } return {}; } WriteAttributePtr try_get_for_write(GeometryComponent &component, const StringRef attribute_name) const final { 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(layer, domain_size); case CD_PROP_FLOAT2: return this->layer_to_write_attribute(layer, domain_size); case CD_PROP_FLOAT3: return this->layer_to_write_attribute(layer, domain_size); case CD_PROP_INT32: return this->layer_to_write_attribute(layer, domain_size); case CD_PROP_COLOR: return this->layer_to_write_attribute(layer, domain_size); case CD_PROP_BOOL: return this->layer_to_write_attribute(layer, domain_size); default: break; } } return {}; } bool try_delete(GeometryComponent &component, const StringRef attribute_name) const final { 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; } bool try_create(GeometryComponent &component, const StringRef attribute_name, const AttributeDomain domain, const CustomDataType data_type) const final { 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_); char attribute_name_c[MAX_NAME]; attribute_name.copy(attribute_name_c); CustomData_add_layer_named( custom_data, data_type, CD_DEFAULT, nullptr, domain_size, attribute_name_c); return true; } bool foreach_attribute(const GeometryComponent &component, const AttributeForeachCallback callback) const final { 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; } void supported_domains(Vector &r_domains) const final { r_domains.append_non_duplicates(domain_); } private: template ReadAttributePtr layer_to_read_attribute(const CustomDataLayer &layer, const int domain_size) const { return std::make_unique>( domain_, Span(static_cast(layer.data), domain_size)); } template WriteAttributePtr layer_to_write_attribute(CustomDataLayer &layer, const int domain_size) const { return std::make_unique>( domain_, MutableSpan(static_cast(layer.data), domain_size)); } bool type_is_supported(CustomDataType data_type) const { return ((1ULL << data_type) & supported_types_mask) != 0; } }; static Mesh *get_mesh_from_component_for_write(GeometryComponent &component) { BLI_assert(component.type() == GeometryComponentType::Mesh); MeshComponent &mesh_component = static_cast(component); return mesh_component.get_for_write(); } static const Mesh *get_mesh_from_component_for_read(const GeometryComponent &component) { BLI_assert(component.type() == GeometryComponentType::Mesh); const MeshComponent &mesh_component = static_cast(component); return mesh_component.get_for_read(); } /** * This attribute provider is used for uv maps and vertex colors. */ class NamedLegacyCustomDataProvider final : public DynamicAttributesProvider { private: using AsReadAttribute = ReadAttributePtr (*)(const void *data, const int domain_size); using AsWriteAttribute = WriteAttributePtr (*)(void *data, const int domain_size); const AttributeDomain domain_; const CustomDataType attribute_type_; const CustomDataType stored_type_; const CustomDataAccessInfo custom_data_access_; const AsReadAttribute as_read_attribute_; const AsWriteAttribute as_write_attribute_; public: NamedLegacyCustomDataProvider(const AttributeDomain domain, const CustomDataType attribute_type, const CustomDataType stored_type, const CustomDataAccessInfo custom_data_access, const AsReadAttribute as_read_attribute, const AsWriteAttribute as_write_attribute) : domain_(domain), attribute_type_(attribute_type), stored_type_(stored_type), custom_data_access_(custom_data_access), as_read_attribute_(as_read_attribute), as_write_attribute_(as_write_attribute) { } ReadAttributePtr try_get_for_read(const GeometryComponent &component, const StringRef attribute_name) const final { 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); } } } return {}; } WriteAttributePtr try_get_for_write(GeometryComponent &component, const StringRef attribute_name) const final { 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); } } } return {}; } bool try_delete(GeometryComponent &component, const StringRef attribute_name) const final { 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 foreach_attribute(const GeometryComponent &component, const AttributeForeachCallback callback) const final { 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 supported_domains(Vector &r_domains) const final { r_domains.append_non_duplicates(domain_); } }; /** * This provider makes vertex groups available as float attributes. */ class VertexGroupsAttributeProvider final : public DynamicAttributesProvider { public: ReadAttributePtr try_get_for_read(const GeometryComponent &component, const StringRef attribute_name) const final { BLI_assert(component.type() == GeometryComponentType::Mesh); const MeshComponent &mesh_component = static_cast(component); const Mesh *mesh = mesh_component.get_for_read(); const int vertex_group_index = mesh_component.vertex_group_names().lookup_default_as( attribute_name, -1); if (vertex_group_index < 0) { return {}; } if (mesh == nullptr || mesh->dvert == nullptr) { static const float default_value = 0.0f; return std::make_unique( ATTR_DOMAIN_POINT, mesh->totvert, CPPType::get(), &default_value); } return std::make_unique( mesh->dvert, mesh->totvert, vertex_group_index); } WriteAttributePtr try_get_for_write(GeometryComponent &component, const StringRef attribute_name) const final { BLI_assert(component.type() == GeometryComponentType::Mesh); MeshComponent &mesh_component = static_cast(component); Mesh *mesh = mesh_component.get_for_write(); if (mesh == nullptr) { return {}; } const int vertex_group_index = mesh_component.vertex_group_names().lookup_default_as( attribute_name, -1); if (vertex_group_index < 0) { return {}; } if (mesh->dvert == nullptr) { BKE_object_defgroup_data_create(&mesh->id); } else { /* Copy the data layer if it is shared with some other mesh. */ mesh->dvert = (MDeformVert *)CustomData_duplicate_referenced_layer( &mesh->vdata, CD_MDEFORMVERT, mesh->totvert); } return std::make_unique( mesh->dvert, mesh->totvert, vertex_group_index); } bool try_delete(GeometryComponent &component, const StringRef attribute_name) const final { BLI_assert(component.type() == GeometryComponentType::Mesh); MeshComponent &mesh_component = static_cast(component); const int vertex_group_index = mesh_component.vertex_group_names().pop_default_as( attribute_name, -1); if (vertex_group_index < 0) { return false; } Mesh *mesh = mesh_component.get_for_write(); if (mesh == nullptr) { return true; } if (mesh->dvert == nullptr) { return true; } for (MDeformVert &dvert : MutableSpan(mesh->dvert, mesh->totvert)) { MDeformWeight *weight = BKE_defvert_find_index(&dvert, vertex_group_index); BKE_defvert_remove_group(&dvert, weight); } return true; } bool foreach_attribute(const GeometryComponent &component, const AttributeForeachCallback callback) const final { BLI_assert(component.type() == GeometryComponentType::Mesh); const MeshComponent &mesh_component = static_cast(component); for (const auto &item : mesh_component.vertex_group_names().items()) { const StringRefNull name = item.key; const int vertex_group_index = item.value; if (vertex_group_index >= 0) { AttributeMetaData meta_data{ATTR_DOMAIN_POINT, CD_PROP_FLOAT}; if (!callback(name, meta_data)) { return false; } } } return true; } void supported_domains(Vector &r_domains) const final { r_domains.append_non_duplicates(ATTR_DOMAIN_POINT); } }; /** * This is a container for multiple attribute providers that are used by one geometry component * type (e.g. there is a set of attribute providers for mesh components). */ class ComponentAttributeProviders { private: /** * Builtin attribute providers are identified by their name. Attribute names that are in this * map will only be accessed using builtin attribute providers. Therefore, these providers have * higher priority when an attribute name is looked up. Usually, that means that builtin * providers are checked before dynamic ones. */ Map builtin_attribute_providers_; /** * An ordered list of dynamic attribute providers. The order is important because that is order * in which they are checked when an attribute is looked up. */ Vector dynamic_attribute_providers_; /** * All the domains that are supported by at least one of the providers above. */ Vector supported_domains_; public: ComponentAttributeProviders(Span builtin_attribute_providers, Span dynamic_attribute_providers) : dynamic_attribute_providers_(dynamic_attribute_providers) { Set domains; for (const BuiltinAttributeProvider *provider : builtin_attribute_providers) { /* Use #add_new to make sure that no two builtin attributes have the same name. */ builtin_attribute_providers_.add_new(provider->name(), provider); supported_domains_.append_non_duplicates(provider->domain()); } for (const DynamicAttributesProvider *provider : dynamic_attribute_providers) { provider->supported_domains(supported_domains_); } } const Map &builtin_attribute_providers() const { return builtin_attribute_providers_; } Span dynamic_attribute_providers() const { return dynamic_attribute_providers_; } Span supported_domains() const { return supported_domains_; } }; static float3 get_vertex_position(const MVert &vert) { return float3(vert.co); } static void set_vertex_position(MVert &vert, const float3 &position) { copy_v3_v3(vert.co, position); } static ReadAttributePtr make_vertex_position_read_attribute(const void *data, const int domain_size) { return std::make_unique>( ATTR_DOMAIN_POINT, Span((const MVert *)data, domain_size)); } static WriteAttributePtr make_vertex_position_write_attribute(void *data, const int domain_size) { return std::make_unique< DerivedArrayWriteAttribute>( ATTR_DOMAIN_POINT, MutableSpan((MVert *)data, domain_size)); } static void tag_normals_dirty_when_writing_position(GeometryComponent &component) { Mesh *mesh = get_mesh_from_component_for_write(component); if (mesh != nullptr) { mesh->runtime.cd_dirty_vert |= CD_MASK_NORMAL; } } static int get_material_index(const MPoly &mpoly) { return static_cast(mpoly.mat_nr); } static void set_material_index(MPoly &mpoly, const int &index) { mpoly.mat_nr = static_cast(std::clamp(index, 0, SHRT_MAX)); } static ReadAttributePtr make_material_index_read_attribute(const void *data, const int domain_size) { return std::make_unique>( ATTR_DOMAIN_POLYGON, Span((const MPoly *)data, domain_size)); } static WriteAttributePtr make_material_index_write_attribute(void *data, const int domain_size) { return std::make_unique< DerivedArrayWriteAttribute>( ATTR_DOMAIN_POLYGON, MutableSpan((MPoly *)data, domain_size)); } static float2 get_loop_uv(const MLoopUV &uv) { return float2(uv.uv); } static void set_loop_uv(MLoopUV &uv, const float2 &co) { copy_v2_v2(uv.uv, co); } static ReadAttributePtr make_uvs_read_attribute(const void *data, const int domain_size) { return std::make_unique>( ATTR_DOMAIN_CORNER, Span((const MLoopUV *)data, domain_size)); } static WriteAttributePtr make_uvs_write_attribute(void *data, const int domain_size) { return std::make_unique>( ATTR_DOMAIN_CORNER, MutableSpan((MLoopUV *)data, domain_size)); } static Color4f get_loop_color(const MLoopCol &col) { Color4f value; rgba_uchar_to_float(value, &col.r); return value; } static void set_loop_color(MLoopCol &col, const Color4f &value) { rgba_float_to_uchar(&col.r, value); } static ReadAttributePtr make_vertex_color_read_attribute(const void *data, const int domain_size) { return std::make_unique>( ATTR_DOMAIN_CORNER, Span((const MLoopCol *)data, domain_size)); } static WriteAttributePtr make_vertex_color_write_attribute(void *data, const int domain_size) { return std::make_unique< DerivedArrayWriteAttribute>( ATTR_DOMAIN_CORNER, MutableSpan((MLoopCol *)data, domain_size)); } template static ReadAttributePtr make_array_read_attribute(const void *data, const int domain_size) { return std::make_unique>(Domain, Span((const T *)data, domain_size)); } template static WriteAttributePtr make_array_write_attribute(void *data, const int domain_size) { return std::make_unique>(Domain, MutableSpan((T *)data, domain_size)); } /** * In this function all the attribute providers for a mesh component are created. Most data in this * function is statically allocated, because it does not change over time. */ static ComponentAttributeProviders create_attribute_providers_for_mesh() { static auto update_custom_data_pointers = [](GeometryComponent &component) { Mesh *mesh = get_mesh_from_component_for_write(component); if (mesh != nullptr) { BKE_mesh_update_customdata_pointers(mesh, false); } }; #define MAKE_MUTABLE_CUSTOM_DATA_GETTER(NAME) \ [](GeometryComponent &component) -> CustomData * { \ Mesh *mesh = get_mesh_from_component_for_write(component); \ return mesh ? &mesh->NAME : nullptr; \ } #define MAKE_CONST_CUSTOM_DATA_GETTER(NAME) \ [](const GeometryComponent &component) -> const CustomData * { \ const Mesh *mesh = get_mesh_from_component_for_read(component); \ return mesh ? &mesh->NAME : nullptr; \ } static CustomDataAccessInfo corner_access = {MAKE_MUTABLE_CUSTOM_DATA_GETTER(ldata), MAKE_CONST_CUSTOM_DATA_GETTER(ldata), update_custom_data_pointers}; static CustomDataAccessInfo point_access = {MAKE_MUTABLE_CUSTOM_DATA_GETTER(vdata), MAKE_CONST_CUSTOM_DATA_GETTER(vdata), update_custom_data_pointers}; static CustomDataAccessInfo edge_access = {MAKE_MUTABLE_CUSTOM_DATA_GETTER(edata), MAKE_CONST_CUSTOM_DATA_GETTER(edata), update_custom_data_pointers}; static CustomDataAccessInfo polygon_access = {MAKE_MUTABLE_CUSTOM_DATA_GETTER(pdata), MAKE_CONST_CUSTOM_DATA_GETTER(pdata), update_custom_data_pointers}; #undef MAKE_CONST_CUSTOM_DATA_GETTER #undef MAKE_MUTABLE_CUSTOM_DATA_GETTER static BuiltinCustomDataLayerProvider position("position", ATTR_DOMAIN_POINT, CD_PROP_FLOAT3, CD_MVERT, BuiltinAttributeProvider::NonCreatable, BuiltinAttributeProvider::Writable, BuiltinAttributeProvider::NonDeletable, point_access, make_vertex_position_read_attribute, make_vertex_position_write_attribute, tag_normals_dirty_when_writing_position); static BuiltinCustomDataLayerProvider material_index("material_index", ATTR_DOMAIN_POLYGON, CD_PROP_INT32, CD_MPOLY, BuiltinAttributeProvider::NonCreatable, BuiltinAttributeProvider::Writable, BuiltinAttributeProvider::NonDeletable, polygon_access, make_material_index_read_attribute, make_material_index_write_attribute, nullptr); static NamedLegacyCustomDataProvider uvs(ATTR_DOMAIN_CORNER, CD_PROP_FLOAT2, CD_MLOOPUV, corner_access, make_uvs_read_attribute, make_uvs_write_attribute); static NamedLegacyCustomDataProvider vertex_colors(ATTR_DOMAIN_CORNER, CD_PROP_COLOR, CD_MLOOPCOL, corner_access, make_vertex_color_read_attribute, make_vertex_color_write_attribute); static VertexGroupsAttributeProvider vertex_groups; static CustomDataAttributeProvider corner_custom_data(ATTR_DOMAIN_CORNER, corner_access); static CustomDataAttributeProvider point_custom_data(ATTR_DOMAIN_POINT, point_access); static CustomDataAttributeProvider edge_custom_data(ATTR_DOMAIN_EDGE, edge_access); static CustomDataAttributeProvider polygon_custom_data(ATTR_DOMAIN_POLYGON, polygon_access); return ComponentAttributeProviders({&position, &material_index}, {&uvs, &vertex_colors, &corner_custom_data, &vertex_groups, &point_custom_data, &edge_custom_data, &polygon_custom_data}); } /** * In this function all the attribute providers for a point cloud component are created. Most data * in this function is statically allocated, because it does not change over time. */ static ComponentAttributeProviders create_attribute_providers_for_point_cloud() { static auto update_custom_data_pointers = [](GeometryComponent &component) { PointCloudComponent &pointcloud_component = static_cast(component); PointCloud *pointcloud = pointcloud_component.get_for_write(); if (pointcloud != nullptr) { BKE_pointcloud_update_customdata_pointers(pointcloud); } }; static CustomDataAccessInfo point_access = { [](GeometryComponent &component) -> CustomData * { PointCloudComponent &pointcloud_component = static_cast(component); PointCloud *pointcloud = pointcloud_component.get_for_write(); return pointcloud ? &pointcloud->pdata : nullptr; }, [](const GeometryComponent &component) -> const CustomData * { const PointCloudComponent &pointcloud_component = static_cast( component); const PointCloud *pointcloud = pointcloud_component.get_for_read(); return pointcloud ? &pointcloud->pdata : nullptr; }, update_custom_data_pointers}; static BuiltinCustomDataLayerProvider position( "position", ATTR_DOMAIN_POINT, CD_PROP_FLOAT3, CD_PROP_FLOAT3, BuiltinAttributeProvider::NonCreatable, BuiltinAttributeProvider::Writable, BuiltinAttributeProvider::NonDeletable, point_access, make_array_read_attribute, make_array_write_attribute, nullptr); static BuiltinCustomDataLayerProvider radius( "radius", ATTR_DOMAIN_POINT, CD_PROP_FLOAT, CD_PROP_FLOAT, BuiltinAttributeProvider::Creatable, BuiltinAttributeProvider::Writable, BuiltinAttributeProvider::Deletable, point_access, make_array_read_attribute, make_array_write_attribute, nullptr); static CustomDataAttributeProvider point_custom_data(ATTR_DOMAIN_POINT, point_access); return ComponentAttributeProviders({&position, &radius}, {&point_custom_data}); } } // 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 { BLI_assert(false); return 0; } ReadAttributePtr 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); } for (const DynamicAttributesProvider *dynamic_provider : providers->dynamic_attribute_providers()) { ReadAttributePtr attribute = dynamic_provider->try_get_for_read(*this, attribute_name); if (attribute) { return attribute; } } return {}; } ReadAttributePtr GeometryComponent::attribute_try_adapt_domain( ReadAttributePtr attribute, const AttributeDomain new_domain) const { if (attribute && attribute->domain() == new_domain) { return attribute; } return {}; } WriteAttributePtr 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); } for (const DynamicAttributesProvider *dynamic_provider : providers->dynamic_attribute_providers()) { WriteAttributePtr 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) { 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); } for (const DynamicAttributesProvider *dynamic_provider : providers->dynamic_attribute_providers()) { if (dynamic_provider->try_create(*this, attribute_name, domain, data_type)) { return true; } } return false; } Set GeometryComponent::attribute_names() const { Set attributes; this->attribute_foreach([&](StringRefNull name, const AttributeMetaData &UNUSED(meta_data)) { attributes.add(name); return true; }); return attributes; } void GeometryComponent::attribute_foreach(const AttributeForeachCallback callback) const { using namespace blender::bke; const ComponentAttributeProviders *providers = this->get_attribute_providers(); if (providers == nullptr) { return; } /* Keep track handled attribute names to make sure that we do not return the same name twice. */ Set 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; } 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; } } } bool GeometryComponent::attribute_exists(const blender::StringRef attribute_name) const { ReadAttributePtr attribute = this->attribute_try_get_for_read(attribute_name); if (attribute) { return true; } return false; } static ReadAttributePtr try_adapt_data_type(ReadAttributePtr attribute, const blender::fn::CPPType &to_type) { const blender::fn::CPPType &from_type = attribute->cpp_type(); if (from_type == to_type) { return attribute; } const blender::nodes::DataTypeConversions &conversions = blender::nodes::get_implicit_type_conversions(); if (!conversions.is_convertible(from_type, to_type)) { return {}; } return std::make_unique(std::move(attribute), to_type); } ReadAttributePtr GeometryComponent::attribute_try_get_for_read( const StringRef attribute_name, const AttributeDomain domain, const CustomDataType data_type) const { ReadAttributePtr attribute = this->attribute_try_get_for_read(attribute_name); if (!attribute) { return {}; } if (attribute->domain() != domain) { attribute = this->attribute_try_adapt_domain(std::move(attribute), domain); if (!attribute) { return {}; } } const blender::fn::CPPType *cpp_type = blender::bke::custom_data_type_to_cpp_type(data_type); BLI_assert(cpp_type != nullptr); if (attribute->cpp_type() != *cpp_type) { attribute = try_adapt_data_type(std::move(attribute), *cpp_type); if (!attribute) { return {}; } } return attribute; } ReadAttributePtr GeometryComponent::attribute_try_get_for_read(const StringRef attribute_name, const AttributeDomain domain) const { if (!this->attribute_domain_supported(domain)) { return {}; } ReadAttributePtr attribute = this->attribute_try_get_for_read(attribute_name); if (!attribute) { return {}; } if (attribute->domain() != domain) { attribute = this->attribute_try_adapt_domain(std::move(attribute), domain); if (!attribute) { return {}; } } return attribute; } ReadAttributePtr GeometryComponent::attribute_get_for_read(const StringRef attribute_name, const AttributeDomain domain, const CustomDataType data_type, const void *default_value) const { ReadAttributePtr attribute = this->attribute_try_get_for_read(attribute_name, domain, data_type); if (attribute) { return attribute; } return this->attribute_get_constant_for_read(domain, data_type, default_value); } blender::bke::ReadAttributePtr GeometryComponent::attribute_get_constant_for_read( const AttributeDomain domain, const CustomDataType data_type, const void *value) const { BLI_assert(this->attribute_domain_supported(domain)); const blender::fn::CPPType *cpp_type = blender::bke::custom_data_type_to_cpp_type(data_type); BLI_assert(cpp_type != nullptr); if (value == nullptr) { value = cpp_type->default_value(); } const int domain_size = this->attribute_domain_size(domain); return std::make_unique( domain, domain_size, *cpp_type, value); } blender::bke::ReadAttributePtr GeometryComponent::attribute_get_constant_for_read_converted( const AttributeDomain domain, const CustomDataType in_data_type, const CustomDataType out_data_type, const void *value) const { BLI_assert(this->attribute_domain_supported(domain)); if (value == nullptr || in_data_type == out_data_type) { return this->attribute_get_constant_for_read(domain, out_data_type, value); } const blender::fn::CPPType *in_cpp_type = blender::bke::custom_data_type_to_cpp_type( in_data_type); const blender::fn::CPPType *out_cpp_type = blender::bke::custom_data_type_to_cpp_type( out_data_type); BLI_assert(in_cpp_type != nullptr); BLI_assert(out_cpp_type != nullptr); const blender::nodes::DataTypeConversions &conversions = blender::nodes::get_implicit_type_conversions(); BLI_assert(conversions.is_convertible(*in_cpp_type, *out_cpp_type)); void *out_value = alloca(out_cpp_type->size()); conversions.convert(*in_cpp_type, *out_cpp_type, value, out_value); const int domain_size = this->attribute_domain_size(domain); blender::bke::ReadAttributePtr attribute = std::make_unique( domain, domain_size, *out_cpp_type, out_value); out_cpp_type->destruct(out_value); return attribute; } OutputAttributePtr GeometryComponent::attribute_try_get_for_output(const StringRef attribute_name, const AttributeDomain domain, const CustomDataType data_type, const void *default_value) { const blender::fn::CPPType *cpp_type = blender::bke::custom_data_type_to_cpp_type(data_type); BLI_assert(cpp_type != nullptr); WriteAttributePtr attribute = this->attribute_try_get_for_write(attribute_name); /* If the attribute doesn't exist, make a new one with the correct type. */ if (!attribute) { this->attribute_try_create(attribute_name, domain, data_type); attribute = this->attribute_try_get_for_write(attribute_name); if (default_value != nullptr) { void *data = attribute->get_span_for_write_only().data(); cpp_type->fill_initialized(default_value, data, attribute->size()); attribute->apply_span(); } return OutputAttributePtr(std::move(attribute)); } /* If an existing attribute has a matching domain and type, just use that. */ if (attribute->domain() == domain && attribute->cpp_type() == *cpp_type) { return OutputAttributePtr(std::move(attribute)); } /* Otherwise create a temporary buffer to use before saving the new attribute. */ return OutputAttributePtr(*this, domain, attribute_name, data_type); } /* Construct from an attribute that already exists in the geometry component. */ OutputAttributePtr::OutputAttributePtr(WriteAttributePtr attribute) : attribute_(std::move(attribute)) { } /* Construct a temporary attribute that has to replace an existing one later on. */ OutputAttributePtr::OutputAttributePtr(GeometryComponent &component, AttributeDomain domain, std::string final_name, CustomDataType data_type) { const blender::fn::CPPType *cpp_type = blender::bke::custom_data_type_to_cpp_type(data_type); BLI_assert(cpp_type != nullptr); const int domain_size = component.attribute_domain_size(domain); void *buffer = MEM_malloc_arrayN(domain_size, cpp_type->size(), __func__); GMutableSpan new_span{*cpp_type, buffer, domain_size}; /* Copy converted values from conflicting attribute, in case the value is read. * TODO: An optimization could be to not do this, when the caller says that the attribute will * only be written. */ ReadAttributePtr src_attribute = component.attribute_get_for_read( final_name, domain, data_type, nullptr); for (const int i : blender::IndexRange(domain_size)) { src_attribute->get(i, new_span[i]); } attribute_ = std::make_unique( domain, new_span, component, std::move(final_name)); } /* Store the computed attribute. If it was stored from the beginning already, nothing is done. This * might delete another attribute with the same name. */ void OutputAttributePtr::save() { if (!attribute_) { CLOG_WARN(&LOG, "Trying to save an attribute that does not exist anymore."); return; } blender::bke::TemporaryWriteAttribute *attribute = dynamic_cast(attribute_.get()); if (attribute == nullptr) { /* The attribute is saved already. */ attribute_.reset(); return; } StringRefNull name = attribute->final_name; const blender::fn::CPPType &cpp_type = attribute->cpp_type(); /* Delete an existing attribute with the same name if necessary. */ attribute->component.attribute_try_delete(name); if (!attribute->component.attribute_try_create( name, attribute_->domain(), attribute_->custom_data_type())) { /* Cannot create the target attribute for some reason. */ CLOG_WARN(&LOG, "Creating the '%s' attribute with type '%s' failed.", name.c_str(), cpp_type.name().c_str()); attribute_.reset(); return; } WriteAttributePtr new_attribute = attribute->component.attribute_try_get_for_write(name); GMutableSpan temp_span = attribute->data; GMutableSpan new_span = new_attribute->get_span_for_write_only(); BLI_assert(temp_span.size() == new_span.size()); /* Currently we copy over the attribute. In the future we want to reuse the buffer. */ cpp_type.move_to_initialized_n(temp_span.data(), new_span.data(), new_span.size()); new_attribute->apply_span(); attribute_.reset(); } OutputAttributePtr::~OutputAttributePtr() { if (attribute_) { CLOG_ERROR(&LOG, "Forgot to call #save or #apply_span_and_save."); } } /* Utility function to call #apply_span and #save in the right order. */ void OutputAttributePtr::apply_span_and_save() { BLI_assert(attribute_); attribute_->apply_span(); this->save(); } /** \} */ /* -------------------------------------------------------------------- */ /** \name Point Cloud Component * \{ */ const blender::bke::ComponentAttributeProviders *PointCloudComponent::get_attribute_providers() const { static blender::bke::ComponentAttributeProviders providers = blender::bke::create_attribute_providers_for_point_cloud(); return &providers; } int PointCloudComponent::attribute_domain_size(const AttributeDomain domain) const { BLI_assert(domain == ATTR_DOMAIN_POINT); UNUSED_VARS_NDEBUG(domain); if (pointcloud_ == nullptr) { return 0; } return pointcloud_->totpoint; } /** \} */ /* -------------------------------------------------------------------- */ /** \name Mesh Component * \{ */ const blender::bke::ComponentAttributeProviders *MeshComponent::get_attribute_providers() const { static blender::bke::ComponentAttributeProviders providers = blender::bke::create_attribute_providers_for_mesh(); return &providers; } int MeshComponent::attribute_domain_size(const AttributeDomain domain) const { BLI_assert(this->attribute_domain_supported(domain)); if (mesh_ == nullptr) { return 0; } switch (domain) { case ATTR_DOMAIN_CORNER: return mesh_->totloop; case ATTR_DOMAIN_POINT: return mesh_->totvert; case ATTR_DOMAIN_EDGE: return mesh_->totedge; case ATTR_DOMAIN_POLYGON: return mesh_->totpoly; default: BLI_assert(false); break; } return 0; } namespace blender::bke { template static void adapt_mesh_domain_corner_to_point_impl(const Mesh &mesh, const TypedReadAttribute &attribute, MutableSpan r_values) { BLI_assert(r_values.size() == mesh.totvert); attribute_math::DefaultMixer mixer(r_values); for (const int loop_index : IndexRange(mesh.totloop)) { const T value = attribute[loop_index]; const MLoop &loop = mesh.mloop[loop_index]; const int point_index = loop.v; mixer.mix_in(point_index, value); } mixer.finalize(); } static ReadAttributePtr adapt_mesh_domain_corner_to_point(const Mesh &mesh, ReadAttributePtr attribute) { ReadAttributePtr new_attribute; const CustomDataType data_type = attribute->custom_data_type(); attribute_math::convert_to_static_type(data_type, [&](auto dummy) { using T = decltype(dummy); if constexpr (!std::is_void_v>) { /* We compute all interpolated values at once, because for this interpolation, one has to * iterate over all loops anyway. */ Array values(mesh.totvert); adapt_mesh_domain_corner_to_point_impl(mesh, *attribute, values); new_attribute = std::make_unique>(ATTR_DOMAIN_POINT, std::move(values)); } }); return new_attribute; } template static void adapt_mesh_domain_point_to_corner_impl(const Mesh &mesh, const TypedReadAttribute &attribute, MutableSpan r_values) { BLI_assert(r_values.size() == mesh.totloop); for (const int loop_index : IndexRange(mesh.totloop)) { const int vertex_index = mesh.mloop[loop_index].v; r_values[loop_index] = attribute[vertex_index]; } } static ReadAttributePtr adapt_mesh_domain_point_to_corner(const Mesh &mesh, ReadAttributePtr attribute) { ReadAttributePtr new_attribute; const CustomDataType data_type = attribute->custom_data_type(); attribute_math::convert_to_static_type(data_type, [&](auto dummy) { using T = decltype(dummy); /* It is not strictly necessary to compute the value for all corners here. Instead one could * lazily lookup the mesh topology when a specific index accessed. This can be more efficient * when an algorithm only accesses very few of the corner values. However, for the algorithms * we currently have, precomputing the array is fine. Also, it is easier to implement. */ Array values(mesh.totloop); adapt_mesh_domain_point_to_corner_impl(mesh, *attribute, values); new_attribute = std::make_unique>(ATTR_DOMAIN_CORNER, std::move(values)); }); return new_attribute; } } // namespace blender::bke ReadAttributePtr MeshComponent::attribute_try_adapt_domain(ReadAttributePtr attribute, const AttributeDomain new_domain) const { if (!attribute) { return {}; } if (attribute->size() == 0) { return {}; } const AttributeDomain old_domain = attribute->domain(); if (old_domain == new_domain) { return attribute; } switch (old_domain) { case ATTR_DOMAIN_CORNER: { switch (new_domain) { case ATTR_DOMAIN_POINT: return blender::bke::adapt_mesh_domain_corner_to_point(*mesh_, std::move(attribute)); default: break; } break; } case ATTR_DOMAIN_POINT: { switch (new_domain) { case ATTR_DOMAIN_CORNER: return blender::bke::adapt_mesh_domain_point_to_corner(*mesh_, std::move(attribute)); default: break; } } default: break; } return {}; } /** \} */