diff options
Diffstat (limited to 'source/blender/blenkernel/intern/attribute_access.cc')
-rw-r--r-- | source/blender/blenkernel/intern/attribute_access.cc | 1080 |
1 files changed, 1080 insertions, 0 deletions
diff --git a/source/blender/blenkernel/intern/attribute_access.cc b/source/blender/blenkernel/intern/attribute_access.cc new file mode 100644 index 00000000000..2345c834be4 --- /dev/null +++ b/source/blender/blenkernel/intern/attribute_access.cc @@ -0,0 +1,1080 @@ +/* + * 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_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::bke::ReadAttributePtr; +using blender::bke::WriteAttributePtr; + +/* 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_if_necessary should be called. + */ +fn::GMutableSpan WriteAttribute::get_span() +{ + if (size_ == 0) { + return fn::GMutableSpan(cpp_type_); + } + if (array_buffer_ == nullptr) { + this->initialize_span(); + } + array_should_be_applied_ = true; + return fn::GMutableSpan(cpp_type_, array_buffer_, size_); +} + +void WriteAttribute::initialize_span() +{ + array_buffer_ = MEM_mallocN_aligned(cpp_type_.size() * size_, cpp_type_.alignment(), __func__); + array_is_temporary_ = true; + /* This does nothing for trivial types, but is necessary for general correctness. */ + cpp_type_.construct_default_n(array_buffer_, 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<float>(), 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<const float *>(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<float>(), 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<typename T> class ArrayWriteAttribute final : public WriteAttribute { + private: + MutableSpan<T> data_; + + public: + ArrayWriteAttribute(AttributeDomain domain, MutableSpan<T> data) + : WriteAttribute(domain, CPPType::get<T>(), 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<const T *>(value); + } + + void initialize_span() 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<typename T> class ArrayReadAttribute final : public ReadAttribute { + private: + Span<T> data_; + + public: + ArrayReadAttribute(AttributeDomain domain, Span<T> data) + : ReadAttribute(domain, CPPType::get<T>(), 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<T *>(data_.data()); + array_is_temporary_ = false; + } +}; + +template<typename StructT, typename ElemT, typename GetFuncT, typename SetFuncT> +class DerivedArrayWriteAttribute final : public WriteAttribute { + private: + MutableSpan<StructT> data_; + GetFuncT get_function_; + SetFuncT set_function_; + + public: + DerivedArrayWriteAttribute(AttributeDomain domain, + MutableSpan<StructT> data, + GetFuncT get_function, + SetFuncT set_function) + : WriteAttribute(domain, CPPType::get<ElemT>(), data.size()), + data_(data), + get_function_(std::move(get_function)), + set_function_(std::move(set_function)) + { + } + + void get_internal(const int64_t index, void *r_value) const override + { + const StructT &struct_value = data_[index]; + const ElemT value = get_function_(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<const ElemT *>(value); + set_function_(struct_value, typed_value); + } +}; + +template<typename StructT, typename ElemT, typename GetFuncT> +class DerivedArrayReadAttribute final : public ReadAttribute { + private: + Span<StructT> data_; + GetFuncT get_function_; + + public: + DerivedArrayReadAttribute(AttributeDomain domain, Span<StructT> data, GetFuncT get_function) + : ReadAttribute(domain, CPPType::get<ElemT>(), data.size()), + data_(data), + get_function_(std::move(get_function)) + { + } + + void get_internal(const int64_t index, void *r_value) const override + { + const StructT &struct_value = data_[index]; + const ElemT value = get_function_(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() + { + 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<MaxValueSize, MaxValueAlignment> 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<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<Color4f>(); + 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<Color4f>()) { + return CD_PROP_COLOR; + } + return static_cast<CustomDataType>(-1); +} + +} // namespace blender::bke + +/* -------------------------------------------------------------------- */ +/** \name Utilities for Accessing Attributes + * \{ */ + +static ReadAttributePtr read_attribute_from_custom_data(const CustomData &custom_data, + const int size, + const StringRef attribute_name, + const AttributeDomain domain) +{ + using namespace blender; + using namespace blender::bke; + for (const CustomDataLayer &layer : Span(custom_data.layers, custom_data.totlayer)) { + if (layer.name != nullptr && layer.name == attribute_name) { + switch (layer.type) { + case CD_PROP_FLOAT: + return std::make_unique<ArrayReadAttribute<float>>( + domain, Span(static_cast<float *>(layer.data), size)); + case CD_PROP_FLOAT2: + return std::make_unique<ArrayReadAttribute<float2>>( + domain, Span(static_cast<float2 *>(layer.data), size)); + case CD_PROP_FLOAT3: + return std::make_unique<ArrayReadAttribute<float3>>( + domain, Span(static_cast<float3 *>(layer.data), size)); + case CD_PROP_INT32: + return std::make_unique<ArrayReadAttribute<int>>( + domain, Span(static_cast<int *>(layer.data), size)); + case CD_PROP_COLOR: + return std::make_unique<ArrayReadAttribute<Color4f>>( + domain, Span(static_cast<Color4f *>(layer.data), size)); + } + } + } + return {}; +} + +static WriteAttributePtr write_attribute_from_custom_data( + CustomData &custom_data, + const int size, + const StringRef attribute_name, + const AttributeDomain domain, + const std::function<void()> &update_customdata_pointers) +{ + + using namespace blender; + using namespace blender::bke; + for (const CustomDataLayer &layer : Span(custom_data.layers, custom_data.totlayer)) { + if (layer.name != nullptr && layer.name == attribute_name) { + const void *data_before = layer.data; + /* The data layer might be shared with someone else. Since the caller wants to modify it, we + * copy it first. */ + CustomData_duplicate_referenced_layer_named(&custom_data, layer.type, layer.name, size); + if (data_before != layer.data) { + update_customdata_pointers(); + } + switch (layer.type) { + case CD_PROP_FLOAT: + return std::make_unique<ArrayWriteAttribute<float>>( + domain, MutableSpan(static_cast<float *>(layer.data), size)); + case CD_PROP_FLOAT2: + return std::make_unique<ArrayWriteAttribute<float2>>( + domain, MutableSpan(static_cast<float2 *>(layer.data), size)); + case CD_PROP_FLOAT3: + return std::make_unique<ArrayWriteAttribute<float3>>( + domain, MutableSpan(static_cast<float3 *>(layer.data), size)); + case CD_PROP_INT32: + return std::make_unique<ArrayWriteAttribute<int>>( + domain, MutableSpan(static_cast<int *>(layer.data), size)); + case CD_PROP_COLOR: + return std::make_unique<ArrayWriteAttribute<Color4f>>( + domain, MutableSpan(static_cast<Color4f *>(layer.data), size)); + } + } + } + return {}; +} + +/* Returns true when the layer was found and is deleted. */ +static bool delete_named_custom_data_layer(CustomData &custom_data, + const StringRef attribute_name, + const int size) +{ + for (const int index : blender::IndexRange(custom_data.totlayer)) { + const CustomDataLayer &layer = custom_data.layers[index]; + if (layer.name == attribute_name) { + CustomData_free_layer(&custom_data, layer.type, size, index); + return true; + } + } + return false; +} + +static void get_custom_data_layer_attribute_names(const CustomData &custom_data, + const GeometryComponent &component, + const AttributeDomain domain, + Set<std::string> &r_names) +{ + for (const CustomDataLayer &layer : blender::Span(custom_data.layers, custom_data.totlayer)) { + if (component.attribute_domain_with_type_supported(domain, + static_cast<CustomDataType>(layer.type))) { + r_names.add(layer.name); + } + } +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Geometry Component + * \{ */ + +bool GeometryComponent::attribute_domain_supported(const AttributeDomain UNUSED(domain)) const +{ + return false; +} + +bool GeometryComponent::attribute_domain_with_type_supported( + const AttributeDomain UNUSED(domain), const CustomDataType UNUSED(data_type)) const +{ + return false; +} + +int GeometryComponent::attribute_domain_size(const AttributeDomain UNUSED(domain)) const +{ + BLI_assert(false); + return 0; +} + +bool GeometryComponent::attribute_is_builtin(const StringRef UNUSED(attribute_name)) const +{ + return true; +} + +ReadAttributePtr GeometryComponent::attribute_try_get_for_read( + const StringRef UNUSED(attribute_name)) const +{ + return {}; +} + +ReadAttributePtr GeometryComponent::attribute_try_adapt_domain(ReadAttributePtr attribute, + const AttributeDomain domain) const +{ + if (attribute && attribute->domain() == domain) { + return attribute; + } + return {}; +} + +WriteAttributePtr GeometryComponent::attribute_try_get_for_write( + const StringRef UNUSED(attribute_name)) +{ + return {}; +} + +bool GeometryComponent::attribute_try_delete(const StringRef UNUSED(attribute_name)) +{ + return false; +} + +bool GeometryComponent::attribute_try_create(const StringRef UNUSED(attribute_name), + const AttributeDomain UNUSED(domain), + const CustomDataType UNUSED(data_type)) +{ + return false; +} + +Set<std::string> GeometryComponent::attribute_names() const +{ + return {}; +} + +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<blender::bke::ConvertedReadAttribute>(std::move(attribute), to_type); +} + +ReadAttributePtr GeometryComponent::attribute_try_get_for_read( + const StringRef attribute_name, + const AttributeDomain domain, + const CustomDataType data_type) const +{ + if (!this->attribute_domain_with_type_supported(domain, data_type)) { + 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 {}; + } + } + + 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_get_for_read(const StringRef attribute_name, + const AttributeDomain domain, + const CustomDataType data_type, + const void *default_value) const +{ + BLI_assert(this->attribute_domain_with_type_supported(domain, data_type)); + + 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<blender::bke::ConstantReadAttribute>( + domain, domain_size, *cpp_type, value); +} + +WriteAttributePtr GeometryComponent::attribute_try_ensure_for_write(const StringRef attribute_name, + const AttributeDomain domain, + const CustomDataType data_type) +{ + 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 (attribute && attribute->domain() == domain && attribute->cpp_type() == *cpp_type) { + return attribute; + } + + if (attribute) { + if (!this->attribute_try_delete(attribute_name)) { + return {}; + } + } + if (!this->attribute_domain_with_type_supported(domain, data_type)) { + return {}; + } + if (!this->attribute_try_create(attribute_name, domain, data_type)) { + return {}; + } + return this->attribute_try_get_for_write(attribute_name); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Point Cloud Component + * \{ */ + +bool PointCloudComponent::attribute_domain_supported(const AttributeDomain domain) const +{ + return domain == ATTR_DOMAIN_POINT; +} + +bool PointCloudComponent::attribute_domain_with_type_supported( + const AttributeDomain domain, const CustomDataType data_type) const +{ + return domain == ATTR_DOMAIN_POINT && ELEM(data_type, + CD_PROP_FLOAT, + CD_PROP_FLOAT2, + CD_PROP_FLOAT3, + CD_PROP_INT32, + CD_PROP_COLOR); +} + +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; +} + +bool PointCloudComponent::attribute_is_builtin(const StringRef attribute_name) const +{ + return attribute_name == "position"; +} + +ReadAttributePtr PointCloudComponent::attribute_try_get_for_read( + const StringRef attribute_name) const +{ + if (pointcloud_ == nullptr) { + return {}; + } + + return read_attribute_from_custom_data( + pointcloud_->pdata, pointcloud_->totpoint, attribute_name, ATTR_DOMAIN_POINT); +} + +WriteAttributePtr PointCloudComponent::attribute_try_get_for_write(const StringRef attribute_name) +{ + PointCloud *pointcloud = this->get_for_write(); + if (pointcloud == nullptr) { + return {}; + } + + return write_attribute_from_custom_data( + pointcloud->pdata, pointcloud->totpoint, attribute_name, ATTR_DOMAIN_POINT, [&]() { + BKE_pointcloud_update_customdata_pointers(pointcloud); + }); +} + +bool PointCloudComponent::attribute_try_delete(const StringRef attribute_name) +{ + if (this->attribute_is_builtin(attribute_name)) { + return false; + } + PointCloud *pointcloud = this->get_for_write(); + if (pointcloud == nullptr) { + return false; + } + delete_named_custom_data_layer(pointcloud->pdata, attribute_name, pointcloud->totpoint); + return true; +} + +static bool custom_data_has_layer_with_name(const CustomData &custom_data, const StringRef name) +{ + for (const CustomDataLayer &layer : blender::Span(custom_data.layers, custom_data.totlayer)) { + if (layer.name == name) { + return true; + } + } + return false; +} + +bool PointCloudComponent::attribute_try_create(const StringRef attribute_name, + const AttributeDomain domain, + const CustomDataType data_type) +{ + if (this->attribute_is_builtin(attribute_name)) { + return false; + } + if (!this->attribute_domain_with_type_supported(domain, data_type)) { + return false; + } + PointCloud *pointcloud = this->get_for_write(); + if (pointcloud == nullptr) { + return false; + } + if (custom_data_has_layer_with_name(pointcloud->pdata, attribute_name)) { + return false; + } + + char attribute_name_c[MAX_NAME]; + attribute_name.copy(attribute_name_c); + CustomData_add_layer_named( + &pointcloud->pdata, data_type, CD_DEFAULT, nullptr, pointcloud_->totpoint, attribute_name_c); + return true; +} + +Set<std::string> PointCloudComponent::attribute_names() const +{ + if (pointcloud_ == nullptr) { + return {}; + } + + Set<std::string> names; + get_custom_data_layer_attribute_names(pointcloud_->pdata, *this, ATTR_DOMAIN_POINT, names); + return names; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Mesh Component + * \{ */ + +bool MeshComponent::attribute_domain_supported(const AttributeDomain domain) const +{ + return ELEM( + domain, ATTR_DOMAIN_CORNER, ATTR_DOMAIN_POINT, ATTR_DOMAIN_EDGE, ATTR_DOMAIN_POLYGON); +} + +bool MeshComponent::attribute_domain_with_type_supported(const AttributeDomain domain, + const CustomDataType data_type) const +{ + if (!this->attribute_domain_supported(domain)) { + return false; + } + return ELEM( + data_type, CD_PROP_FLOAT, CD_PROP_FLOAT2, CD_PROP_FLOAT3, CD_PROP_INT32, CD_PROP_COLOR); +} + +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; +} + +bool MeshComponent::attribute_is_builtin(const StringRef attribute_name) const +{ + return attribute_name == "position"; +} + +ReadAttributePtr MeshComponent::attribute_try_get_for_read(const StringRef attribute_name) const +{ + if (mesh_ == nullptr) { + return {}; + } + + if (attribute_name == "position") { + auto get_vertex_position = [](const MVert &vert) { return float3(vert.co); }; + return std::make_unique< + blender::bke::DerivedArrayReadAttribute<MVert, float3, decltype(get_vertex_position)>>( + ATTR_DOMAIN_POINT, blender::Span(mesh_->mvert, mesh_->totvert), get_vertex_position); + } + + ReadAttributePtr corner_attribute = read_attribute_from_custom_data( + mesh_->ldata, mesh_->totloop, attribute_name, ATTR_DOMAIN_CORNER); + if (corner_attribute) { + return corner_attribute; + } + + const int vertex_group_index = vertex_group_names_.lookup_default(attribute_name, -1); + if (vertex_group_index >= 0) { + return std::make_unique<blender::bke::VertexWeightReadAttribute>( + mesh_->dvert, mesh_->totvert, vertex_group_index); + } + + ReadAttributePtr vertex_attribute = read_attribute_from_custom_data( + mesh_->vdata, mesh_->totvert, attribute_name, ATTR_DOMAIN_POINT); + if (vertex_attribute) { + return vertex_attribute; + } + + ReadAttributePtr edge_attribute = read_attribute_from_custom_data( + mesh_->edata, mesh_->totedge, attribute_name, ATTR_DOMAIN_EDGE); + if (edge_attribute) { + return edge_attribute; + } + + ReadAttributePtr polygon_attribute = read_attribute_from_custom_data( + mesh_->pdata, mesh_->totpoly, attribute_name, ATTR_DOMAIN_POLYGON); + if (polygon_attribute) { + return polygon_attribute; + } + + return {}; +} + +WriteAttributePtr MeshComponent::attribute_try_get_for_write(const StringRef attribute_name) +{ + Mesh *mesh = this->get_for_write(); + if (mesh == nullptr) { + return {}; + } + + const std::function<void()> update_mesh_pointers = [&]() { + BKE_mesh_update_customdata_pointers(mesh, false); + }; + + if (attribute_name == "position") { + CustomData_duplicate_referenced_layer(&mesh->vdata, CD_MVERT, mesh->totvert); + update_mesh_pointers(); + + auto get_vertex_position = [](const MVert &vert) { return float3(vert.co); }; + auto set_vertex_position = [](MVert &vert, const float3 &co) { copy_v3_v3(vert.co, co); }; + return std::make_unique< + blender::bke::DerivedArrayWriteAttribute<MVert, + float3, + decltype(get_vertex_position), + decltype(set_vertex_position)>>( + ATTR_DOMAIN_POINT, + blender::MutableSpan(mesh_->mvert, mesh_->totvert), + get_vertex_position, + set_vertex_position); + } + + WriteAttributePtr corner_attribute = write_attribute_from_custom_data( + mesh_->ldata, mesh_->totloop, attribute_name, ATTR_DOMAIN_CORNER, update_mesh_pointers); + if (corner_attribute) { + return corner_attribute; + } + + const int vertex_group_index = vertex_group_names_.lookup_default_as(attribute_name, -1); + if (vertex_group_index >= 0) { + if (mesh_->dvert == nullptr) { + BKE_object_defgroup_data_create(&mesh_->id); + } + return std::make_unique<blender::bke::VertexWeightWriteAttribute>( + mesh_->dvert, mesh_->totvert, vertex_group_index); + } + + WriteAttributePtr vertex_attribute = write_attribute_from_custom_data( + mesh_->vdata, mesh_->totvert, attribute_name, ATTR_DOMAIN_POINT, update_mesh_pointers); + if (vertex_attribute) { + return vertex_attribute; + } + + WriteAttributePtr edge_attribute = write_attribute_from_custom_data( + mesh_->edata, mesh_->totedge, attribute_name, ATTR_DOMAIN_EDGE, update_mesh_pointers); + if (edge_attribute) { + return edge_attribute; + } + + WriteAttributePtr polygon_attribute = write_attribute_from_custom_data( + mesh_->pdata, mesh_->totpoly, attribute_name, ATTR_DOMAIN_POLYGON, update_mesh_pointers); + if (polygon_attribute) { + return polygon_attribute; + } + + return {}; +} + +bool MeshComponent::attribute_try_delete(const StringRef attribute_name) +{ + if (this->attribute_is_builtin(attribute_name)) { + return false; + } + Mesh *mesh = this->get_for_write(); + if (mesh == nullptr) { + return false; + } + + delete_named_custom_data_layer(mesh_->ldata, attribute_name, mesh_->totloop); + delete_named_custom_data_layer(mesh_->vdata, attribute_name, mesh_->totvert); + delete_named_custom_data_layer(mesh_->edata, attribute_name, mesh_->totedge); + delete_named_custom_data_layer(mesh_->pdata, attribute_name, mesh_->totpoly); + + const int vertex_group_index = vertex_group_names_.lookup_default_as(attribute_name, -1); + if (vertex_group_index != -1) { + for (MDeformVert &dvert : blender::MutableSpan(mesh_->dvert, mesh_->totvert)) { + MDeformWeight *weight = BKE_defvert_find_index(&dvert, vertex_group_index); + BKE_defvert_remove_group(&dvert, weight); + } + vertex_group_names_.remove_as(attribute_name); + } + + return true; +} + +bool MeshComponent::attribute_try_create(const StringRef attribute_name, + const AttributeDomain domain, + const CustomDataType data_type) +{ + if (this->attribute_is_builtin(attribute_name)) { + return false; + } + if (!this->attribute_domain_with_type_supported(domain, data_type)) { + return false; + } + Mesh *mesh = this->get_for_write(); + if (mesh == nullptr) { + return false; + } + + char attribute_name_c[MAX_NAME]; + attribute_name.copy(attribute_name_c); + + switch (domain) { + case ATTR_DOMAIN_CORNER: { + if (custom_data_has_layer_with_name(mesh->ldata, attribute_name)) { + return false; + } + CustomData_add_layer_named( + &mesh->ldata, data_type, CD_DEFAULT, nullptr, mesh->totloop, attribute_name_c); + return true; + } + case ATTR_DOMAIN_POINT: { + if (custom_data_has_layer_with_name(mesh->vdata, attribute_name)) { + return false; + } + if (vertex_group_names_.contains_as(attribute_name)) { + return false; + } + CustomData_add_layer_named( + &mesh->vdata, data_type, CD_DEFAULT, nullptr, mesh->totvert, attribute_name_c); + return true; + } + case ATTR_DOMAIN_EDGE: { + if (custom_data_has_layer_with_name(mesh->edata, attribute_name)) { + return false; + } + CustomData_add_layer_named( + &mesh->edata, data_type, CD_DEFAULT, nullptr, mesh->totedge, attribute_name_c); + return true; + } + case ATTR_DOMAIN_POLYGON: { + if (custom_data_has_layer_with_name(mesh->pdata, attribute_name)) { + return false; + } + CustomData_add_layer_named( + &mesh->pdata, data_type, CD_DEFAULT, nullptr, mesh->totpoly, attribute_name_c); + return true; + } + default: + return false; + } +} + +Set<std::string> MeshComponent::attribute_names() const +{ + if (mesh_ == nullptr) { + return {}; + } + + Set<std::string> names; + names.add("position"); + for (StringRef name : vertex_group_names_.keys()) { + names.add(name); + } + get_custom_data_layer_attribute_names(mesh_->pdata, *this, ATTR_DOMAIN_CORNER, names); + get_custom_data_layer_attribute_names(mesh_->vdata, *this, ATTR_DOMAIN_POINT, names); + get_custom_data_layer_attribute_names(mesh_->edata, *this, ATTR_DOMAIN_EDGE, names); + get_custom_data_layer_attribute_names(mesh_->pdata, *this, ATTR_DOMAIN_POLYGON, names); + return names; +} + +/** \} */ |