diff options
-rw-r--r-- | source/blender/blenkernel/BKE_idprop.hh | 93 | ||||
-rw-r--r-- | source/blender/blenkernel/CMakeLists.txt | 4 | ||||
-rw-r--r-- | source/blender/blenkernel/intern/idprop_create.cc | 140 | ||||
-rw-r--r-- | source/blender/blenkernel/intern/idprop_serialize.cc | 844 | ||||
-rw-r--r-- | source/blender/blenkernel/intern/idprop_serialize_test.cc | 448 | ||||
-rw-r--r-- | source/blender/editors/asset/intern/asset_indexer.cc | 36 | ||||
-rw-r--r-- | source/blender/makesdna/DNA_ID.h | 7 |
7 files changed, 1567 insertions, 5 deletions
diff --git a/source/blender/blenkernel/BKE_idprop.hh b/source/blender/blenkernel/BKE_idprop.hh new file mode 100644 index 00000000000..782fa9c7404 --- /dev/null +++ b/source/blender/blenkernel/BKE_idprop.hh @@ -0,0 +1,93 @@ +/* + * 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. + */ + +#pragma once + +#include "BKE_idprop.h" + +#include "BLI_serialize.hh" +#include "BLI_span.hh" + +namespace blender::bke::idprop { + +/** + * \brief Convert the given `properties` to `Value` objects for serialization. + * + * `IDP_ID` and `IDP_IDPARRAY` are not supported and will be ignored. + * + * UI data such as max/min will not be serialized. + */ +std::unique_ptr<io::serialize::ArrayValue> convert_to_serialize_values( + const IDProperty *properties); + +/** + * \brief Convert the given `value` to an `IDProperty`. + */ +IDProperty *convert_from_serialize_value(const blender::io::serialize::Value &value); + +class IDPropertyDeleter { + public: + void operator()(IDProperty *id_prop) + { + IDP_FreeProperty(id_prop); + } +}; + +/** \brief Allocate a new IDProperty of type IDP_INT, set its name and value. */ +std::unique_ptr<IDProperty, IDPropertyDeleter> create(StringRefNull prop_name, int32_t value); + +/** \brief Allocate a new IDProperty of type IDP_FLOAT, set its name and value. */ +std::unique_ptr<IDProperty, IDPropertyDeleter> create(StringRefNull prop_name, float value); + +/** \brief Allocate a new IDProperty of type IDP_DOUBLE, set its name and value. */ +std::unique_ptr<IDProperty, IDPropertyDeleter> create(StringRefNull prop_name, double value); + +/** \brief Allocate a new IDProperty of type IDP_STRING, set its name and value. */ +std::unique_ptr<IDProperty, IDPropertyDeleter> create(StringRefNull prop_name, + const StringRefNull value); + +/** + * \brief Allocate a new IDProperty of type IDP_ARRAY and subtype IDP_INT. + * + * \param values: The values will be copied into the IDProperty. + */ +std::unique_ptr<IDProperty, IDPropertyDeleter> create(StringRefNull prop_name, + Span<int32_t> values); + +/** + * \brief Allocate a new IDProperty of type IDP_ARRAY and subtype IDP_FLOAT. + * + * \param values: The values will be copied into the IDProperty. + */ +std::unique_ptr<IDProperty, IDPropertyDeleter> create(StringRefNull prop_name, Span<float> values); + +/** + * \brief Allocate a new IDProperty of type IDP_ARRAY and subtype IDP_DOUBLE. + * + * \param values: The values will be copied into the IDProperty. + */ +std::unique_ptr<IDProperty, IDPropertyDeleter> create(StringRefNull prop_name, + Span<double> values); + +/** + * \brief Allocate a new IDProperty of type IDP_GROUP. + * + * \param prop_name: The name of the newly created property. + */ + +std::unique_ptr<IDProperty, IDPropertyDeleter> create_group(StringRefNull prop_name); + +} // namespace blender::bke::idprop diff --git a/source/blender/blenkernel/CMakeLists.txt b/source/blender/blenkernel/CMakeLists.txt index 7f8a917e002..b5b8f629970 100644 --- a/source/blender/blenkernel/CMakeLists.txt +++ b/source/blender/blenkernel/CMakeLists.txt @@ -160,6 +160,8 @@ set(SRC intern/icons.cc intern/icons_rasterize.c intern/idprop.c + intern/idprop_create.cc + intern/idprop_serialize.cc intern/idprop_utils.c intern/idtype.c intern/image.c @@ -381,6 +383,7 @@ set(SRC BKE_hair.h BKE_icons.h BKE_idprop.h + BKE_idprop.hh BKE_idtype.h BKE_image.h BKE_image_save.h @@ -816,6 +819,7 @@ if(WITH_GTESTS) intern/bpath_test.cc intern/cryptomatte_test.cc intern/fcurve_test.cc + intern/idprop_serialize_test.cc intern/lattice_deform_test.cc intern/layer_test.cc intern/lib_id_test.cc diff --git a/source/blender/blenkernel/intern/idprop_create.cc b/source/blender/blenkernel/intern/idprop_create.cc new file mode 100644 index 00000000000..12f2fdc6a63 --- /dev/null +++ b/source/blender/blenkernel/intern/idprop_create.cc @@ -0,0 +1,140 @@ +/* + * 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. + * + * The Original Code is Copyright (C) 2021 by Blender Foundation. + */ + +#include <type_traits> + +#include "DNA_ID.h" + +#include "BKE_idprop.hh" + +namespace blender::bke::idprop { + +/* -------------------------------------------------------------------- */ +/** \name Create Functions + * \{ */ + +std::unique_ptr<IDProperty, IDPropertyDeleter> create(const StringRefNull prop_name, int32_t value) +{ + IDPropertyTemplate prop_template{0}; + prop_template.i = value; + IDProperty *property = IDP_New(IDP_INT, &prop_template, prop_name.c_str()); + return std::unique_ptr<IDProperty, IDPropertyDeleter>(property); +} + +std::unique_ptr<IDProperty, IDPropertyDeleter> create(const StringRefNull prop_name, float value) +{ + IDPropertyTemplate prop_template{0}; + prop_template.f = value; + IDProperty *property = IDP_New(IDP_FLOAT, &prop_template, prop_name.c_str()); + return std::unique_ptr<IDProperty, IDPropertyDeleter>(property); +} + +std::unique_ptr<IDProperty, IDPropertyDeleter> create(const StringRefNull prop_name, double value) +{ + IDPropertyTemplate prop_template{0}; + prop_template.d = value; + IDProperty *property = IDP_New(IDP_DOUBLE, &prop_template, prop_name.c_str()); + return std::unique_ptr<IDProperty, IDPropertyDeleter>(property); +} + +std::unique_ptr<IDProperty, IDPropertyDeleter> create(const StringRefNull prop_name, + const StringRefNull value) +{ + IDProperty *property = IDP_NewString(value.c_str(), prop_name.c_str(), value.size() + 1); + return std::unique_ptr<IDProperty, IDPropertyDeleter>(property); +} + +static std::unique_ptr<IDProperty, IDPropertyDeleter> array_create(const StringRefNull prop_name, + eIDPropertyType subtype, + size_t array_len) +{ + IDPropertyTemplate prop_template{0}; + prop_template.array.len = array_len; + prop_template.array.type = subtype; + IDProperty *property = IDP_New(IDP_ARRAY, &prop_template, prop_name.c_str()); + return std::unique_ptr<IDProperty, IDPropertyDeleter>(property); +} + +static void array_values_set(IDProperty *property, + const void *values, + size_t values_len, + size_t value_size) +{ + BLI_assert(values); + BLI_assert(property->len == values_len); + memcpy(IDP_Array(property), values, values_len * value_size); +} + +/** + * Create a IDProperty array of `id_property_subtype` and fill it with the given values. + */ +template< + /** C-Primitive type of the array. Can be int32_t, float, double. */ + typename PrimitiveType, + /** Subtype of the ID_ARRAY. Must match PrimitiveType. */ + eIDPropertyType id_property_subtype> +std::unique_ptr<IDProperty, IDPropertyDeleter> create_array(StringRefNull prop_name, + Span<PrimitiveType> values) +{ + static_assert(std::is_same_v<PrimitiveType, int32_t> || std::is_same_v<PrimitiveType, float_t> || + std::is_same_v<PrimitiveType, double>, + "Allowed values for PrimitiveType are int32_t, float and double."); + static_assert(!std::is_same_v<PrimitiveType, int32_t> || id_property_subtype == IDP_INT, + "PrimitiveType and id_property_type do not match (int32_t)."); + static_assert(!std::is_same_v<PrimitiveType, float> || id_property_subtype == IDP_FLOAT, + "PrimitiveType and id_property_type do not match (float)."); + static_assert(!std::is_same_v<PrimitiveType, double> || id_property_subtype == IDP_DOUBLE, + "PrimitiveType and id_property_type do not match (double)."); + + const int64_t values_len = values.size(); + BLI_assert(values_len > 0); + std::unique_ptr<IDProperty, IDPropertyDeleter> property = array_create( + prop_name.c_str(), id_property_subtype, values_len); + array_values_set( + property.get(), static_cast<const void *>(values.data()), values_len, sizeof(PrimitiveType)); + return property; +} + +std::unique_ptr<IDProperty, IDPropertyDeleter> create(const StringRefNull prop_name, + Span<int32_t> values) +{ + return create_array<int32_t, IDP_INT>(prop_name, values); +} + +std::unique_ptr<IDProperty, IDPropertyDeleter> create(const StringRefNull prop_name, + Span<float> values) +{ + return create_array<float, IDP_FLOAT>(prop_name, values); +} + +std::unique_ptr<IDProperty, IDPropertyDeleter> create(const StringRefNull prop_name, + Span<double> values) +{ + return create_array<double, IDP_DOUBLE>(prop_name, values); +} + +std::unique_ptr<IDProperty, IDPropertyDeleter> create_group(const StringRefNull prop_name) +{ + IDPropertyTemplate prop_template{0}; + IDProperty *property = IDP_New(IDP_GROUP, &prop_template, prop_name.c_str()); + return std::unique_ptr<IDProperty, IDPropertyDeleter>(property); +} + +/* \} */ + +} // namespace blender::bke::idprop diff --git a/source/blender/blenkernel/intern/idprop_serialize.cc b/source/blender/blenkernel/intern/idprop_serialize.cc new file mode 100644 index 00000000000..289bc39f4b5 --- /dev/null +++ b/source/blender/blenkernel/intern/idprop_serialize.cc @@ -0,0 +1,844 @@ +/* + * 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. + * + * The Original Code is Copyright (C) 2021 by Blender Foundation. + */ + +#include <optional> + +#include "DNA_ID.h" + +#include "BKE_idprop.hh" + +#include "BLI_listbase.h" + +namespace blender::bke::idprop { +using namespace blender::io::serialize; + +/* Forward declarations */ +class IDPropertySerializer; +class DictionaryEntryParser; +static IDProperty *idprop_from_value(const DictionaryValue &value); +static const IDPropertySerializer &serializer_for(eIDPropertyType property_type); +static const IDPropertySerializer &serializer_for(StringRef idprop_typename); + +/* -------------------------------------------------------------------- */ +/** \name ID property serialization. + * \{ */ + +/* Definitions */ +static constexpr StringRef IDP_KEY_NAME("name"); +static constexpr StringRef IDP_KEY_TYPE("type"); +static constexpr StringRef IDP_KEY_SUBTYPE("subtype"); +static constexpr StringRef IDP_KEY_VALUE("value"); + +static constexpr StringRef IDP_PROPERTY_TYPENAME_STRING("IDP_STRING"); +static constexpr StringRef IDP_PROPERTY_TYPENAME_INT("IDP_INT"); +static constexpr StringRef IDP_PROPERTY_TYPENAME_FLOAT("IDP_FLOAT"); +static constexpr StringRef IDP_PROPERTY_TYPENAME_DOUBLE("IDP_DOUBLE"); +static constexpr StringRef IDP_PROPERTY_TYPENAME_ARRAY("IDP_ARRAY"); +static constexpr StringRef IDP_PROPERTY_TYPENAME_GROUP("IDP_GROUP"); +static constexpr StringRef IDP_PROPERTY_TYPENAME_UNKNOWN("IDP_UNKNOWN"); + +/** + * \brief Base class for (de)serializing IDProperties. + * + * Has a subclass for supported IDProperties and one for unsupported IDProperties. + */ +class IDPropertySerializer { + public: + constexpr IDPropertySerializer() = default; + + /** + * \brief return the type name for (de)serializing. + * Type name is stored in the `type` or `subtype` attribute of the serialized id_property. + */ + virtual std::string type_name() const = 0; + + /** + * \brief return the IDPropertyType for (de)serializing. + */ + virtual std::optional<eIDPropertyType> property_type() const = 0; + + /** + * \brief create dictionary containing the given id_property. + */ + virtual std::shared_ptr<DictionaryValue> idprop_to_dictionary( + const struct IDProperty *id_property) const = 0; + + /** + * \brief convert the entry to an id property. + */ + virtual std::unique_ptr<IDProperty, IDPropertyDeleter> entry_to_idprop( + DictionaryEntryParser &entry_reader) const = 0; + + /** + * \brief Can the serializer be used? + * + * IDP_ID and IDP_IDPARRAY aren't supported for serialization. + */ + virtual bool supports_serializing() const + { + return true; + } + + protected: + /** + * \brief Create a new DictionaryValue instance. + * + * Only fill the dictionary with common attributes (name, type). + */ + std::shared_ptr<DictionaryValue> create_dictionary(const struct IDProperty *id_property) const + { + std::shared_ptr<DictionaryValue> result = std::make_shared<DictionaryValue>(); + DictionaryValue::Items &attributes = result->elements(); + attributes.append_as(std::pair(IDP_KEY_NAME, new StringValue(id_property->name))); + attributes.append_as(std::pair(IDP_KEY_TYPE, new StringValue(type_name()))); + return result; + } +}; + +/** + * \brief Helper class for parsing DictionaryValues. + */ +struct DictionaryEntryParser { + const DictionaryValue::Lookup lookup; + + public: + explicit DictionaryEntryParser(const DictionaryValue &value) : lookup(value.create_lookup()) + { + } + + std::optional<eIDPropertyType> get_type() const + { + return get_id_property_type(IDP_KEY_TYPE); + } + + std::optional<eIDPropertyType> get_subtype() const + { + return get_id_property_type(IDP_KEY_SUBTYPE); + } + + std::optional<std::string> get_name() const + { + return get_string(IDP_KEY_NAME); + } + + std::optional<std::string> get_string_value() const + { + return get_string(IDP_KEY_VALUE); + } + + std::optional<int32_t> get_int_value() const + { + return get_int(IDP_KEY_VALUE); + } + + std::optional<float> get_float_value() const + { + return get_float(IDP_KEY_VALUE); + } + + std::optional<double> get_double_value() const + { + return get_double(IDP_KEY_VALUE); + } + + const ArrayValue *get_array_value() const + { + return get_array(IDP_KEY_VALUE); + } + + std::optional<Vector<int32_t>> get_array_int_value() const + { + return get_array_primitive<int32_t, IntValue>(IDP_KEY_VALUE); + } + + std::optional<Vector<float>> get_array_float_value() const + { + return get_array_primitive<float, DoubleValue>(IDP_KEY_VALUE); + } + + std::optional<Vector<double>> get_array_double_value() const + { + return get_array_primitive<double, DoubleValue>(IDP_KEY_VALUE); + } + + private: + std::optional<std::string> get_string(StringRef key) const + { + const DictionaryValue::LookupValue *value_ptr = lookup.lookup_ptr(key); + if (value_ptr == nullptr) { + return std::nullopt; + } + const DictionaryValue::LookupValue &value = *value_ptr; + + if (value->type() != eValueType::String) { + return std::nullopt; + } + + return value->as_string_value()->value(); + } + + const ArrayValue *get_array(StringRef key) const + { + const DictionaryValue::LookupValue *value_ptr = lookup.lookup_ptr(key); + if (value_ptr == nullptr) { + return nullptr; + } + const DictionaryValue::LookupValue &value = *value_ptr; + + if (value->type() != eValueType::Array) { + return nullptr; + } + + return value->as_array_value(); + } + + std::optional<int32_t> get_int(StringRef key) const + { + const DictionaryValue::LookupValue *value_ptr = lookup.lookup_ptr(key); + if (value_ptr == nullptr) { + return std::nullopt; + } + const DictionaryValue::LookupValue &value = *value_ptr; + + if (value->type() != eValueType::Int) { + return std::nullopt; + } + + return value->as_int_value()->value(); + } + + std::optional<double> get_double(StringRef key) const + { + const DictionaryValue::LookupValue *value_ptr = lookup.lookup_ptr(key); + if (value_ptr == nullptr) { + return std::nullopt; + } + const DictionaryValue::LookupValue &value = *value_ptr; + + if (value->type() != eValueType::Double) { + return std::nullopt; + } + + return value->as_double_value()->value(); + } + + std::optional<float> get_float(StringRef key) const + { + return static_cast<std::optional<float>>(get_double(key)); + } + + template<typename PrimitiveType, typename ValueType> + std::optional<Vector<PrimitiveType>> get_array_primitive(StringRef key) const + { + const DictionaryValue::LookupValue *value_ptr = lookup.lookup_ptr(key); + if (value_ptr == nullptr) { + return std::nullopt; + } + const DictionaryValue::LookupValue &value = *value_ptr; + + if (value->type() != eValueType::Array) { + return std::nullopt; + } + + Vector<PrimitiveType> result; + const ArrayValue::Items &elements = value->as_array_value()->elements(); + for (const ArrayValue::Item &element : elements) { + const ValueType *value_type = static_cast<const ValueType *>(element.get()); + PrimitiveType primitive_value = value_type->value(); + result.append_as(primitive_value); + } + + return result; + } + + std::optional<eIDPropertyType> get_id_property_type(StringRef key) const + { + std::optional<std::string> string_value = get_string(key); + if (!string_value.has_value()) { + return std::nullopt; + } + const IDPropertySerializer &serializer = serializer_for(*string_value); + return serializer.property_type(); + } +}; + +/** \brief IDPSerializer for IDP_STRING. */ +class IDPStringSerializer : public IDPropertySerializer { + public: + constexpr IDPStringSerializer() = default; + + std::string type_name() const override + { + return IDP_PROPERTY_TYPENAME_STRING; + } + + std::optional<eIDPropertyType> property_type() const override + { + return IDP_STRING; + } + + std::shared_ptr<DictionaryValue> idprop_to_dictionary( + const struct IDProperty *id_property) const override + { + std::shared_ptr<DictionaryValue> result = create_dictionary(id_property); + DictionaryValue::Items &attributes = result->elements(); + attributes.append_as(std::pair(IDP_KEY_VALUE, new StringValue(IDP_String(id_property)))); + return result; + } + + std::unique_ptr<IDProperty, IDPropertyDeleter> entry_to_idprop( + DictionaryEntryParser &entry_reader) const override + { + BLI_assert(entry_reader.get_type().value() == IDP_STRING); + std::optional<std::string> name = entry_reader.get_name(); + if (!name.has_value()) { + return nullptr; + } + std::optional<std::string> string_value = entry_reader.get_string_value(); + if (!string_value.has_value()) { + return nullptr; + } + return create(name->c_str(), string_value->c_str()); + } +}; + +/** \brief IDPSerializer for IDP_INT. */ +class IDPIntSerializer : public IDPropertySerializer { + public: + constexpr IDPIntSerializer() = default; + + std::string type_name() const override + { + return IDP_PROPERTY_TYPENAME_INT; + } + + std::optional<eIDPropertyType> property_type() const override + { + return IDP_INT; + } + + std::shared_ptr<DictionaryValue> idprop_to_dictionary( + const struct IDProperty *id_property) const override + { + std::shared_ptr<DictionaryValue> result = create_dictionary(id_property); + DictionaryValue::Items &attributes = result->elements(); + attributes.append_as(std::pair(IDP_KEY_VALUE, new IntValue(IDP_Int(id_property)))); + return result; + } + + std::unique_ptr<IDProperty, IDPropertyDeleter> entry_to_idprop( + DictionaryEntryParser &entry_reader) const override + { + BLI_assert(entry_reader.get_type().value() == IDP_INT); + std::optional<std::string> name = entry_reader.get_name(); + if (!name.has_value()) { + return nullptr; + } + std::optional<int32_t> extracted_value = entry_reader.get_int_value(); + if (!extracted_value.has_value()) { + return nullptr; + } + return create(name->c_str(), *extracted_value); + } +}; + +/** \brief IDPSerializer for IDP_FLOAT. */ +class IDPFloatSerializer : public IDPropertySerializer { + public: + constexpr IDPFloatSerializer() = default; + + std::string type_name() const override + { + return IDP_PROPERTY_TYPENAME_FLOAT; + } + + std::optional<eIDPropertyType> property_type() const override + { + return IDP_FLOAT; + } + + std::shared_ptr<DictionaryValue> idprop_to_dictionary( + const struct IDProperty *id_property) const override + { + std::shared_ptr<DictionaryValue> result = create_dictionary(id_property); + DictionaryValue::Items &attributes = result->elements(); + attributes.append_as(std::pair(IDP_KEY_VALUE, new DoubleValue(IDP_Float(id_property)))); + return result; + } + + std::unique_ptr<IDProperty, IDPropertyDeleter> entry_to_idprop( + DictionaryEntryParser &entry_reader) const override + { + BLI_assert(entry_reader.get_type().value() == IDP_FLOAT); + std::optional<std::string> name = entry_reader.get_name(); + if (!name.has_value()) { + return nullptr; + } + std::optional<float> extracted_value = entry_reader.get_float_value(); + if (!extracted_value.has_value()) { + return nullptr; + } + return create(name->c_str(), *extracted_value); + } +}; + +/** \brief IDPSerializer for IDP_DOUBLE. */ +class IDPDoubleSerializer : public IDPropertySerializer { + public: + constexpr IDPDoubleSerializer() = default; + + std::string type_name() const override + { + return IDP_PROPERTY_TYPENAME_DOUBLE; + } + + std::optional<eIDPropertyType> property_type() const override + { + return IDP_DOUBLE; + } + + std::shared_ptr<DictionaryValue> idprop_to_dictionary( + const struct IDProperty *id_property) const override + { + std::shared_ptr<DictionaryValue> result = create_dictionary(id_property); + DictionaryValue::Items &attributes = result->elements(); + attributes.append_as(std::pair(IDP_KEY_VALUE, new DoubleValue(IDP_Double(id_property)))); + return result; + } + + std::unique_ptr<IDProperty, IDPropertyDeleter> entry_to_idprop( + DictionaryEntryParser &entry_reader) const override + { + BLI_assert(entry_reader.get_type().value() == IDP_DOUBLE); + std::optional<std::string> name = entry_reader.get_name(); + if (!name.has_value()) { + return nullptr; + } + std::optional<double> extracted_value = entry_reader.get_double_value(); + if (!extracted_value.has_value()) { + return nullptr; + } + return create(name->c_str(), *extracted_value); + } +}; + +/** \brief IDPSerializer for IDP_ARRAY. */ +class IDPArraySerializer : public IDPropertySerializer { + public: + constexpr IDPArraySerializer() = default; + + std::string type_name() const override + { + return IDP_PROPERTY_TYPENAME_ARRAY; + } + + std::optional<eIDPropertyType> property_type() const override + { + return IDP_ARRAY; + } + + std::shared_ptr<DictionaryValue> idprop_to_dictionary( + const struct IDProperty *id_property) const override + { + std::shared_ptr<DictionaryValue> result = create_dictionary(id_property); + DictionaryValue::Items &attributes = result->elements(); + const IDPropertySerializer &subtype_serializer = serializer_for( + static_cast<eIDPropertyType>(id_property->subtype)); + attributes.append_as( + std::pair(IDP_KEY_SUBTYPE, new StringValue(subtype_serializer.type_name()))); + + std::shared_ptr<ArrayValue> array = std::make_shared<ArrayValue>(); + switch (static_cast<eIDPropertyType>(id_property->subtype)) { + case IDP_INT: { + int32_t *values = static_cast<int32_t *>(IDP_Array(id_property)); + add_values<int32_t, IntValue>(array.get(), Span<int32_t>(values, id_property->len)); + break; + } + + case IDP_FLOAT: { + float *values = static_cast<float *>(IDP_Array(id_property)); + add_values<float, DoubleValue>(array.get(), Span<float>(values, id_property->len)); + break; + } + + case IDP_DOUBLE: { + double *values = static_cast<double *>(IDP_Array(id_property)); + add_values<double, DoubleValue>(array.get(), Span<double>(values, id_property->len)); + break; + } + + case IDP_GROUP: { + IDProperty *values = static_cast<IDProperty *>(IDP_Array(id_property)); + add_values(array.get(), Span<IDProperty>(values, id_property->len)); + break; + } + + default: { + /* IDP_ARRAY only supports IDP_INT, IDP_FLOAT, IDP_DOUBLE and IDP_GROUP. */ + BLI_assert_unreachable(); + break; + } + } + attributes.append_as(std::pair(IDP_KEY_VALUE, std::move(array))); + + return result; + } + + std::unique_ptr<IDProperty, IDPropertyDeleter> entry_to_idprop( + DictionaryEntryParser &entry_reader) const override + { + BLI_assert(entry_reader.get_type().value() == IDP_ARRAY); + std::optional<eIDPropertyType> property_subtype = entry_reader.get_subtype(); + if (!property_subtype.has_value()) { + return nullptr; + } + + switch (*property_subtype) { + case IDP_INT: + return idprop_array_int_from_value(entry_reader); + + case IDP_FLOAT: + return idprop_array_float_from_value(entry_reader); + + case IDP_DOUBLE: + return idprop_array_double_from_value(entry_reader); + + default: + break; + } + return nullptr; + } + + private: + /** Add the given values to array. */ + template</* C-primitive type of the values to add. Possible types are `float`, `int32_t` or + * `double`. */ + typename PrimitiveType, + /* Type of value that can store the PrimitiveType in the Array. */ + typename ValueType> + void add_values(ArrayValue *array, Span<PrimitiveType> values) const + { + ArrayValue::Items &items = array->elements(); + for (PrimitiveType value : values) { + items.append_as(std::make_shared<ValueType>(value)); + } + } + + void add_values(ArrayValue *array, Span<IDProperty> values) const + { + ArrayValue::Items &items = array->elements(); + for (const IDProperty &id_property : values) { + const IDPropertySerializer &value_serializer = serializer_for( + static_cast<eIDPropertyType>(id_property.type)); + if (!value_serializer.supports_serializing()) { + continue; + } + std::shared_ptr<DictionaryValue> value = value_serializer.idprop_to_dictionary(&id_property); + items.append_as(value); + } + } + + std::unique_ptr<IDProperty, IDPropertyDeleter> idprop_array_int_from_value( + DictionaryEntryParser &entry_reader) const + { + BLI_assert(entry_reader.get_type().value() == IDP_ARRAY); + BLI_assert(entry_reader.get_subtype().value() == IDP_INT); + std::optional<std::string> name = entry_reader.get_name(); + if (!name.has_value()) { + return nullptr; + } + std::optional<Vector<int32_t>> extracted_value = entry_reader.get_array_int_value(); + if (!extracted_value.has_value()) { + return nullptr; + } + return create(name->c_str(), *extracted_value); + } + + std::unique_ptr<IDProperty, IDPropertyDeleter> idprop_array_float_from_value( + DictionaryEntryParser &entry_reader) const + { + BLI_assert(entry_reader.get_type().value() == IDP_ARRAY); + BLI_assert(entry_reader.get_subtype().value() == IDP_FLOAT); + std::optional<std::string> name = entry_reader.get_name(); + if (!name.has_value()) { + return nullptr; + } + std::optional<Vector<float>> extracted_value = entry_reader.get_array_float_value(); + if (!extracted_value.has_value()) { + return nullptr; + } + return create(name->c_str(), *extracted_value); + } + + std::unique_ptr<IDProperty, IDPropertyDeleter> idprop_array_double_from_value( + DictionaryEntryParser &entry_reader) const + { + BLI_assert(entry_reader.get_type().value() == IDP_ARRAY); + BLI_assert(entry_reader.get_subtype().value() == IDP_DOUBLE); + std::optional<std::string> name = entry_reader.get_name(); + if (!name.has_value()) { + return nullptr; + } + std::optional<Vector<double>> extracted_value = entry_reader.get_array_double_value(); + if (!extracted_value.has_value()) { + return nullptr; + } + return create(name->c_str(), *extracted_value); + } +}; + +/** \brief IDPSerializer for IDP_GROUP. */ +class IDPGroupSerializer : public IDPropertySerializer { + public: + constexpr IDPGroupSerializer() = default; + + std::string type_name() const override + { + return IDP_PROPERTY_TYPENAME_GROUP; + } + + std::optional<eIDPropertyType> property_type() const override + { + return IDP_GROUP; + } + + std::shared_ptr<DictionaryValue> idprop_to_dictionary( + const struct IDProperty *id_property) const override + { + std::shared_ptr<DictionaryValue> result = create_dictionary(id_property); + DictionaryValue::Items &attributes = result->elements(); + std::shared_ptr<ArrayValue> array = std::make_shared<ArrayValue>(); + ArrayValue::Items &elements = array->elements(); + + LISTBASE_FOREACH (IDProperty *, sub_property, &id_property->data.group) { + + const IDPropertySerializer &sub_property_serializer = serializer_for( + static_cast<eIDPropertyType>(sub_property->type)); + elements.append_as(sub_property_serializer.idprop_to_dictionary(sub_property)); + } + + attributes.append_as(std::pair(IDP_KEY_VALUE, array)); + return result; + } + + std::unique_ptr<IDProperty, IDPropertyDeleter> entry_to_idprop( + DictionaryEntryParser &entry_reader) const override + { + BLI_assert(entry_reader.get_type().value() == IDP_GROUP); + std::optional<std::string> name = entry_reader.get_name(); + if (!name.has_value()) { + return nullptr; + } + + const ArrayValue *array = entry_reader.get_array_value(); + if (array == nullptr) { + return nullptr; + } + + std::unique_ptr<IDProperty, IDPropertyDeleter> result = create_group(name->c_str()); + for (const ArrayValue::Item &element : array->elements()) { + if (element->type() != eValueType::Dictionary) { + continue; + } + const DictionaryValue *subobject = element->as_dictionary_value(); + IDProperty *subproperty = idprop_from_value(*subobject); + IDP_AddToGroup(result.get(), subproperty); + } + + return result; + } +}; + +/** + * \brief Dummy serializer for unknown and unsupported types. + */ +class IDPUnknownSerializer : public IDPropertySerializer { + public: + constexpr IDPUnknownSerializer() = default; + std::string type_name() const override + { + return IDP_PROPERTY_TYPENAME_UNKNOWN; + } + std::optional<eIDPropertyType> property_type() const override + { + return std::nullopt; + } + + std::shared_ptr<DictionaryValue> idprop_to_dictionary( + const struct IDProperty *UNUSED(id_property)) const override + { + BLI_assert_unreachable(); + return nullptr; + } + + bool supports_serializing() const override + { + return false; + } + + std::unique_ptr<IDProperty, IDPropertyDeleter> entry_to_idprop( + DictionaryEntryParser &UNUSED(entry_reader)) const override + { + return nullptr; + } +}; + +/* Serializers are constructed statically to remove construction/destruction. */ +static constexpr IDPStringSerializer IDP_SERIALIZER_STRING; +static constexpr IDPIntSerializer IDP_SERIALIZER_INT; +static constexpr IDPFloatSerializer IDP_SERIALIZER_FLOAT; +static constexpr IDPDoubleSerializer IDP_SERIALIZER_DOUBLE; +static constexpr IDPArraySerializer IDP_SERIALIZER_ARRAY; +static constexpr IDPGroupSerializer IDP_SERIALIZER_GROUP; +static constexpr IDPUnknownSerializer IDP_SERIALIZER_UNKNOWN; + +/** \brief get the serializer for the given property type. */ +static const IDPropertySerializer &serializer_for(eIDPropertyType property_type) +{ + switch (property_type) { + case IDP_STRING: + return IDP_SERIALIZER_STRING; + + case IDP_INT: + return IDP_SERIALIZER_INT; + + case IDP_FLOAT: + return IDP_SERIALIZER_FLOAT; + + case IDP_DOUBLE: + return IDP_SERIALIZER_DOUBLE; + + case IDP_ARRAY: + return IDP_SERIALIZER_ARRAY; + + case IDP_GROUP: + return IDP_SERIALIZER_GROUP; + + default: + BLI_assert_msg(false, "Trying to convert an unsupported/unknown property type to a string"); + return IDP_SERIALIZER_UNKNOWN; + } +} + +/** \brief get serializer for the given typename. */ +static const IDPropertySerializer &serializer_for(StringRef idprop_typename) +{ + if (idprop_typename == IDP_PROPERTY_TYPENAME_STRING) { + return IDP_SERIALIZER_STRING; + } + if (idprop_typename == IDP_PROPERTY_TYPENAME_INT) { + return IDP_SERIALIZER_INT; + } + if (idprop_typename == IDP_PROPERTY_TYPENAME_FLOAT) { + return IDP_SERIALIZER_FLOAT; + } + if (idprop_typename == IDP_PROPERTY_TYPENAME_DOUBLE) { + return IDP_SERIALIZER_DOUBLE; + } + if (idprop_typename == IDP_PROPERTY_TYPENAME_ARRAY) { + return IDP_SERIALIZER_ARRAY; + } + if (idprop_typename == IDP_PROPERTY_TYPENAME_GROUP) { + return IDP_SERIALIZER_GROUP; + } + return IDP_SERIALIZER_UNKNOWN; +} + +/* \} */ + +/* -------------------------------------------------------------------- */ +/** \name IDProperty to Value + * \{ */ +std::unique_ptr<ArrayValue> convert_to_serialize_values(const struct IDProperty *properties) +{ + BLI_assert(properties != nullptr); + std::unique_ptr<ArrayValue> result = std::make_unique<ArrayValue>(); + ArrayValue::Items &elements = result->elements(); + const struct IDProperty *current_property = properties; + while (current_property != nullptr) { + const IDPropertySerializer &serializer = serializer_for( + static_cast<eIDPropertyType>(current_property->type)); + if (serializer.supports_serializing()) { + elements.append_as(serializer.idprop_to_dictionary(current_property)); + } + current_property = current_property->next; + } + + return result; +} + +/* \} */ + +/* -------------------------------------------------------------------- */ +/** \name IDProperty from Value + * \{ */ + +static IDProperty *idprop_from_value(const DictionaryValue &value) +{ + DictionaryEntryParser entry_reader(value); + std::optional<eIDPropertyType> property_type = entry_reader.get_type(); + if (!property_type.has_value()) { + return nullptr; + } + + const IDPropertySerializer &serializer = serializer_for(property_type.value()); + return serializer.entry_to_idprop(entry_reader).release(); +} + +static IDProperty *idprop_from_value(const ArrayValue &value) +{ + IDProperty *result = nullptr; + IDProperty *previous_added = nullptr; + + const ArrayValue::Items &elements = value.elements(); + for (const ArrayValue::Item &element : elements) { + if (element->type() != eValueType::Dictionary) { + continue; + } + const DictionaryValue *object_value = element->as_dictionary_value(); + IDProperty *last_created = idprop_from_value(*object_value); + if (last_created == nullptr) { + continue; + } + + if (result == nullptr) { + result = last_created; + } + + if (previous_added) { + previous_added->next = last_created; + } + last_created->prev = previous_added; + previous_added = last_created; + } + + return result; +} + +IDProperty *convert_from_serialize_value(const Value &value) +{ + if (value.type() != eValueType::Array) { + return nullptr; + } + + return idprop_from_value(*value.as_array_value()); +} + +/* \} */ + +} // namespace blender::bke::idprop diff --git a/source/blender/blenkernel/intern/idprop_serialize_test.cc b/source/blender/blenkernel/intern/idprop_serialize_test.cc new file mode 100644 index 00000000000..eeee3fc2aea --- /dev/null +++ b/source/blender/blenkernel/intern/idprop_serialize_test.cc @@ -0,0 +1,448 @@ +/* + * 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. + * + * The Original Code is Copyright (C) 2021 by Blender Foundation. + */ + +#include "testing/testing.h" + +#include "DNA_ID.h" + +#include "BKE_idprop.hh" + +namespace blender::bke::idprop::tests { + +using namespace blender::io::serialize; + +static void check_container_value(ArrayValue *value) +{ + ASSERT_NE(value, nullptr); + ASSERT_EQ(value->type(), eValueType::Array); + const ArrayValue::Items elements = value->elements(); + EXPECT_FALSE(elements.is_empty()); + EXPECT_EQ(elements.size(), 1); + + const ArrayValue::Item &item = value->elements()[0]; + ASSERT_EQ(item->type(), eValueType::Dictionary); +} + +static void check_object_attribute(const DictionaryValue::Lookup &lookup, + const std::string expected_key, + const std::string expected_value) +{ + EXPECT_TRUE(lookup.contains(expected_key)); + const std::shared_ptr<Value> &element = *lookup.lookup_ptr(expected_key); + ASSERT_EQ(element->type(), eValueType::String); + EXPECT_EQ(element->as_string_value()->value(), expected_value); +} + +static void check_object_attribute(const DictionaryValue::Lookup &lookup, + const std::string expected_key, + const int32_t expected_value) +{ + EXPECT_TRUE(lookup.contains(expected_key)); + const std::shared_ptr<Value> &element = *lookup.lookup_ptr(expected_key); + ASSERT_EQ(element->type(), eValueType::Int); + EXPECT_EQ(element->as_int_value()->value(), expected_value); +} + +static void check_object_attribute(const DictionaryValue::Lookup &lookup, + const std::string expected_key, + const float expected_value) +{ + EXPECT_TRUE(lookup.contains(expected_key)); + const std::shared_ptr<Value> &element = *lookup.lookup_ptr(expected_key); + ASSERT_EQ(element->type(), eValueType::Double); + EXPECT_EQ(element->as_double_value()->value(), expected_value); +} + +static void check_object_attribute(const DictionaryValue::Lookup &lookup, + const std::string expected_key, + const double expected_value) +{ + EXPECT_TRUE(lookup.contains(expected_key)); + const std::shared_ptr<Value> &element = *lookup.lookup_ptr(expected_key); + ASSERT_EQ(element->type(), eValueType::Double); + EXPECT_EQ(element->as_double_value()->value(), expected_value); +} + +static void test_string_to_value(const StringRefNull prop_name, const StringRefNull prop_content) +{ + std::unique_ptr<IDProperty, IDPropertyDeleter> property = create(prop_name, prop_content); + + std::unique_ptr<ArrayValue> value = convert_to_serialize_values(property.get()); + check_container_value(value.get()); + const ArrayValue::Item &item = value->elements()[0]; + const DictionaryValue *object = item->as_dictionary_value(); + const DictionaryValue::Lookup lookup = object->create_lookup(); + + EXPECT_EQ(lookup.size(), 3); + check_object_attribute(lookup, "name", prop_name); + check_object_attribute(lookup, "type", "IDP_STRING"); + check_object_attribute(lookup, "value", prop_content); +} + +TEST(idprop, convert_idp_string_to_value) +{ + test_string_to_value("mykey", "mycontent"); +} + +static void test_int_to_value(const StringRefNull prop_name, int32_t prop_content) +{ + std::unique_ptr<IDProperty, IDPropertyDeleter> property = create(prop_name, prop_content); + + std::unique_ptr<ArrayValue> value = convert_to_serialize_values(property.get()); + check_container_value(value.get()); + const ArrayValue::Item &item = value->elements()[0]; + const DictionaryValue *object = item->as_dictionary_value(); + const DictionaryValue::Lookup lookup = object->create_lookup(); + + EXPECT_EQ(lookup.size(), 3); + check_object_attribute(lookup, "name", prop_name); + check_object_attribute(lookup, "type", "IDP_INT"); + check_object_attribute(lookup, "value", prop_content); +} + +TEST(idprop, convert_idp_int_to_value) +{ + test_int_to_value("mykey", 0); +} + +static void test_float_to_value(const StringRefNull prop_name, float prop_content) +{ + std::unique_ptr<IDProperty, IDPropertyDeleter> property = create(prop_name, prop_content); + + std::unique_ptr<ArrayValue> value = convert_to_serialize_values(property.get()); + check_container_value(value.get()); + const ArrayValue::Item &item = value->elements()[0]; + const DictionaryValue *object = item->as_dictionary_value(); + const DictionaryValue::Lookup lookup = object->create_lookup(); + + EXPECT_EQ(lookup.size(), 3); + check_object_attribute(lookup, "name", prop_name); + check_object_attribute(lookup, "type", "IDP_FLOAT"); + check_object_attribute(lookup, "value", prop_content); +} + +TEST(idprop, convert_idp_float_to_value) +{ + test_float_to_value("mykey", 0.2f); +} + +static void test_double_to_value(const StringRefNull prop_name, double prop_content) +{ + std::unique_ptr<IDProperty, IDPropertyDeleter> property = create(prop_name, prop_content); + + std::unique_ptr<ArrayValue> value = convert_to_serialize_values(property.get()); + check_container_value(value.get()); + const ArrayValue::Item &item = value->elements()[0]; + const DictionaryValue *object = item->as_dictionary_value(); + const DictionaryValue::Lookup lookup = object->create_lookup(); + + EXPECT_EQ(lookup.size(), 3); + check_object_attribute(lookup, "name", prop_name); + check_object_attribute(lookup, "type", "IDP_DOUBLE"); + check_object_attribute(lookup, "value", prop_content); +} + +TEST(idprop, convert_idp_double_to_value) +{ + test_double_to_value("mykey", 0.2); +} + +template<typename PrimitiveType, typename ValueType> +static void test_array_to_value(const StringRefNull prop_name, Vector<PrimitiveType> prop_content) +{ + std::unique_ptr<IDProperty, IDPropertyDeleter> property = create(prop_name, prop_content); + std::unique_ptr<ArrayValue> value = convert_to_serialize_values(property.get()); + + check_container_value(value.get()); + const ArrayValue::Item &item = value->elements()[0]; + const DictionaryValue *object = item->as_dictionary_value(); + const DictionaryValue::Lookup lookup = object->create_lookup(); + + EXPECT_EQ(lookup.size(), 4); + check_object_attribute(lookup, "name", prop_name); + check_object_attribute(lookup, "type", "IDP_ARRAY"); + + const std::shared_ptr<Value> &element = *lookup.lookup_ptr("value"); + const ArrayValue *subvalues = element->as_array_value(); + ASSERT_NE(subvalues, nullptr); + const ArrayValue::Items &subitems = subvalues->elements(); + ASSERT_EQ(subitems.size(), prop_content.size()); + + for (size_t i = 0; i < prop_content.size(); i++) { + EXPECT_EQ(static_cast<ValueType *>(subitems[i].get())->value(), prop_content[i]); + } +} + +TEST(idprop, convert_idp_int_array_to_value) +{ + test_array_to_value<int32_t, IntValue>("my_integer_array", + {-16, -8, -4, -2, -1, 0, 1, 2, 4, 8, 16}); +} + +TEST(idprop, convert_idp_float_array_to_value) +{ + test_array_to_value<float, DoubleValue>( + "my_float_array", {-16.8f, -8.4f, -4.2f, -2.1f, -1.0f, 0.0f, 1.0f, 2.1f, 4.2f, 8.4f, 16.8f}); +} + +TEST(idprop, convert_idp_double_array_to_value) +{ + test_array_to_value<double, DoubleValue>( + "my_double_array", {-16.8, -8.4, -4.2, -2.1, -1.0, 0.0, 1.0, 2.1, 4.2, 8.4, 16.8}); +} + +static std::unique_ptr<Value> parse_json(StringRef input) +{ + std::stringstream is(input); + JsonFormatter json; + std::unique_ptr<Value> value = json.deserialize(is); + return value; +} + +static std::string to_json(const Value &value) +{ + std::stringstream out; + JsonFormatter json; + json.serialize(out, value); + return out.str(); +} + +static void test_idprop(const IDProperty *id_property, + StringRef expected_name, + StringRef expected_value) +{ + ASSERT_NE(id_property, nullptr); + EXPECT_EQ(id_property->type, IDP_STRING); + EXPECT_EQ(id_property->name, expected_name); + EXPECT_EQ(IDP_String(id_property), expected_value); +} + +static void test_idprop(const IDProperty *id_property, + StringRef expected_name, + int32_t expected_value) +{ + ASSERT_NE(id_property, nullptr); + EXPECT_EQ(id_property->type, IDP_INT); + EXPECT_EQ(id_property->name, expected_name); + EXPECT_EQ(IDP_Int(id_property), expected_value); +} + +static void test_idprop(const IDProperty *id_property, + StringRef expected_name, + float expected_value) +{ + ASSERT_NE(id_property, nullptr); + EXPECT_EQ(id_property->type, IDP_FLOAT); + EXPECT_EQ(id_property->name, expected_name); + EXPECT_EQ(IDP_Float(id_property), expected_value); +} + +static void test_idprop(const IDProperty *id_property, + StringRef expected_name, + double expected_value) +{ + ASSERT_NE(id_property, nullptr); + EXPECT_EQ(id_property->type, IDP_DOUBLE); + EXPECT_EQ(id_property->name, expected_name); + EXPECT_EQ(IDP_Double(id_property), expected_value); +} + +static void test_idprop(const IDProperty *id_property, + StringRef expected_name, + const Vector<int32_t> &values) +{ + ASSERT_NE(id_property, nullptr); + EXPECT_EQ(id_property->type, IDP_ARRAY); + EXPECT_EQ(id_property->subtype, IDP_INT); + EXPECT_EQ(id_property->len, values.size()); + EXPECT_EQ(id_property->name, expected_name); + int32_t *idprop_values = static_cast<int32_t *>(IDP_Array(id_property)); + for (int i = 0; i < values.size(); i++) { + EXPECT_EQ(idprop_values[i], values[i]); + } +} + +static void test_idprop(const IDProperty *id_property, + StringRef expected_name, + const Vector<float> &values) +{ + ASSERT_NE(id_property, nullptr); + EXPECT_EQ(id_property->type, IDP_ARRAY); + EXPECT_EQ(id_property->subtype, IDP_FLOAT); + EXPECT_EQ(id_property->len, values.size()); + EXPECT_EQ(id_property->name, expected_name); + float *idprop_values = static_cast<float *>(IDP_Array(id_property)); + for (int i = 0; i < values.size(); i++) { + EXPECT_EQ(idprop_values[i], values[i]); + } +} + +static void test_idprop(const IDProperty *id_property, + StringRef expected_name, + const Vector<double> &values) +{ + ASSERT_NE(id_property, nullptr); + EXPECT_EQ(id_property->type, IDP_ARRAY); + EXPECT_EQ(id_property->subtype, IDP_DOUBLE); + EXPECT_EQ(id_property->len, values.size()); + EXPECT_EQ(id_property->name, expected_name); + double *idprop_values = static_cast<double *>(IDP_Array(id_property)); + for (int i = 0; i < values.size(); i++) { + EXPECT_EQ(idprop_values[i], values[i]); + } +} + +template<typename Type> +static void test_convert_idprop_from_value(StringRef input, + StringRef expected_name, + Type expected_value) +{ + std::unique_ptr<Value> value = parse_json(input); + IDProperty *id_property = convert_from_serialize_value(*value); + test_idprop(id_property, expected_name, expected_value); + IDP_FreeProperty(id_property); +} + +TEST(idprop, convert_idp_string_from_value) +{ + test_convert_idprop_from_value( + R"([{"name":"MyStringName","type":"IDP_STRING","value":"MyString"}])", + "MyStringName", + "MyString"); +} + +TEST(idprop, convert_idp_int_from_value) +{ + test_convert_idprop_from_value( + R"([{"name":"MyIntegerName","type":"IDP_INT","value":42}])", "MyIntegerName", 42); +} + +TEST(idprop, convert_idp_float_from_value) +{ + test_convert_idprop_from_value( + R"([{"name":"MyFloatName","type":"IDP_FLOAT","value":42.24}])", "MyFloatName", 42.24f); +} + +TEST(idprop, convert_idp_double_from_value) +{ + test_convert_idprop_from_value( + R"([{"name":"MyDoubleName","type":"IDP_DOUBLE","value":42.24}])", "MyDoubleName", 42.24); +} + +TEST(idprop, convert_idp_array_int_from_value) +{ + test_convert_idprop_from_value( + R"([{"name":"MyArrayName","type":"IDP_ARRAY","subtype":"IDP_INT","value":[42, 24, 35]}])", + "MyArrayName", + Vector<int32_t>{42, 24, 35}); +} + +TEST(idprop, convert_idp_array_float_from_value) +{ + test_convert_idprop_from_value( + R"([{"name":"MyArrayName","type":"IDP_ARRAY","subtype":"IDP_FLOAT","value":[42.0, 24.4, 35.2]}])", + "MyArrayName", + Vector<float>{42.0f, 24.4f, 35.2f}); +} + +TEST(idprop, convert_idp_array_double_from_value) +{ + test_convert_idprop_from_value( + R"([{"name":"MyArrayName","type":"IDP_ARRAY","subtype":"IDP_DOUBLE","value":[42.43,24.5,35.8]}])", + "MyArrayName", + Vector<double>{42.43, 24.5, 35.8}); +} + +TEST(idprop, convert_idp_multiple_from_value) +{ + static const std::string input_json = + R"([{"name":"MyIntegerName","type":"IDP_INT","value":42},{"name":"MyStringName","type":"IDP_STRING","value":"MyString"},{"name":"MyFloatName","type":"IDP_FLOAT","value":42.24},{"name":"MyDoubleName","type":"IDP_DOUBLE","value":42.24}])"; + std::unique_ptr<Value> value = parse_json(input_json); + + IDProperty *id_property = convert_from_serialize_value(*value); + IDProperty *id_property_1 = id_property; + ASSERT_NE(id_property_1, nullptr); + IDProperty *id_property_2 = id_property_1->next; + ASSERT_NE(id_property_2, nullptr); + IDProperty *id_property_3 = id_property_2->next; + ASSERT_NE(id_property_3, nullptr); + IDProperty *id_property_4 = id_property_3->next; + ASSERT_NE(id_property_4, nullptr); + + EXPECT_EQ(id_property_1->prev, nullptr); + EXPECT_EQ(id_property_2->prev, id_property_1); + EXPECT_EQ(id_property_3->prev, id_property_2); + EXPECT_EQ(id_property_4->prev, id_property_3); + EXPECT_EQ(id_property_4->next, nullptr); + + test_idprop(id_property_1, "MyIntegerName", 42); + test_idprop(id_property_2, "MyStringName", "MyString"); + test_idprop(id_property_3, "MyFloatName", 42.24f); + test_idprop(id_property_4, "MyDoubleName", 42.24); + + IDP_FreeProperty(id_property_1); + IDP_FreeProperty(id_property_2); + IDP_FreeProperty(id_property_3); + IDP_FreeProperty(id_property_4); +} + +TEST(idprop, convert_idp_multiple_roundtrip) +{ + static const std::string input_json = + R"([{"name":"MyIntegerName","type":"IDP_INT","value":42},{"name":"MyStringName","type":"IDP_STRING","value":"MyString"},{"name":"MyFloatName","type":"IDP_FLOAT","value":42.2400016784668},{"name":"MyDoubleName","type":"IDP_DOUBLE","value":42.24}])"; + std::unique_ptr<Value> value = parse_json(input_json); + + IDProperty *id_property = convert_from_serialize_value(*value); + IDProperty *id_property_1 = id_property; + ASSERT_NE(id_property_1, nullptr); + IDProperty *id_property_2 = id_property_1->next; + ASSERT_NE(id_property_2, nullptr); + IDProperty *id_property_3 = id_property_2->next; + ASSERT_NE(id_property_3, nullptr); + IDProperty *id_property_4 = id_property_3->next; + ASSERT_NE(id_property_4, nullptr); + + std::unique_ptr<Value> value_from_id_properties = convert_to_serialize_values(id_property); + std::string output_json = to_json(*value_from_id_properties); + EXPECT_EQ(input_json, output_json); + + IDP_FreeProperty(id_property_1); + IDP_FreeProperty(id_property_2); + IDP_FreeProperty(id_property_3); + IDP_FreeProperty(id_property_4); +} + +TEST(idprop, convert_idp_group_from_value) +{ + static const std::string input_json = + R"([{"name":"AssetMetaData.properties","type":"IDP_GROUP","value":[{"name":"dimensions","type":"IDP_ARRAY","subtype":"IDP_FLOAT","value":[2.0,2.0,2.0]}]}])"; + std::unique_ptr<Value> value = parse_json(input_json); + + IDProperty *id_property = convert_from_serialize_value(*value); + ASSERT_NE(id_property, nullptr); + EXPECT_EQ(id_property->type, IDP_GROUP); + EXPECT_EQ(BLI_listbase_count(&id_property->data.group), 1); + + test_idprop(static_cast<IDProperty *>(id_property->data.group.first), + "dimensions", + Vector<float>{2.0f, 2.0f, 2.0f}); + + IDP_FreeProperty(id_property); +} + +} // namespace blender::bke::idprop::tests diff --git a/source/blender/editors/asset/intern/asset_indexer.cc b/source/blender/editors/asset/intern/asset_indexer.cc index a9c6e3798c9..336ccff900c 100644 --- a/source/blender/editors/asset/intern/asset_indexer.cc +++ b/source/blender/editors/asset/intern/asset_indexer.cc @@ -39,6 +39,7 @@ #include "BKE_appdir.h" #include "BKE_asset.h" #include "BKE_asset_catalog.hh" +#include "BKE_idprop.hh" #include "BKE_preferences.h" #include "CLG_log.h" @@ -49,6 +50,7 @@ namespace blender::ed::asset::index { using namespace blender::io::serialize; using namespace blender::bke; +using namespace blender::bke::idprop; /** * \file asset_indexer.cc @@ -69,12 +71,13 @@ using namespace blender::bke; * "catalog_name": "<catalog_name>", * "description": "<description>", * "author": "<author>", - * "tags": ["<tag>"] + * "tags": ["<tag>"], + * "properties": [..] * }] * } * \endcode * - * NOTE: entries, author, description and tags are optional attributes. + * NOTE: entries, author, description, tags and properties are optional attributes. * * NOTE: File browser uses name and idcode separate. Inside the index they are joined together like * #ID.name. @@ -88,6 +91,7 @@ constexpr StringRef ATTRIBUTE_ENTRIES_CATALOG_NAME("catalog_name"); constexpr StringRef ATTRIBUTE_ENTRIES_DESCRIPTION("description"); constexpr StringRef ATTRIBUTE_ENTRIES_AUTHOR("author"); constexpr StringRef ATTRIBUTE_ENTRIES_TAGS("tags"); +constexpr StringRef ATTRIBUTE_ENTRIES_PROPERTIES("properties"); /** Abstract class for #BlendFile and #AssetIndexFile. */ class AbstractFile { @@ -216,6 +220,20 @@ struct AssetEntryReader { BKE_asset_metadata_tag_add(asset_data, tag_name.c_str()); } } + + void add_properties_to_meta_data(AssetMetaData *asset_data) const + { + BLI_assert(asset_data->properties == nullptr); + const DictionaryValue::LookupValue *value_ptr = lookup.lookup_ptr( + ATTRIBUTE_ENTRIES_PROPERTIES); + if (value_ptr == nullptr) { + return; + } + + const Value &value = *(value_ptr->get()); + IDProperty *properties = convert_from_serialize_value(value); + asset_data->properties = properties; + } }; struct AssetEntryWriter { @@ -274,6 +292,15 @@ struct AssetEntryWriter { tag_items.append_as(new StringValue(tag->name)); } } + + void add_properties(const IDProperty *properties) + { + std::unique_ptr<Value> value = convert_to_serialize_values(properties); + if (value == nullptr) { + return; + } + attributes.append_as(std::pair(ATTRIBUTE_ENTRIES_PROPERTIES, value.release())); + } }; static void init_value_from_file_indexer_entry(AssetEntryWriter &result, @@ -298,6 +325,10 @@ static void init_value_from_file_indexer_entry(AssetEntryWriter &result, result.add_tags(&asset_data.tags); } + if (asset_data.properties != nullptr) { + result.add_properties(asset_data.properties); + } + /* TODO: asset_data.IDProperties */ } @@ -363,6 +394,7 @@ static void init_indexer_entry_from_value(FileIndexerEntry &indexer_entry, asset_data->catalog_id = entry.get_catalog_id(); entry.add_tags_to_meta_data(asset_data); + entry.add_properties_to_meta_data(asset_data); } static int init_indexer_entries_from_value(FileIndexerEntries &indexer_entries, diff --git a/source/blender/makesdna/DNA_ID.h b/source/blender/makesdna/DNA_ID.h index a70befc155b..060b55ffe5c 100644 --- a/source/blender/makesdna/DNA_ID.h +++ b/source/blender/makesdna/DNA_ID.h @@ -147,17 +147,18 @@ typedef struct IDProperty { #define DEFAULT_ALLOC_FOR_NULL_STRINGS 64 /*->type*/ -enum { +typedef enum eIDPropertyType { IDP_STRING = 0, IDP_INT = 1, IDP_FLOAT = 2, + /** Array containing int, floats, doubles or groups. */ IDP_ARRAY = 5, IDP_GROUP = 6, IDP_ID = 7, IDP_DOUBLE = 8, IDP_IDPARRAY = 9, - IDP_NUMTYPES = 10, -}; +} eIDPropertyType; +#define IDP_NUMTYPES 10 /** Used by some IDP utils, keep values in sync with type enum above. */ enum { |