From fa03c53d4a8ba16150232316a6ff8843ce02bb34 Mon Sep 17 00:00:00 2001 From: Hans Goudey Date: Mon, 9 Jun 2025 21:53:20 +0200 Subject: [PATCH] Point Cloud: Use AttributeStorage instead of CustomData This moves `PointCloud` to use the recently added `AttributeStorage` at runtime. Mainly this involves implementing the higher level attribute API on top, and implementing the RNA API as well. The attribute RNA type is now backed by either CustomDataLayer or bke::Attribute. For now the new code is specific to point clouds but next steps can reuse it for Grease Pencil layer attributes, curves, and eventually meshes. Point cloud attributes no longer have a name length limit. Internally, the `AttributeStorage` API is extended with a few additions: - The data structs have static constructors for convenience. - A few functions give index-based access to attributes - A "rename" function is added. The `Attribute` RNA type now exposes a `storage_type` property. For now the "single value" option is still unused at runtime, and accessing the single value data isn't implemented yet. Pull Request: https://projects.blender.org/blender/blender/pulls/139165 --- .../BKE_attribute_legacy_convert.hh | 3 - .../blenkernel/BKE_attribute_storage.hh | 24 +- .../blender/blenkernel/BKE_blender_version.h | 6 +- source/blender/blenkernel/intern/attribute.cc | 41 ++- .../intern/attribute_legacy_convert.cc | 8 +- .../blenkernel/intern/attribute_storage.cc | 131 ++++++- .../blender/blenkernel/intern/pointcloud.cc | 104 +++--- .../intern/pointcloud_attributes.cc | 296 +++++++++++++--- .../blenloader/intern/versioning_290.cc | 4 +- .../blenloader/intern/versioning_450.cc | 1 + .../blenloader/intern/versioning_500.cc | 7 + .../editors/geometry/geometry_attributes.cc | 43 +++ .../editors/geometry/node_group_operator.cc | 2 +- source/blender/editors/object/object_add.cc | 1 - .../blender/editors/pointcloud/intern/undo.cc | 32 +- source/blender/geometry/intern/randomize.cc | 30 +- .../blender/makesdna/DNA_pointcloud_types.h | 7 +- .../blender/makesdna/intern/dna_rename_defs.h | 1 + .../blender/makesrna/intern/rna_attribute.cc | 325 +++++++++++++++++- 19 files changed, 890 insertions(+), 176 deletions(-) diff --git a/source/blender/blenkernel/BKE_attribute_legacy_convert.hh b/source/blender/blenkernel/BKE_attribute_legacy_convert.hh index 8ec259e360d..6bdfc943616 100644 --- a/source/blender/blenkernel/BKE_attribute_legacy_convert.hh +++ b/source/blender/blenkernel/BKE_attribute_legacy_convert.hh @@ -50,9 +50,6 @@ void curves_convert_storage_to_customdata(CurvesGeometry &curves); /** See #mesh_convert_customdata_to_storage. */ void curves_convert_customdata_to_storage(CurvesGeometry &curves); -/** See #mesh_convert_storage_to_customdata. */ -void pointcloud_convert_storage_to_customdata(PointCloud &pointcloud); - /** See #mesh_convert_customdata_to_storage. */ void pointcloud_convert_customdata_to_storage(PointCloud &pointcloud); diff --git a/source/blender/blenkernel/BKE_attribute_storage.hh b/source/blender/blenkernel/BKE_attribute_storage.hh index 7178736d0db..ba372d03d54 100644 --- a/source/blender/blenkernel/BKE_attribute_storage.hh +++ b/source/blender/blenkernel/BKE_attribute_storage.hh @@ -17,8 +17,10 @@ struct BlendDataReader; struct BlendWriter; namespace blender { +class GPointer; +class CPPType; class ResourceScope; -} +} // namespace blender namespace blender::bke { @@ -41,6 +43,9 @@ class Attribute { /* The number of elements in the array. */ int64_t size; ImplicitSharingPtr<> sharing_info; + static ArrayData ForValue(const GPointer &value, int64_t domain_size); + static ArrayData ForDefaultValue(const CPPType &type, int64_t domain_size); + static ArrayData ForConstructed(const CPPType &type, int64_t domain_size); }; /** Data for an attribute stored as a single value for the entire domain. */ struct SingleData { @@ -48,6 +53,8 @@ class Attribute { * It's not necessary to manage a single value. */ void *value; ImplicitSharingPtr<> sharing_info; + static SingleData ForValue(const GPointer &value); + static SingleData ForDefaultValue(const CPPType &type); }; using DataVariant = std::variant; friend AttributeStorage; @@ -133,6 +140,18 @@ class AttributeStorage : public ::AttributeStorage { */ void foreach(FunctionRef fn); void foreach(FunctionRef fn) const; + void foreach_with_stop(FunctionRef fn); + void foreach_with_stop(FunctionRef fn) const; + + /** Return the number of attributes. */ + int count() const; + + /** Return the attribute at the given index. */ + Attribute &at_index(int index); + const Attribute &at_index(int index) const; + + /** Return the index of the attribute with the given name, or -1 if not found. */ + int index_of(StringRef name) const; /** * Try to find the attribute with a given name. The non-const overload does not make the @@ -159,6 +178,9 @@ class AttributeStorage : public ::AttributeStorage { /** Return a possibly changed version of the input name that is unique within existing names. */ std::string unique_name_calc(StringRef name); + /** Change the name of a single existing attribute. */ + void rename(StringRef old_name, std::string new_name); + /** * Read data owned by the #AttributeStorage struct. This works by converting the DNA-specific * types stored in the files to the runtime data structures. diff --git a/source/blender/blenkernel/BKE_blender_version.h b/source/blender/blenkernel/BKE_blender_version.h index 38033aeefb6..8a08a751752 100644 --- a/source/blender/blenkernel/BKE_blender_version.h +++ b/source/blender/blenkernel/BKE_blender_version.h @@ -27,7 +27,7 @@ /* Blender file format version. */ #define BLENDER_FILE_VERSION BLENDER_VERSION -#define BLENDER_FILE_SUBVERSION 1 +#define BLENDER_FILE_SUBVERSION 2 /* Minimum Blender version that supports reading file written with the current * version. Older Blender versions will test this and cancel loading the file, showing a warning to @@ -36,8 +36,8 @@ * See * https://developer.blender.org/docs/handbook/guidelines/compatibility_handling_for_blend_files/ * for details. */ -#define BLENDER_FILE_MIN_VERSION 306 -#define BLENDER_FILE_MIN_SUBVERSION 13 +#define BLENDER_FILE_MIN_VERSION 405 +#define BLENDER_FILE_MIN_SUBVERSION 85 /** User readable version string. */ const char *BKE_blender_version_string(void); diff --git a/source/blender/blenkernel/intern/attribute.cc b/source/blender/blenkernel/intern/attribute.cc index ef0d7e8fc04..3c33d37e303 100644 --- a/source/blender/blenkernel/intern/attribute.cc +++ b/source/blender/blenkernel/intern/attribute.cc @@ -115,9 +115,8 @@ static std::array get_domains(const AttributeOwner switch (owner.type()) { case AttributeOwnerType::PointCloud: { - PointCloud *pointcloud = owner.get_pointcloud(); - info[int(AttrDomain::Point)].customdata = &pointcloud->pdata; - info[int(AttrDomain::Point)].length = pointcloud->totpoint; + /* This should be implemented with #AttributeStorage instead. */ + BLI_assert_unreachable(); break; } case AttributeOwnerType::Mesh: { @@ -279,6 +278,17 @@ bool BKE_attribute_rename(AttributeOwner &owner, return false; } + if (owner.type() == AttributeOwnerType::PointCloud) { + PointCloud &pointcloud = *owner.get_pointcloud(); + bke::AttributeStorage &attributes = pointcloud.attribute_storage.wrap(); + if (!attributes.lookup(old_name)) { + BKE_report(reports, RPT_ERROR, "Attribute is not part of this geometry"); + return false; + } + attributes.rename(old_name, new_name); + return true; + } + /* NOTE: Checking if the new name matches the old name only makes sense when the name * is clamped to it's maximum length, otherwise assigning an over-long name multiple times * will add `.001` suffix unnecessarily. */ @@ -382,6 +392,9 @@ static bool attribute_name_exists(const AttributeOwner &owner, const StringRef n std::string BKE_attribute_calc_unique_name(const AttributeOwner &owner, const StringRef name) { + if (owner.type() == AttributeOwnerType::PointCloud) { + return owner.get_pointcloud()->attribute_storage.wrap().unique_name_calc(name); + } return BLI_uniquename_cb( [&](const StringRef new_name) { return attribute_name_exists(owner, new_name); }, '.', @@ -533,9 +546,8 @@ bool BKE_attribute_remove(AttributeOwner &owner, const StringRef name, ReportLis return false; } - const std::array info = get_domains(owner); - if (owner.type() == AttributeOwnerType::Mesh) { + const std::array info = get_domains(owner); Mesh *mesh = owner.get_mesh(); if (BMEditMesh *em = mesh->runtime->edit_mesh.get()) { for (const int domain : IndexRange(ATTR_DOMAIN_NUM)) { @@ -801,10 +813,21 @@ bool BKE_attribute_required(const AttributeOwner &owner, const StringRef name) std::optional BKE_attributes_active_name_get(AttributeOwner &owner) { + using namespace blender; + using namespace blender::bke; int active_index = *BKE_attributes_active_index_p(owner); if (active_index == -1) { return std::nullopt; } + if (owner.type() == AttributeOwnerType::PointCloud) { + PointCloud &pointcloud = *owner.get_pointcloud(); + bke::AttributeStorage &storage = pointcloud.attribute_storage.wrap(); + if (active_index >= storage.count()) { + return std::nullopt; + } + return storage.at_index(active_index).name(); + } + if (active_index > BKE_attributes_length(owner, ATTR_DOMAIN_MASK_ALL, CD_MASK_PROP_ALL)) { active_index = 0; } @@ -837,6 +860,14 @@ std::optional BKE_attributes_active_name_get(AttributeOw void BKE_attributes_active_set(AttributeOwner &owner, const StringRef name) { + using namespace blender; + if (owner.type() == AttributeOwnerType::PointCloud) { + PointCloud &pointcloud = *owner.get_pointcloud(); + bke::AttributeStorage &attributes = pointcloud.attribute_storage.wrap(); + *BKE_attributes_active_index_p(owner) = attributes.index_of(name); + return; + } + const CustomDataLayer *layer = BKE_attribute_search( owner, name, CD_MASK_PROP_ALL, ATTR_DOMAIN_MASK_ALL); BLI_assert(layer != nullptr); diff --git a/source/blender/blenkernel/intern/attribute_legacy_convert.cc b/source/blender/blenkernel/intern/attribute_legacy_convert.cc index 2004e85d54f..31fc9fdcb2c 100644 --- a/source/blender/blenkernel/intern/attribute_legacy_convert.cc +++ b/source/blender/blenkernel/intern/attribute_legacy_convert.cc @@ -268,16 +268,10 @@ void curves_convert_customdata_to_storage(CurvesGeometry &curves) {AttrDomain::Curve, {curves.curve_data, curves.curves_num()}}}); } -void pointcloud_convert_storage_to_customdata(PointCloud &pointcloud) -{ - convert_storage_to_customdata(pointcloud.attribute_storage.wrap(), - {{AttrDomain::Point, {pointcloud.pdata, pointcloud.totpoint}}}); -} - void pointcloud_convert_customdata_to_storage(PointCloud &pointcloud) { pointcloud.attribute_storage.wrap() = attribute_legacy_convert_customdata_to_storage( - {{AttrDomain::Point, {pointcloud.pdata, pointcloud.totpoint}}}); + {{AttrDomain::Point, {pointcloud.pdata_legacy, pointcloud.totpoint}}}); } void grease_pencil_convert_storage_to_customdata(GreasePencil &grease_pencil) diff --git a/source/blender/blenkernel/intern/attribute_storage.cc b/source/blender/blenkernel/intern/attribute_storage.cc index 17905d05ce1..56513b903fa 100644 --- a/source/blender/blenkernel/intern/attribute_storage.cc +++ b/source/blender/blenkernel/intern/attribute_storage.cc @@ -57,6 +57,62 @@ class ArrayDataImplicitSharing : public ImplicitSharingInfo { } }; +Attribute::ArrayData Attribute::ArrayData::ForValue(const GPointer &value, + const int64_t domain_size) +{ + Attribute::ArrayData data{}; + const CPPType &type = *value.type(); + const void *value_ptr = type.default_value(); + + /* Prefer `calloc` to zeroing after allocation since it is faster. */ + if (BLI_memory_is_zero(value_ptr, type.size)) { + data.data = MEM_calloc_arrayN_aligned(domain_size, type.size, type.alignment, __func__); + } + else { + data.data = MEM_malloc_arrayN_aligned(domain_size, type.size, type.alignment, __func__); + type.fill_construct_n(value_ptr, data.data, domain_size); + } + + data.size = domain_size; + BLI_assert(type.is_trivially_destructible); + data.sharing_info = ImplicitSharingPtr<>(implicit_sharing::info_for_mem_free(data.data)); + return data; +} + +Attribute::ArrayData Attribute::ArrayData::ForDefaultValue(const CPPType &type, + const int64_t domain_size) +{ + return ForValue(GPointer(type, type.default_value()), domain_size); +} + +Attribute::ArrayData Attribute::ArrayData::ForConstructed(const CPPType &type, + const int64_t domain_size) +{ + Attribute::ArrayData data{}; + data.data = MEM_malloc_arrayN_aligned(domain_size, type.size, type.alignment, __func__); + type.default_construct_n(data.data, domain_size); + data.size = domain_size; + BLI_assert(type.is_trivially_destructible); + data.sharing_info = ImplicitSharingPtr<>(implicit_sharing::info_for_mem_free(data.data)); + return data; +} + +Attribute::SingleData Attribute::SingleData::ForValue(const GPointer &value) +{ + Attribute::SingleData data{}; + const CPPType &type = *value.type(); + data.value = MEM_mallocN_aligned(type.size, type.alignment, __func__); + type.copy_construct(value.get(), data.value); + BLI_assert(type.is_trivially_destructible); + data.sharing_info = ImplicitSharingPtr<>(implicit_sharing::info_for_mem_free(data.value)); + return data; +} + +Attribute::SingleData Attribute::SingleData::ForDefaultValue(const CPPType &type) +{ + return ForValue(GPointer(type, type.default_value())); +} + void AttributeStorage::foreach(FunctionRef fn) { for (const std::unique_ptr &attribute : this->runtime->attributes) { @@ -70,11 +126,21 @@ void AttributeStorage::foreach(FunctionRef fn) const } } -static ImplicitSharingInfo *create_sharing_info_for_array(void *data, - const int64_t size, - const CPPType &type) +void AttributeStorage::foreach_with_stop(FunctionRef fn) { - return MEM_new(__func__, data, size, type); + for (const std::unique_ptr &attribute : this->runtime->attributes) { + if (!fn(*attribute)) { + break; + } + } +} +void AttributeStorage::foreach_with_stop(FunctionRef fn) const +{ + for (const std::unique_ptr &attribute : this->runtime->attributes) { + if (!fn(*attribute)) { + break; + } + } } AttrStorageType Attribute::storage_type() const @@ -96,19 +162,18 @@ Attribute::DataVariant &Attribute::data_for_write() data->sharing_info->tag_ensured_mutable(); return data_; } - - const CPPType &cpp_type = attribute_type_to_cpp_type(type_); - void *new_data = MEM_malloc_arrayN_aligned( - data->size, cpp_type.size, cpp_type.alignment, __func__); - cpp_type.copy_construct_n(data->data, new_data, data->size); - - data->data = new_data; - data->sharing_info = ImplicitSharingPtr<>( - create_sharing_info_for_array(data->data, data->size, cpp_type)); + const CPPType &type = attribute_type_to_cpp_type(type_); + ArrayData new_data = ArrayData::ForConstructed(type, data->size); + type.copy_construct_n(data->data, new_data.data, data->size); + *data = std::move(new_data); } - else if (std::get_if(&data_)) { - /* Not yet implemented because #SingleData isn't used at runtime yet. */ - BLI_assert_unreachable(); + else if (auto *data = std::get_if(&data_)) { + if (data->sharing_info->is_mutable()) { + data->sharing_info->tag_ensured_mutable(); + return data_; + } + const CPPType &type = attribute_type_to_cpp_type(type_); + *data = SingleData::ForValue(GPointer(type, data->value)); } return data_; } @@ -163,6 +228,25 @@ AttributeStorage::~AttributeStorage() MEM_delete(this->runtime); } +int AttributeStorage::count() const +{ + return this->runtime->attributes.size(); +} + +Attribute &AttributeStorage::at_index(int index) +{ + return *this->runtime->attributes[index]; +} +const Attribute &AttributeStorage::at_index(int index) const +{ + return *this->runtime->attributes[index]; +} + +int AttributeStorage::index_of(StringRef name) const +{ + return this->runtime->attributes.index_of_try_as(name); +} + const Attribute *AttributeStorage::lookup(const StringRef name) const { const std::unique_ptr *attribute = @@ -210,6 +294,19 @@ std::string AttributeStorage::unique_name_calc(const StringRef name) [&](const StringRef check_name) { return this->lookup(check_name) != nullptr; }, '.', name); } +void AttributeStorage::rename(const StringRef old_name, std::string new_name) +{ + /* The VectorSet must be rebuilt from scratch because the data used to create the hash is + * changed. */ + const int index = this->runtime->attributes.index_of_try_as(old_name); + Vector> old_vector = this->runtime->attributes.extract_vector(); + old_vector[index]->name_ = std::move(new_name); + this->runtime->attributes.reserve(old_vector.size()); + for (std::unique_ptr &attribute : old_vector) { + this->runtime->attributes.add_new(std::move(attribute)); + } +} + static void read_array_data(BlendDataReader &reader, const int8_t dna_attr_type, const int64_t size, @@ -443,7 +540,7 @@ void attribute_storage_blend_write_prepare( } } data.foreach([&](Attribute &attr) { - if (!U.experimental.use_attribute_storage_write) { + if (!U.experimental.use_attribute_storage_write && !layers_to_write.is_empty()) { /* In version 4.5, all attribute data is written in the #CustomData format (at least when the * debug option is not enabled), so the #Attribute needs to be converted to a * #CustomDataLayer in the proper list. This is only relevant when #AttributeStorage is diff --git a/source/blender/blenkernel/intern/pointcloud.cc b/source/blender/blenkernel/intern/pointcloud.cc index 517011fae53..f42bb5d6113 100644 --- a/source/blender/blenkernel/intern/pointcloud.cc +++ b/source/blender/blenkernel/intern/pointcloud.cc @@ -63,8 +63,6 @@ static void pointcloud_init_data(ID *id) new (&pointcloud->attribute_storage.wrap()) blender::bke::AttributeStorage(); pointcloud->runtime = new blender::bke::PointCloudRuntime(); - - CustomData_reset(&pointcloud->pdata); } static void pointcloud_copy_data(Main * /*bmain*/, @@ -77,8 +75,6 @@ static void pointcloud_copy_data(Main * /*bmain*/, const PointCloud *pointcloud_src = (const PointCloud *)id_src; pointcloud_dst->mat = static_cast(MEM_dupallocN(pointcloud_src->mat)); - CustomData_init_from( - &pointcloud_src->pdata, &pointcloud_dst->pdata, CD_MASK_ALL, pointcloud_dst->totpoint); new (&pointcloud_dst->attribute_storage.wrap()) blender::bke::AttributeStorage(pointcloud_src->attribute_storage.wrap()); @@ -101,7 +97,6 @@ static void pointcloud_free_data(ID *id) PointCloud *pointcloud = (PointCloud *)id; BKE_animdata_free(&pointcloud->id, false); BKE_pointcloud_batch_cache_free(pointcloud); - CustomData_free(&pointcloud->pdata); pointcloud->attribute_storage.wrap().~AttributeStorage(); MEM_SAFE_FREE(pointcloud->mat); delete pointcloud->runtime; @@ -122,12 +117,9 @@ static void pointcloud_blend_write(BlendWriter *writer, ID *id, const void *id_a PointCloud *pointcloud = (PointCloud *)id; ResourceScope scope; - Vector point_layers; bke::AttributeStorage::BlendWriteData attribute_data{scope}; - attribute_storage_blend_write_prepare( - pointcloud->attribute_storage.wrap(), {{AttrDomain::Point, &point_layers}}, attribute_data); - CustomData_blend_write_prepare( - pointcloud->pdata, AttrDomain::Point, pointcloud->totpoint, point_layers, attribute_data); + attribute_storage_blend_write_prepare(pointcloud->attribute_storage.wrap(), {}, attribute_data); + BLI_assert(pointcloud->pdata_legacy.totlayer == 0); pointcloud->attribute_storage.dna_attributes = attribute_data.attributes.data(); pointcloud->attribute_storage.dna_attributes_num = attribute_data.attributes.size(); @@ -136,12 +128,6 @@ static void pointcloud_blend_write(BlendWriter *writer, ID *id, const void *id_a BKE_id_blend_write(writer, &pointcloud->id); /* Direct data */ - CustomData_blend_write(writer, - &pointcloud->pdata, - point_layers, - pointcloud->totpoint, - CD_MASK_ALL, - &pointcloud->id); pointcloud->attribute_storage.wrap().blend_write(*writer, attribute_data); BLO_write_pointer_array(writer, pointcloud->totcol, pointcloud->mat); @@ -152,12 +138,9 @@ static void pointcloud_blend_read_data(BlendDataReader *reader, ID *id) PointCloud *pointcloud = (PointCloud *)id; /* Geometry */ - CustomData_blend_read(reader, &pointcloud->pdata, pointcloud->totpoint); + CustomData_blend_read(reader, &pointcloud->pdata_legacy, pointcloud->totpoint); pointcloud->attribute_storage.wrap().blend_read(*reader); - /* Forward compatibility. To be removed when runtime format changes. */ - blender::bke::pointcloud_convert_storage_to_customdata(*pointcloud); - /* Materials */ BLO_read_pointer_array(reader, pointcloud->totcol, (void **)&pointcloud->mat); @@ -199,11 +182,22 @@ static VArray get_varray_attribute(const PointCloud &pointcloud, const StringRef name, const T default_value) { - const eCustomDataType type = blender::bke::cpp_type_to_custom_data_type(CPPType::get()); - - const T *data = (const T *)CustomData_get_layer_named(&pointcloud.pdata, type, name); - if (data != nullptr) { - return VArray::ForSpan(Span(data, pointcloud.totpoint)); + using namespace blender; + const bke::Attribute *attr = pointcloud.attribute_storage.wrap().lookup(name); + if (!attr) { + return VArray::ForSingle(default_value, pointcloud.totpoint); + } + switch (attr->storage_type()) { + case bke::AttrStorageType::Array: { + const auto &data = std::get(attr->data()); + const Span span(static_cast(data.data), data.size); + BLI_assert(data.size == pointcloud.totpoint); + return VArray::ForSpan(span); + } + case bke::AttrStorageType::Single: { + const auto &data = std::get(attr->data()); + return VArray::ForSingle(*static_cast(data.value), pointcloud.totpoint); + } } return VArray::ForSingle(default_value, pointcloud.totpoint); } @@ -211,13 +205,16 @@ static VArray get_varray_attribute(const PointCloud &pointcloud, template static Span get_span_attribute(const PointCloud &pointcloud, const StringRef name) { - const eCustomDataType type = blender::bke::cpp_type_to_custom_data_type(CPPType::get()); - - T *data = (T *)CustomData_get_layer_named(&pointcloud.pdata, type, name); - if (data == nullptr) { + using namespace blender; + const bke::Attribute *attr = pointcloud.attribute_storage.wrap().lookup(name); + if (!attr) { return {}; } - return {data, pointcloud.totpoint}; + if (const auto *array_data = std::get_if(&attr->data())) { + BLI_assert(array_data->size == pointcloud.totpoint); + return Span(static_cast(array_data->data), array_data->size); + } + return {}; } template @@ -225,23 +222,35 @@ static MutableSpan get_mutable_attribute(PointCloud &pointcloud, const StringRef name, const T default_value = T()) { + using namespace blender; if (pointcloud.totpoint <= 0) { return {}; } - const eCustomDataType type = blender::bke::cpp_type_to_custom_data_type(CPPType::get()); - - T *data = (T *)CustomData_get_layer_named_for_write( - &pointcloud.pdata, type, name, pointcloud.totpoint); - if (data != nullptr) { - return {data, pointcloud.totpoint}; + const bke::AttrType type = bke::cpp_type_to_attribute_type(CPPType::get()); + if (bke::Attribute *attr = pointcloud.attribute_storage.wrap().lookup(name)) { + if (attr->data_type() == type) { + if (const auto *single_data = std::get_if(&attr->data())) { + /* Convert single value storage to array storage. */ + const GPointer g_value(CPPType::get(), single_data->value); + attr->data_for_write() = bke::Attribute::ArrayData::ForValue(g_value, pointcloud.totpoint); + } + auto &array_data = std::get(attr->data_for_write()); + BLI_assert(array_data.size == pointcloud.totpoint); + return MutableSpan(static_cast(array_data.data), pointcloud.totpoint); + } + /* The attribute has the wrong type. This shouldn't happen for builtin attributes, but just in + * case, remove it. */ + pointcloud.attribute_storage.wrap().remove(name); } - data = (T *)CustomData_add_layer_named( - &pointcloud.pdata, type, CD_SET_DEFAULT, pointcloud.totpoint, name); - MutableSpan span = {data, pointcloud.totpoint}; - if (pointcloud.totpoint > 0 && span.first() != default_value) { - span.fill(default_value); - } - return span; + bke::Attribute &attr = pointcloud.attribute_storage.wrap().add( + name, + bke::AttrDomain::Point, + type, + bke::Attribute::ArrayData::ForValue({CPPType::get(), &default_value}, + pointcloud.totpoint)); + auto &array_data = std::get(attr.data_for_write()); + BLI_assert(array_data.size == pointcloud.totpoint); + return MutableSpan(static_cast(array_data.data), pointcloud.totpoint); } Span PointCloud::positions() const @@ -288,11 +297,8 @@ void BKE_pointcloud_nomain_to_pointcloud(PointCloud *pointcloud_src, PointCloud { BLI_assert(pointcloud_src->id.tag & ID_TAG_NO_MAIN); - CustomData_free(&pointcloud_dst->pdata); - - const int totpoint = pointcloud_dst->totpoint = pointcloud_src->totpoint; - CustomData_init_from(&pointcloud_src->pdata, &pointcloud_dst->pdata, CD_MASK_ALL, totpoint); - + pointcloud_dst->totpoint = pointcloud_src->totpoint; + pointcloud_dst->attribute_storage.wrap() = std::move(pointcloud_src->attribute_storage.wrap()); pointcloud_dst->runtime->bounds_cache = pointcloud_src->runtime->bounds_cache; pointcloud_dst->runtime->bounds_with_radius_cache = pointcloud_src->runtime->bounds_with_radius_cache; @@ -344,7 +350,7 @@ std::optional PointCloud::material_index_max() const void PointCloud::count_memory(blender::MemoryCounter &memory) const { - CustomData_count_memory(this->pdata, this->totpoint, memory); + this->attribute_storage.wrap().count_memory(memory); } blender::bke::AttributeAccessor PointCloud::attributes() const diff --git a/source/blender/blenkernel/intern/pointcloud_attributes.cc b/source/blender/blenkernel/intern/pointcloud_attributes.cc index e3fa4746e0d..79d8dfae207 100644 --- a/source/blender/blenkernel/intern/pointcloud_attributes.cc +++ b/source/blender/blenkernel/intern/pointcloud_attributes.cc @@ -4,86 +4,194 @@ #include "DNA_pointcloud_types.h" +#include "BKE_attribute_legacy_convert.hh" +#include "BKE_attribute_storage.hh" #include "BKE_pointcloud.hh" #include "attribute_access_intern.hh" namespace blender::bke { -static void tag_component_positions_changed(void *owner) +static void tag_position_changed(void *owner) { PointCloud &points = *static_cast(owner); points.tag_positions_changed(); } -static void tag_component_radius_changed(void *owner) +static void tag_radius_changed(void *owner) { PointCloud &points = *static_cast(owner); points.tag_radii_changed(); } -/** - * 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 GeometryAttributeProviders create_attribute_providers_for_pointcloud() -{ - static CustomDataAccessInfo point_access = { - [](void *owner) -> CustomData * { - PointCloud *pointcloud = static_cast(owner); - return &pointcloud->pdata; - }, - [](const void *owner) -> const CustomData * { - const PointCloud *pointcloud = static_cast(owner); - return &pointcloud->pdata; - }, - [](const void *owner) -> int { - const PointCloud *pointcloud = static_cast(owner); - return pointcloud->totpoint; - }}; +using UpdateOnChange = void (*)(void *owner); - static BuiltinCustomDataLayerProvider position("position", - AttrDomain::Point, - CD_PROP_FLOAT3, - BuiltinAttributeProvider::NonDeletable, - point_access, - tag_component_positions_changed); - static BuiltinCustomDataLayerProvider radius("radius", - AttrDomain::Point, - CD_PROP_FLOAT, - BuiltinAttributeProvider::Deletable, - point_access, - tag_component_radius_changed); - static BuiltinCustomDataLayerProvider id("id", - AttrDomain::Point, - CD_PROP_INT32, - BuiltinAttributeProvider::Deletable, - point_access, - nullptr); - static CustomDataAttributeProvider point_custom_data(AttrDomain::Point, point_access); - return GeometryAttributeProviders({&position, &radius, &id}, {&point_custom_data}); +static const auto &changed_tags() +{ + static Map attributes{{"position", tag_position_changed}, + {"radius", tag_radius_changed}}; + return attributes; } -static AttributeAccessorFunctions get_pointcloud_accessor_functions() +namespace { + +struct BuiltinInfo { + bke::AttrDomain domain; + bke::AttrType type; + GPointer default_value = {}; + AttributeValidator validator = {}; + bool deletable = false; + BuiltinInfo(bke::AttrDomain domain, bke::AttrType type) : domain(domain), type(type) {} +}; + +} // namespace + +static const auto &builtin_attributes() { - static const GeometryAttributeProviders providers = create_attribute_providers_for_pointcloud(); - AttributeAccessorFunctions fn = - attribute_accessor_functions::accessor_functions_for_providers(); - fn.domain_size = [](const void *owner, const AttrDomain domain) { - if (owner == nullptr) { - return 0; + static auto attributes = []() { + Map map; + + BuiltinInfo position(bke::AttrDomain::Point, bke::AttrType::Float3); + position.deletable = false; + map.add_new("position", std::move(position)); + + BuiltinInfo radius(bke::AttrDomain::Point, bke::AttrType::Float); + map.add_new("radius", std::move(radius)); + + return map; + }(); + return attributes; +} + +static GAttributeReader attribute_to_reader(const Attribute &attribute, + const AttrDomain domain, + const int64_t domain_size) +{ + const CPPType &cpp_type = attribute_type_to_cpp_type(attribute.data_type()); + switch (attribute.storage_type()) { + case AttrStorageType::Array: { + const auto &data = std::get(attribute.data()); + return GAttributeReader{GVArray::ForSpan(GSpan(cpp_type, data.data, data.size)), + domain, + data.sharing_info.get()}; } - const PointCloud &pointcloud = *static_cast(owner); - switch (domain) { - case AttrDomain::Point: - return pointcloud.totpoint; - default: - return 0; + case AttrStorageType::Single: { + const auto &data = std::get(attribute.data()); + return GAttributeReader{GVArray::ForSingleRef(cpp_type, domain_size, data.value), + domain, + data.sharing_info.get()}; } - }; + } + BLI_assert_unreachable(); + return {}; +} + +static GAttributeWriter attribute_to_writer(PointCloud &pointcloud, + const int64_t domain_size, + Attribute &attribute) +{ + const CPPType &cpp_type = attribute_type_to_cpp_type(attribute.data_type()); + switch (attribute.storage_type()) { + case AttrStorageType::Array: { + auto &data = std::get(attribute.data_for_write()); + BLI_assert(data.size == domain_size); + + std::function tag_modified_fn; + if (const UpdateOnChange update_fn = changed_tags().lookup_default(attribute.name(), + nullptr)) + { + tag_modified_fn = [pointcloud = &pointcloud, update_fn]() { update_fn(pointcloud); }; + }; + + return GAttributeWriter{ + GVMutableArray::ForSpan(GMutableSpan(cpp_type, data.data, domain_size)), + attribute.domain(), + std::move(tag_modified_fn)}; + } + case AttrStorageType::Single: { + /* Not yet implemented. */ + BLI_assert_unreachable(); + } + } + BLI_assert_unreachable(); + return {}; +} + +static Attribute::DataVariant attribute_init_to_data(const bke::AttrType data_type, + const int64_t domain_size, + const AttributeInit &initializer) +{ + switch (initializer.type) { + case AttributeInit::Type::Construct: { + const CPPType &type = bke::attribute_type_to_cpp_type(data_type); + return Attribute::ArrayData::ForConstructed(type, domain_size); + } + case AttributeInit::Type::DefaultValue: { + const CPPType &type = bke::attribute_type_to_cpp_type(data_type); + return Attribute::ArrayData::ForDefaultValue(type, domain_size); + } + case AttributeInit::Type::VArray: { + const auto &init = static_cast(initializer); + const GVArray &varray = init.varray; + BLI_assert(varray.size() == domain_size); + const CPPType &type = varray.type(); + Attribute::ArrayData data; + data.data = MEM_malloc_arrayN_aligned(domain_size, type.size, type.alignment, __func__); + varray.materialize_to_uninitialized(varray.index_range(), data.data); + data.size = domain_size; + data.sharing_info = ImplicitSharingPtr<>(implicit_sharing::info_for_mem_free(data.data)); + return data; + } + case AttributeInit::Type::MoveArray: { + const auto &init = static_cast(initializer); + Attribute::ArrayData data; + data.data = init.data; + data.size = domain_size; + data.sharing_info = ImplicitSharingPtr<>(implicit_sharing::info_for_mem_free(data.data)); + return data; + } + case AttributeInit::Type::Shared: { + const auto &init = static_cast(initializer); + Attribute::ArrayData data; + data.data = const_cast(init.data); + data.size = domain_size; + data.sharing_info = ImplicitSharingPtr<>(init.sharing_info); + data.sharing_info->add_user(); + return data; + } + } + BLI_assert_unreachable(); + return {}; +} + +static constexpr AttributeAccessorFunctions get_pointcloud_accessor_functions() +{ + AttributeAccessorFunctions fn{}; fn.domain_supported = [](const void * /*owner*/, const AttrDomain domain) { return domain == AttrDomain::Point; }; + fn.domain_size = [](const void *owner, const AttrDomain domain) { + return domain == AttrDomain::Point ? static_cast(owner)->totpoint : 0; + }; + fn.builtin_domain_and_type = [](const void * /*owner*/, + const StringRef name) -> std::optional { + const BuiltinInfo *info = builtin_attributes().lookup_ptr(name); + if (!info) { + return std::nullopt; + } + const std::optional cd_type = attr_type_to_custom_data_type(info->type); + BLI_assert(cd_type.has_value()); + return AttributeDomainAndType{info->domain, *cd_type}; + }; + fn.lookup = [](const void *owner, const StringRef name) -> GAttributeReader { + const PointCloud &pointcloud = *static_cast(owner); + const AttributeStorage &storage = pointcloud.attribute_storage.wrap(); + const Attribute *attribute = storage.lookup(name); + if (!attribute) { + return {}; + } + return attribute_to_reader(*attribute, AttrDomain::Point, pointcloud.totpoint); + }; fn.adapt_domain = [](const void * /*owner*/, const GVArray &varray, const AttrDomain from_domain, @@ -93,12 +201,88 @@ static AttributeAccessorFunctions get_pointcloud_accessor_functions() } return GVArray{}; }; + fn.foreach_attribute = [](const void *owner, + const FunctionRef fn, + const AttributeAccessor &accessor) { + const PointCloud &pointcloud = *static_cast(owner); + const AttributeStorage &storage = pointcloud.attribute_storage.wrap(); + storage.foreach_with_stop([&](const Attribute &attribute) { + const auto get_fn = [&]() { + return attribute_to_reader(attribute, AttrDomain::Point, pointcloud.totpoint); + }; + const std::optional cd_type = attr_type_to_custom_data_type( + attribute.data_type()); + BLI_assert(cd_type.has_value()); + AttributeIter iter(attribute.name(), attribute.domain(), *cd_type, get_fn); + iter.is_builtin = builtin_attributes().contains(attribute.name()); + iter.accessor = &accessor; + fn(iter); + return !iter.is_stopped(); + }); + }; + fn.lookup_validator = [](const void * /*owner*/, const StringRef name) -> AttributeValidator { + const BuiltinInfo *info = builtin_attributes().lookup_ptr(name); + if (!info) { + return {}; + } + return info->validator; + }; + fn.lookup_for_write = [](void *owner, const StringRef name) -> GAttributeWriter { + PointCloud &pointcloud = *static_cast(owner); + AttributeStorage &storage = pointcloud.attribute_storage.wrap(); + Attribute *attribute = storage.lookup(name); + if (!attribute) { + return {}; + } + return attribute_to_writer(pointcloud, pointcloud.totpoint, *attribute); + }; + fn.remove = [](void *owner, const StringRef name) -> bool { + PointCloud &pointcloud = *static_cast(owner); + AttributeStorage &storage = pointcloud.attribute_storage.wrap(); + if (const BuiltinInfo *info = builtin_attributes().lookup_ptr(name)) { + if (!info->deletable) { + return false; + } + } + const std::optional fn = changed_tags().lookup_try(name); + const bool removed = storage.remove(name); + if (!removed) { + return false; + } + if (fn) { + (*fn)(owner); + } + return true; + }; + fn.add = [](void *owner, + const StringRef name, + const AttrDomain domain, + const eCustomDataType data_type, + const AttributeInit &initializer) { + PointCloud &pointcloud = *static_cast(owner); + const int domain_size = pointcloud.totpoint; + AttributeStorage &storage = pointcloud.attribute_storage.wrap(); + const std::optional type = custom_data_type_to_attr_type(data_type); + BLI_assert(type.has_value()); + if (const BuiltinInfo *info = builtin_attributes().lookup_ptr(name)) { + if (info->domain != domain || info->type != type) { + return false; + } + } + if (storage.lookup(name)) { + return false; + } + Attribute::DataVariant data = attribute_init_to_data(*type, domain_size, initializer); + storage.add(name, domain, *type, std::move(data)); + return true; + }; + return fn; } const AttributeAccessorFunctions &pointcloud_attribute_accessor_functions() { - static const AttributeAccessorFunctions fn = get_pointcloud_accessor_functions(); + static constexpr AttributeAccessorFunctions fn = get_pointcloud_accessor_functions(); return fn; } diff --git a/source/blender/blenloader/intern/versioning_290.cc b/source/blender/blenloader/intern/versioning_290.cc index 3ab12529f38..f0eb76125e1 100644 --- a/source/blender/blenloader/intern/versioning_290.cc +++ b/source/blender/blenloader/intern/versioning_290.cc @@ -1182,7 +1182,7 @@ void blo_do_versions_290(FileData *fd, Library * /*lib*/, Main *bmain) /* PointCloud attributes. */ LISTBASE_FOREACH (PointCloud *, pointcloud, &bmain->pointclouds) { - do_versions_point_attributes(&pointcloud->pdata); + do_versions_point_attributes(&pointcloud->pdata_legacy); } /* Show outliner mode column by default. */ @@ -1488,7 +1488,7 @@ void blo_do_versions_290(FileData *fd, Library * /*lib*/, Main *bmain) /* PointCloud attributes names. */ LISTBASE_FOREACH (PointCloud *, pointcloud, &bmain->pointclouds) { - do_versions_point_attribute_names(&pointcloud->pdata); + do_versions_point_attribute_names(&pointcloud->pdata_legacy); } /* Cryptomatte render pass */ diff --git a/source/blender/blenloader/intern/versioning_450.cc b/source/blender/blenloader/intern/versioning_450.cc index f5c5794d7b1..21346cb0a49 100644 --- a/source/blender/blenloader/intern/versioning_450.cc +++ b/source/blender/blenloader/intern/versioning_450.cc @@ -16,6 +16,7 @@ #include "DNA_light_types.h" #include "DNA_mesh_types.h" #include "DNA_object_force_types.h" +#include "DNA_pointcloud_types.h" #include "DNA_sequence_types.h" #include "BLI_listbase.h" diff --git a/source/blender/blenloader/intern/versioning_500.cc b/source/blender/blenloader/intern/versioning_500.cc index 56843be44f1..70fe7fb8760 100644 --- a/source/blender/blenloader/intern/versioning_500.cc +++ b/source/blender/blenloader/intern/versioning_500.cc @@ -16,6 +16,7 @@ #include "BLI_string.h" #include "BLI_string_utils.hh" +#include "BKE_attribute_legacy_convert.hh" #include "BKE_main.hh" #include "BKE_mesh_legacy_convert.hh" @@ -96,6 +97,12 @@ void blo_do_versions_500(FileData * /*fd*/, Library * /*lib*/, Main *bmain) } } + if (!MAIN_VERSION_FILE_ATLEAST(bmain, 500, 2)) { + LISTBASE_FOREACH (PointCloud *, pointcloud, &bmain->pointclouds) { + blender::bke::pointcloud_convert_customdata_to_storage(*pointcloud); + } + } + /** * Always bump subversion in BKE_blender_version.h when adding versioning * code here, and wrap it inside a MAIN_VERSION_FILE_ATLEAST check. diff --git a/source/blender/editors/geometry/geometry_attributes.cc b/source/blender/editors/geometry/geometry_attributes.cc index 2da8cf98b26..54cdd34f9b0 100644 --- a/source/blender/editors/geometry/geometry_attributes.cc +++ b/source/blender/editors/geometry/geometry_attributes.cc @@ -16,6 +16,7 @@ #include "BLI_listbase.h" #include "BKE_attribute.hh" +#include "BKE_attribute_legacy_convert.hh" #include "BKE_context.hh" #include "BKE_curves.hh" #include "BKE_customdata.hh" @@ -211,6 +212,22 @@ bool attribute_set_poll(bContext &C, const ID &object_data) CTX_wm_operator_poll_msg_set(&C, "No active attribute"); return false; } + + if (owner.type() == AttributeOwnerType::PointCloud) { + PointCloud &pointcloud = *owner.get_pointcloud(); + bke::AttributeAccessor attributes = pointcloud.attributes(); + std::optional meta_data = attributes.lookup_meta_data(*name); + if (!meta_data) { + CTX_wm_operator_poll_msg_set(&C, "No active attribute"); + return false; + } + if (ELEM(meta_data->data_type, CD_PROP_STRING, CD_PROP_FLOAT4X4, CD_PROP_QUATERNION)) { + CTX_wm_operator_poll_msg_set(&C, "The active attribute has an unsupported type"); + return false; + } + return true; + } + const CustomDataLayer *layer = BKE_attribute_search( owner, *name, CD_MASK_PROP_ALL, ATTR_DOMAIN_MASK_ALL); if (ELEM(layer->type, CD_PROP_STRING, CD_PROP_FLOAT4X4, CD_PROP_QUATERNION)) { @@ -281,6 +298,32 @@ static wmOperatorStatus geometry_attribute_add_exec(bContext *C, wmOperator *op) eCustomDataType type = eCustomDataType(RNA_enum_get(op->ptr, "data_type")); bke::AttrDomain domain = bke::AttrDomain(RNA_enum_get(op->ptr, "domain")); AttributeOwner owner = AttributeOwner::from_id(id); + + if (owner.type() == AttributeOwnerType::PointCloud) { + PointCloud &pointcloud = *owner.get_pointcloud(); + bke::MutableAttributeAccessor accessor = pointcloud.attributes_for_write(); + if (!accessor.domain_supported(bke::AttrDomain(domain))) { + BKE_report(op->reports, RPT_ERROR, "Attribute domain not supported by this geometry type"); + return OPERATOR_CANCELLED; + } + bke::AttributeStorage &attributes = pointcloud.attribute_storage.wrap(); + const int domain_size = accessor.domain_size(bke::AttrDomain(domain)); + + const CPPType &cpp_type = *bke::custom_data_type_to_cpp_type(type); + bke::Attribute &attr = attributes.add( + attributes.unique_name_calc(name), + bke::AttrDomain(domain), + *bke::custom_data_type_to_attr_type(type), + bke::Attribute::ArrayData::ForDefaultValue(cpp_type, domain_size)); + + BKE_attributes_active_set(owner, attr.name()); + + DEG_id_tag_update(id, ID_RECALC_GEOMETRY); + WM_main_add_notifier(NC_GEOM | ND_DATA, id); + + return OPERATOR_FINISHED; + } + CustomDataLayer *layer = BKE_attribute_new(owner, name, type, domain, op->reports); if (layer == nullptr) { diff --git a/source/blender/editors/geometry/node_group_operator.cc b/source/blender/editors/geometry/node_group_operator.cc index 8e9314fb7c2..1cc7019c8a8 100644 --- a/source/blender/editors/geometry/node_group_operator.cc +++ b/source/blender/editors/geometry/node_group_operator.cc @@ -321,7 +321,7 @@ static void store_result_geometry(const bContext &C, PointCloud *new_points = geometry.get_component_for_write().release(); if (!new_points) { - CustomData_free(&points.pdata); + new_points->attribute_storage.wrap() = {}; points.totpoint = 0; break; } diff --git a/source/blender/editors/object/object_add.cc b/source/blender/editors/object/object_add.cc index 57a78bbb96c..13132729653 100644 --- a/source/blender/editors/object/object_add.cc +++ b/source/blender/editors/object/object_add.cc @@ -2138,7 +2138,6 @@ static wmOperatorStatus object_pointcloud_add_exec(bContext *C, wmOperator *op) Object *object = add_type(C, OB_POINTCLOUD, nullptr, loc, rot, false, local_view_bits); PointCloud &pointcloud = *static_cast(object->data); pointcloud.totpoint = 400; - CustomData_realloc(&pointcloud.pdata, 0, pointcloud.totpoint); bke::MutableAttributeAccessor attributes = pointcloud.attributes_for_write(); bke::SpanAttributeWriter position = attributes.lookup_or_add_for_write_only_span( diff --git a/source/blender/editors/pointcloud/intern/undo.cc b/source/blender/editors/pointcloud/intern/undo.cc index 0ec39e9c217..e2ef7e41137 100644 --- a/source/blender/editors/pointcloud/intern/undo.cc +++ b/source/blender/editors/pointcloud/intern/undo.cc @@ -8,8 +8,8 @@ #include "BLI_task.hh" +#include "BKE_attribute_storage.hh" #include "BKE_context.hh" -#include "BKE_customdata.hh" #include "BKE_main.hh" #include "BKE_object.hh" #include "BKE_pointcloud.hh" @@ -38,7 +38,7 @@ namespace undo { struct StepObject { UndoRefID_Object obedit_ref = {}; - CustomData custom_data = {}; + bke::AttributeStorage attribute_storage; int totpoint = 0; /* Store the bounds caches because they are small. */ SharedCache> bounds_cache; @@ -67,10 +67,9 @@ static bool step_encode(bContext *C, Main *bmain, UndoStep *us_p) for (const int i : range) { Object *ob = objects[i]; StepObject &object = us->objects[i]; - PointCloud &pointcloud = *static_cast(ob->data); + const PointCloud &pointcloud = *static_cast(ob->data); object.obedit_ref.ptr = ob; - CustomData_init_from( - &pointcloud.pdata, &object.custom_data, CD_MASK_ALL, pointcloud.totpoint); + object.attribute_storage.wrap() = pointcloud.attribute_storage.wrap(); object.bounds_cache = pointcloud.runtime->bounds_cache; object.bounds_with_radius_cache = pointcloud.runtime->bounds_with_radius_cache; object.totpoint = pointcloud.totpoint; @@ -101,12 +100,22 @@ static void step_decode( for (const StepObject &object : us->objects) { PointCloud &pointcloud = *static_cast(object.obedit_ref.ptr->data); - const bool positions_changed = - CustomData_get_layer_named(&pointcloud.pdata, CD_PROP_FLOAT3, "position") != - CustomData_get_layer_named(&object.custom_data, CD_PROP_FLOAT3, "position"); - CustomData_free(&pointcloud.pdata); - CustomData_init_from(&object.custom_data, &pointcloud.pdata, CD_MASK_ALL, object.totpoint); + const bool positions_changed = [&]() { + const bke::Attribute *attr_a = pointcloud.attribute_storage.wrap().lookup("position"); + const bke::Attribute *attr_b = object.attribute_storage.wrap().lookup("position"); + if (!attr_b && !attr_a) { + return false; + } + if (!attr_a || !attr_b) { + return true; + } + return std::get(attr_a->data()).data != + std::get(attr_b->data()).data; + }(); + + pointcloud.attribute_storage.wrap() = object.attribute_storage.wrap(); + pointcloud.totpoint = object.totpoint; pointcloud.runtime->bounds_cache = object.bounds_cache; pointcloud.runtime->bounds_with_radius_cache = object.bounds_with_radius_cache; @@ -127,9 +136,6 @@ static void step_decode( static void step_free(UndoStep *us_p) { PointCloudUndoStep *us = reinterpret_cast(us_p); - for (StepObject &object : us->objects) { - CustomData_free(&object.custom_data); - } us->objects.~Array(); } diff --git a/source/blender/geometry/intern/randomize.cc b/source/blender/geometry/intern/randomize.cc index 7e9b638b2ff..02b7a04104d 100644 --- a/source/blender/geometry/intern/randomize.cc +++ b/source/blender/geometry/intern/randomize.cc @@ -11,6 +11,7 @@ #include "DNA_mesh_types.h" #include "DNA_pointcloud_types.h" +#include "BKE_attribute_storage.hh" #include "BKE_curves.hh" #include "BKE_customdata.hh" #include "BKE_geometry_set.hh" @@ -81,6 +82,31 @@ static void reorder_customdata(CustomData &data, const Span new_by_old_map) data = new_data; } +static void reorder_attribute_domain(bke::AttributeStorage &data, + const bke::AttrDomain domain, + const Span new_by_old_map) +{ + data.foreach([&](bke::Attribute &attr) { + if (attr.domain() != domain) { + return; + } + const CPPType &type = bke::attribute_type_to_cpp_type(attr.data_type()); + switch (attr.storage_type()) { + case bke::AttrStorageType::Array: { + const auto &data = std::get(attr.data()); + auto new_data = bke::Attribute::ArrayData::ForConstructed(type, new_by_old_map.size()); + bke::attribute_math::gather(GSpan(type, data.data, data.size), + new_by_old_map, + GMutableSpan(type, new_data.data, new_data.size)); + attr.data_for_write() = std::move(new_data); + } + case bke::AttrStorageType::Single: { + return; + } + } + }); +} + void debug_randomize_vert_order(Mesh *mesh) { if (mesh == nullptr || !use_debug_randomization()) { @@ -183,8 +209,8 @@ void debug_randomize_point_order(PointCloud *pointcloud) const int seed = seed_from_pointcloud(*pointcloud); const Array new_by_old_map = get_permutation(pointcloud->totpoint, seed); - - reorder_customdata(pointcloud->pdata, new_by_old_map); + reorder_attribute_domain( + pointcloud->attribute_storage.wrap(), bke::AttrDomain::Point, new_by_old_map); pointcloud->tag_positions_changed(); pointcloud->tag_radii_changed(); diff --git a/source/blender/makesdna/DNA_pointcloud_types.h b/source/blender/makesdna/DNA_pointcloud_types.h index 66a096f8eb3..8d059be9ac8 100644 --- a/source/blender/makesdna/DNA_pointcloud_types.h +++ b/source/blender/makesdna/DNA_pointcloud_types.h @@ -51,14 +51,11 @@ typedef struct PointCloud { /* Geometry */ int totpoint; - /** - * Storage for generic attributes. Currently unused at runtime, but used for forward - * compatibility when reading files (see #122398). - */ + /** Storage for generic attributes. */ struct AttributeStorage attribute_storage; /* Custom Data */ - struct CustomData pdata; + struct CustomData pdata_legacy; /** Set to -1 when none is active. */ int attributes_active_index; int _pad4; diff --git a/source/blender/makesdna/intern/dna_rename_defs.h b/source/blender/makesdna/intern/dna_rename_defs.h index 690194786d7..c3473ba0df9 100644 --- a/source/blender/makesdna/intern/dna_rename_defs.h +++ b/source/blender/makesdna/intern/dna_rename_defs.h @@ -182,6 +182,7 @@ DNA_STRUCT_RENAME_MEMBER(ParticleSettings, dup_group, instance_collection) DNA_STRUCT_RENAME_MEMBER(ParticleSettings, dup_ob, instance_object) DNA_STRUCT_RENAME_MEMBER(ParticleSettings, dupliweights, instance_weights) DNA_STRUCT_RENAME_MEMBER(ParticleSettings, ren_child_nbr, child_render_percent) +DNA_STRUCT_RENAME_MEMBER(PointCloud, pdata, pdata_legacy) DNA_STRUCT_RENAME_MEMBER(RenderData, bake_filter, bake_margin) DNA_STRUCT_RENAME_MEMBER(RenderData, blurfac, motion_blur_shutter) DNA_STRUCT_RENAME_MEMBER(RigidBodyWorld, steps_per_second, substeps_per_frame) diff --git a/source/blender/makesrna/intern/rna_attribute.cc b/source/blender/makesrna/intern/rna_attribute.cc index 0bee0378ad7..0937deaed3e 100644 --- a/source/blender/makesrna/intern/rna_attribute.cc +++ b/source/blender/makesrna/intern/rna_attribute.cc @@ -73,6 +73,20 @@ const EnumPropertyItem rna_enum_attribute_type_with_auto_items[] = { {0, nullptr, 0, nullptr, nullptr}, }; +static const EnumPropertyItem rna_enum_attr_storage_type[] = { + {int(blender::bke::AttrStorageType::Array), + "ARRAY", + 0, + "Array", + "Store a value for every element"}, + {int(blender::bke::AttrStorageType::Single), + "SINGLE", + 0, + "Single", + "Store a single value for the entire domain"}, + {0, nullptr, 0, nullptr, nullptr}, +}; + const EnumPropertyItem rna_enum_attribute_domain_items[] = { /* Not implement yet */ // {ATTR_DOMAIN_GEOMETRY, "GEOMETRY", 0, "Geometry", "Attribute on (whole) geometry"}, @@ -171,10 +185,16 @@ const EnumPropertyItem rna_enum_attribute_curves_domain_items[] = { # include "DNA_grease_pencil_types.h" # include "DNA_mesh_types.h" # include "DNA_meshdata_types.h" +# include "DNA_pointcloud_types.h" +# include "BLI_math_color.h" # include "BLI_string.h" +# include "BKE_attribute_legacy_convert.hh" # include "BKE_customdata.hh" +# include "BKE_report.hh" + +# include "RNA_prototypes.hh" # include "DEG_depsgraph.hh" @@ -189,10 +209,10 @@ using blender::StringRef; static AttributeOwner owner_from_attribute_pointer_rna(PointerRNA *ptr) { ID *owner_id = ptr->owner_id; - const CustomDataLayer *layer = static_cast(ptr->data); /* TODO: Because we don't know the path to the `ptr`, we need to look though all possible * candidates and search for the `layer` currently. This should be just a simple lookup. */ if (GS(owner_id->name) == ID_GP) { + const CustomDataLayer *layer = static_cast(ptr->data); GreasePencil *grease_pencil = reinterpret_cast(owner_id); /* First check the layer attributes. */ CustomData *layers_data = &grease_pencil->layers_data; @@ -235,6 +255,13 @@ static AttributeOwner owner_from_pointer_rna(PointerRNA *ptr) static std::optional rna_Attribute_path(const PointerRNA *ptr) { + using namespace blender; + if (GS(ptr->owner_id->name) == ID_PT) { + bke::Attribute *attr = ptr->data_as(); + const std::string escaped_name = BLI_str_escape(attr->name().c_str()); + return fmt::format("attributes[\"{}\"]", escaped_name); + } + const CustomDataLayer *layer = static_cast(ptr->data); char layer_name_esc[sizeof(layer->name) * 2]; BLI_str_escape(layer_name_esc, layer->name, sizeof(layer_name_esc)); @@ -277,21 +304,71 @@ static StructRNA *srna_by_custom_data_layer_type(const eCustomDataType type) static StructRNA *rna_Attribute_refine(PointerRNA *ptr) { + using namespace blender; + if (GS(ptr->owner_id->name) == ID_PT) { + bke::Attribute *attr = ptr->data_as(); + const eCustomDataType data_type = *bke::attr_type_to_custom_data_type(attr->data_type()); + return srna_by_custom_data_layer_type(data_type); + } + CustomDataLayer *layer = static_cast(ptr->data); return srna_by_custom_data_layer_type(eCustomDataType(layer->type)); } +static void rna_Attribute_name_get(PointerRNA *ptr, char *value) +{ + using namespace blender; + AttributeOwner owner = owner_from_attribute_pointer_rna(ptr); + if (owner.type() == AttributeOwnerType::PointCloud) { + const bke::Attribute *attr = ptr->data_as(); + attr->name().copy_unsafe(value); + return; + } + + strcpy(value, ptr->data_as()->name); +} + +static int rna_Attribute_name_length(PointerRNA *ptr) +{ + using namespace blender; + AttributeOwner owner = owner_from_attribute_pointer_rna(ptr); + if (owner.type() == AttributeOwnerType::PointCloud) { + const bke::Attribute *attr = ptr->data_as(); + return attr->name().size(); + } + + const CustomDataLayer *layer = ptr->data_as(); + return strlen(layer->name); +} + static void rna_Attribute_name_set(PointerRNA *ptr, const char *value) { - const CustomDataLayer *layer = (const CustomDataLayer *)ptr->data; + using namespace blender; AttributeOwner owner = owner_from_attribute_pointer_rna(ptr); + if (owner.type() == AttributeOwnerType::PointCloud) { + const bke::Attribute *attr = ptr->data_as(); + BKE_attribute_rename(owner, attr->name(), value, nullptr); + return; + } + + const CustomDataLayer *layer = (const CustomDataLayer *)ptr->data; BKE_attribute_rename(owner, layer->name, value, nullptr); } static int rna_Attribute_name_editable(const PointerRNA *ptr, const char **r_info) { - CustomDataLayer *layer = static_cast(ptr->data); + using namespace blender; AttributeOwner owner = owner_from_attribute_pointer_rna(const_cast(ptr)); + if (owner.type() == AttributeOwnerType::PointCloud) { + bke::Attribute *attr = ptr->data_as(); + if (BKE_attribute_required(owner, attr->name())) { + *r_info = N_("Cannot modify name of required geometry attribute"); + return false; + } + return true; + } + + CustomDataLayer *layer = static_cast(ptr->data); if (BKE_attribute_required(owner, layer->name)) { *r_info = N_("Cannot modify name of required geometry attribute"); return false; @@ -302,10 +379,27 @@ static int rna_Attribute_name_editable(const PointerRNA *ptr, const char **r_inf static int rna_Attribute_type_get(PointerRNA *ptr) { + using namespace blender; + if (GS(ptr->owner_id->name) == ID_PT) { + const bke::Attribute *attr = static_cast(ptr->data); + return *bke::attr_type_to_custom_data_type(attr->data_type()); + } + CustomDataLayer *layer = static_cast(ptr->data); return layer->type; } +static int rna_Attribute_storage_type_get(PointerRNA *ptr) +{ + using namespace blender; + if (GS(ptr->owner_id->name) == ID_PT) { + const bke::Attribute *attr = static_cast(ptr->data); + return int(attr->storage_type()); + } + + return int(bke::AttrStorageType::Array); +} + const EnumPropertyItem *rna_enum_attribute_domain_itemf(const AttributeOwner &owner, bool include_instances, bool *r_free) @@ -368,26 +462,67 @@ static const EnumPropertyItem *rna_Attribute_domain_itemf(bContext * /*C*/, static int rna_Attribute_domain_get(PointerRNA *ptr) { + using namespace blender; AttributeOwner owner = owner_from_attribute_pointer_rna(ptr); + if (owner.type() == AttributeOwnerType::PointCloud) { + const bke::Attribute *attr = static_cast(ptr->data); + return int(attr->domain()); + } + return int(BKE_attribute_domain(owner, static_cast(ptr->data))); } static bool rna_Attribute_is_internal_get(PointerRNA *ptr) { + using namespace blender; + if (GS(ptr->owner_id->name) == ID_PT) { + const bke::Attribute *attr = static_cast(ptr->data); + return !bke::allow_procedural_attribute_access(attr->name()); + } + const CustomDataLayer *layer = (const CustomDataLayer *)ptr->data; return !blender::bke::allow_procedural_attribute_access(layer->name); } static bool rna_Attribute_is_required_get(PointerRNA *ptr) { - const CustomDataLayer *layer = (const CustomDataLayer *)ptr->data; + using namespace blender; AttributeOwner owner = owner_from_attribute_pointer_rna(ptr); + if (owner.type() == AttributeOwnerType::PointCloud) { + const bke::Attribute *attr = static_cast(ptr->data); + return BKE_attribute_required(owner, attr->name()); + } + + const CustomDataLayer *layer = (const CustomDataLayer *)ptr->data; return BKE_attribute_required(owner, layer->name); } static void rna_Attribute_data_begin(CollectionPropertyIterator *iter, PointerRNA *ptr) { + using namespace blender; AttributeOwner owner = owner_from_attribute_pointer_rna(ptr); + if (owner.type() == AttributeOwnerType::PointCloud) { + PointCloud &pointcloud = *owner.get_pointcloud(); + bke::MutableAttributeAccessor accessor = pointcloud.attributes_for_write(); + + bke::Attribute *attr = ptr->data_as(); + const int domain_size = accessor.domain_size(attr->domain()); + const CPPType &type = bke::attribute_type_to_cpp_type(attr->data_type()); + switch (attr->storage_type()) { + case bke::AttrStorageType::Array: { + const auto &data = std::get(attr->data_for_write()); + rna_iterator_array_begin(iter, ptr, data.data, type.size, domain_size, false, nullptr); + break; + } + case bke::AttrStorageType::Single: { + /* TODO: Access to single values is unimplemented for now. */ + iter->valid = false; + break; + } + } + return; + } + CustomDataLayer *layer = (CustomDataLayer *)ptr->data; if (!(CD_TYPE_AS_MASK(layer->type) & CD_MASK_PROP_ALL)) { iter->valid = false; @@ -402,7 +537,13 @@ static void rna_Attribute_data_begin(CollectionPropertyIterator *iter, PointerRN static int rna_Attribute_data_length(PointerRNA *ptr) { + using namespace blender; AttributeOwner owner = owner_from_attribute_pointer_rna(ptr); + if (owner.type() == AttributeOwnerType::PointCloud) { + const PointCloud &pointcloud = *owner.get_pointcloud(); + return pointcloud.totpoint; + } + CustomDataLayer *layer = (CustomDataLayer *)ptr->data; return BKE_attribute_data_length(owner, layer); } @@ -495,7 +636,31 @@ static void rna_StringAttributeValue_s_set(PointerRNA *ptr, const char *value) static PointerRNA rna_AttributeGroupID_new( ID *id, ReportList *reports, const char *name, const int type, const int domain) { + using namespace blender; AttributeOwner owner = AttributeOwner::from_id(id); + if (owner.type() == AttributeOwnerType::PointCloud) { + PointCloud &pointcloud = *owner.get_pointcloud(); + bke::MutableAttributeAccessor accessor = pointcloud.attributes_for_write(); + if (!accessor.domain_supported(AttrDomain(domain))) { + BKE_report(reports, RPT_ERROR, "Attribute domain not supported by this geometry type"); + return PointerRNA_NULL; + } + const int domain_size = accessor.domain_size(AttrDomain(domain)); + + bke::AttributeStorage &attributes = pointcloud.attribute_storage.wrap(); + const CPPType &cpp_type = *bke::custom_data_type_to_cpp_type(eCustomDataType(type)); + bke::Attribute &attr = attributes.add( + attributes.unique_name_calc(name), + AttrDomain(domain), + *bke::custom_data_type_to_attr_type(eCustomDataType(type)), + bke::Attribute::ArrayData::ForDefaultValue(cpp_type, domain_size)); + + DEG_id_tag_update(id, ID_RECALC_GEOMETRY); + WM_main_add_notifier(NC_GEOM | ND_DATA, id); + + return RNA_pointer_create_discrete(id, &RNA_Attribute, &attr); + } + CustomDataLayer *layer = BKE_attribute_new( owner, name, eCustomDataType(type), AttrDomain(domain), reports); @@ -522,7 +687,26 @@ static PointerRNA rna_AttributeGroupID_new( static void rna_AttributeGroupID_remove(ID *id, ReportList *reports, PointerRNA *attribute_ptr) { + using namespace blender; AttributeOwner owner = AttributeOwner::from_id(id); + if (owner.type() == AttributeOwnerType::PointCloud) { + const bke::Attribute *attr = static_cast(attribute_ptr->data); + if (BKE_attribute_required(owner, attr->name())) { + BKE_report(reports, RPT_ERROR, "Attribute is required and can't be removed"); + return; + } + + PointCloud &pointcloud = *owner.get_pointcloud(); + bke::MutableAttributeAccessor accessor = pointcloud.attributes_for_write(); + accessor.remove(attr->name()); + attribute_ptr->invalidate(); + + DEG_id_tag_update(id, ID_RECALC_GEOMETRY); + WM_main_add_notifier(NC_GEOM | ND_DATA, id); + + return; + } + const CustomDataLayer *layer = (const CustomDataLayer *)attribute_ptr->data; BKE_attribute_remove(owner, layer->name, reports); attribute_ptr->invalidate(); @@ -552,6 +736,19 @@ static bool rna_Attributes_noncolor_layer_skip(CollectionPropertyIterator *iter, return !(CD_TYPE_AS_MASK(layer->type) & CD_MASK_COLOR_ALL) || (layer->flag & CD_FLAG_TEMPORARY); } +static bool rna_AttributeStorage_noncolor_skip(CollectionPropertyIterator * /*iter*/, void *data) +{ + using namespace blender; + bke::Attribute *attr = static_cast(data); + if (!(ATTR_DOMAIN_AS_MASK(attr->domain()) & ATTR_DOMAIN_MASK_COLOR)) { + return true; + } + if (!(CD_TYPE_AS_MASK(attr->data_type()) & CD_MASK_COLOR_ALL)) { + return true; + } + return false; +} + /* Attributes are spread over multiple domains in separate CustomData, we use repeated * array iterators to loop over all. */ static void rna_AttributeGroup_next_domain(AttributeOwner &owner, @@ -576,24 +773,47 @@ static void rna_AttributeGroup_next_domain(AttributeOwner &owner, void rna_AttributeGroup_iterator_begin(CollectionPropertyIterator *iter, PointerRNA *ptr) { + using namespace blender; memset(&iter->internal.array, 0, sizeof(iter->internal.array)); AttributeOwner owner = owner_from_pointer_rna(ptr); + if (owner.type() == AttributeOwnerType::PointCloud) { + PointCloud &pointcloud = *owner.get_pointcloud(); + bke::AttributeStorage &storage = pointcloud.attribute_storage.wrap(); + Vector attributes; + storage.foreach([&](bke::Attribute &attr) { attributes.append(&attr); }); + VectorData data = attributes.release(); + rna_iterator_array_begin( + iter, ptr, data.data, sizeof(bke::Attribute *), data.size, true, nullptr); + return; + } + rna_AttributeGroup_next_domain(owner, iter, ptr, rna_Attributes_layer_skip); } void rna_AttributeGroup_iterator_next(CollectionPropertyIterator *iter) { rna_iterator_array_next(iter); + AttributeOwner owner = owner_from_pointer_rna(&iter->parent); + if (owner.type() == AttributeOwnerType::PointCloud) { + return; + } if (!iter->valid) { - AttributeOwner owner = owner_from_pointer_rna(&iter->parent); rna_AttributeGroup_next_domain(owner, iter, &iter->parent, rna_Attributes_layer_skip); } } PointerRNA rna_AttributeGroup_iterator_get(CollectionPropertyIterator *iter) { - /* Refine to the proper type. */ + using namespace blender; + AttributeOwner owner = owner_from_pointer_rna(&iter->parent); + if (owner.type() == AttributeOwnerType::PointCloud) { + bke::Attribute *attr = *static_cast(rna_iterator_array_get(iter)); + const eCustomDataType data_type = *bke::attr_type_to_custom_data_type(attr->data_type()); + StructRNA *type = srna_by_custom_data_layer_type(data_type); + return RNA_pointer_create_with_parent(iter->parent, type, attr); + } + CustomDataLayer *layer = static_cast(rna_iterator_array_get(iter)); StructRNA *type = srna_by_custom_data_layer_type(eCustomDataType(layer->type)); if (type == nullptr) { @@ -630,21 +850,78 @@ PointerRNA rna_AttributeGroup_color_iterator_get(CollectionPropertyIterator *ite return RNA_pointer_create_with_parent(iter->parent, type, layer); } +void rna_AttributeStorage_color_iterator_begin(CollectionPropertyIterator *iter, PointerRNA *ptr) +{ + using namespace blender; + memset(&iter->internal.array, 0, sizeof(iter->internal.array)); + AttributeOwner owner = owner_from_pointer_rna(ptr); + if (owner.type() == AttributeOwnerType::PointCloud) { + PointCloud &pointcloud = *owner.get_pointcloud(); + bke::AttributeStorage &storage = pointcloud.attribute_storage.wrap(); + Vector attributes; + storage.foreach([&](bke::Attribute &attr) { attributes.append(&attr); }); + VectorData data = attributes.release(); + rna_iterator_array_begin(iter, + ptr, + data.data, + sizeof(bke::Attribute *), + data.size, + true, + rna_AttributeStorage_noncolor_skip); + } +} + int rna_AttributeGroup_color_length(PointerRNA *ptr) { + using namespace blender; + if (GS(ptr->owner_id->name) == ID_PT) { + PointCloud &pointcloud = *reinterpret_cast(ptr->owner_id); + bke::AttributeStorage &storage = pointcloud.attribute_storage.wrap(); + int count = 0; + storage.foreach([&](bke::Attribute &attr) { + if (!(ATTR_DOMAIN_AS_MASK(attr.domain()) & ATTR_DOMAIN_MASK_COLOR)) { + return; + } + if (!(CD_TYPE_AS_MASK(attr.data_type()) & CD_MASK_COLOR_ALL)) { + return; + } + count++; + }); + return count; + } AttributeOwner owner = owner_from_pointer_rna(ptr); return BKE_attributes_length(owner, ATTR_DOMAIN_MASK_COLOR, CD_MASK_COLOR_ALL); } int rna_AttributeGroup_length(PointerRNA *ptr) { + using namespace blender; AttributeOwner owner = owner_from_pointer_rna(ptr); + if (owner.type() == AttributeOwnerType::PointCloud) { + PointCloud &pointcloud = *owner.get_pointcloud(); + bke::AttributeStorage &storage = pointcloud.attribute_storage.wrap(); + int count = 0; + storage.foreach([&](bke::Attribute & /*attr*/) { count++; }); + return count; + } return BKE_attributes_length(owner, ATTR_DOMAIN_MASK_ALL, CD_MASK_PROP_ALL); } bool rna_AttributeGroup_lookup_string(PointerRNA *ptr, const char *key, PointerRNA *r_ptr) { + using namespace blender; AttributeOwner owner = owner_from_pointer_rna(ptr); + if (owner.type() == AttributeOwnerType::PointCloud) { + PointCloud &pointcloud = *owner.get_pointcloud(); + bke::AttributeStorage &storage = pointcloud.attribute_storage.wrap(); + bke::Attribute *attr = storage.lookup(key); + if (!attr) { + *r_ptr = PointerRNA_NULL; + return false; + } + rna_pointer_create_with_ancestors(*ptr, &RNA_Attribute, attr, *r_ptr); + return true; + } if (CustomDataLayer *layer = BKE_attribute_search_for_write( owner, key, CD_MASK_PROP_ALL, ATTR_DOMAIN_MASK_ALL)) @@ -665,11 +942,19 @@ static int rna_AttributeGroupID_active_index_get(PointerRNA *ptr) static PointerRNA rna_AttributeGroupID_active_get(PointerRNA *ptr) { + using namespace blender; AttributeOwner owner = AttributeOwner::from_id(ptr->owner_id); const std::optional name = BKE_attributes_active_name_get(owner); if (!name) { return PointerRNA_NULL; } + if (owner.type() == AttributeOwnerType::PointCloud) { + PointCloud &pointcloud = *owner.get_pointcloud(); + bke::AttributeStorage &storage = pointcloud.attribute_storage.wrap(); + bke::Attribute *attr = storage.lookup(*name); + return RNA_pointer_create_with_parent(*ptr, &RNA_Attribute, attr); + } + CustomDataLayer *layer = BKE_attribute_search_for_write( owner, *name, CD_MASK_PROP_ALL, ATTR_DOMAIN_MASK_ALL); return RNA_pointer_create_with_parent(*ptr, &RNA_Attribute, layer); @@ -679,7 +964,14 @@ static void rna_AttributeGroupID_active_set(PointerRNA *ptr, PointerRNA attribute_ptr, ReportList * /*reports*/) { + using namespace blender; AttributeOwner owner = AttributeOwner::from_id(ptr->owner_id); + if (owner.type() == AttributeOwnerType::PointCloud) { + bke::Attribute *attr = attribute_ptr.data_as(); + BKE_attributes_active_set(owner, attr->name()); + return; + } + CustomDataLayer *layer = static_cast(attribute_ptr.data); if (layer) { BKE_attributes_active_set(owner, layer->name); @@ -698,9 +990,8 @@ static void rna_AttributeGroupID_active_index_set(PointerRNA *ptr, int value) static void rna_AttributeGroupID_active_index_range( PointerRNA *ptr, int *min, int *max, int *softmin, int *softmax) { - AttributeOwner owner = AttributeOwner::from_id(ptr->owner_id); *min = -1; - *max = BKE_attributes_length(owner, ATTR_DOMAIN_MASK_ALL, CD_MASK_PROP_ALL); + *max = rna_AttributeGroup_length(ptr); *softmin = *min; *softmax = *max; @@ -726,7 +1017,14 @@ static void rna_AttributeGroup_update_active_color(Main * /*bmain*/, static int rna_AttributeGroupID_domain_size(ID *id, const int domain) { + using namespace blender; AttributeOwner owner = AttributeOwner::from_id(id); + if (owner.type() == AttributeOwnerType::PointCloud) { + PointCloud &pointcloud = *owner.get_pointcloud(); + bke::AttributeAccessor attributes = pointcloud.attributes(); + return attributes.domain_size(bke::AttrDomain(domain)); + } + return BKE_attribute_domain_size(owner, domain); } @@ -1475,24 +1773,29 @@ static void rna_def_attribute(BlenderRNA *brna) StructRNA *srna; srna = RNA_def_struct(brna, "Attribute", nullptr); - RNA_def_struct_sdna(srna, "CustomDataLayer"); RNA_def_struct_ui_text(srna, "Attribute", "Geometry attribute"); RNA_def_struct_path_func(srna, "rna_Attribute_path"); RNA_def_struct_refine_func(srna, "rna_Attribute_refine"); prop = RNA_def_property(srna, "name", PROP_STRING, PROP_NONE); - RNA_def_property_string_funcs(prop, nullptr, nullptr, "rna_Attribute_name_set"); + RNA_def_property_string_funcs( + prop, "rna_Attribute_name_get", "rna_Attribute_name_length", "rna_Attribute_name_set"); RNA_def_property_editable_func(prop, "rna_Attribute_name_editable"); RNA_def_property_ui_text(prop, "Name", "Name of the Attribute"); RNA_def_struct_name_property(srna, prop); prop = RNA_def_property(srna, "data_type", PROP_ENUM, PROP_NONE); - RNA_def_property_enum_sdna(prop, nullptr, "type"); RNA_def_property_enum_items(prop, rna_enum_attribute_type_items); RNA_def_property_enum_funcs(prop, "rna_Attribute_type_get", nullptr, nullptr); RNA_def_property_ui_text(prop, "Data Type", "Type of data stored in attribute"); RNA_def_property_clear_flag(prop, PROP_EDITABLE); + prop = RNA_def_property(srna, "storage_type", PROP_ENUM, PROP_NONE); + RNA_def_property_enum_items(prop, rna_enum_attr_storage_type); + RNA_def_property_enum_funcs(prop, "rna_Attribute_storage_type_get", nullptr, nullptr); + RNA_def_property_ui_text(prop, "Storage Type", "Method used to store the data"); + RNA_def_property_clear_flag(prop, PROP_EDITABLE); + prop = RNA_def_property(srna, "domain", PROP_ENUM, PROP_NONE); RNA_def_property_enum_items(prop, rna_enum_attribute_domain_items); RNA_def_property_enum_funcs(