tornavis/source/blender/blenkernel/intern/idprop_serialize.cc

829 lines
24 KiB
C++

/* SPDX-FileCopyrightText: 2021 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#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;
struct 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 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 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 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()) == 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 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()) == 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 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()) == 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 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()) == 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 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()) == 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()) == IDP_ARRAY);
BLI_assert(*(entry_reader.get_subtype()) == 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()) == IDP_ARRAY);
BLI_assert(*(entry_reader.get_subtype()) == 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()) == IDP_ARRAY);
BLI_assert(*(entry_reader.get_subtype()) == 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 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()) == 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 IDProperty * /*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 & /*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 IDProperty *properties)
{
BLI_assert(properties != nullptr);
std::unique_ptr<ArrayValue> result = std::make_unique<ArrayValue>();
ArrayValue::Items &elements = result->elements();
const 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);
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