/* SPDX-FileCopyrightText: 2023 Blender Authors * * SPDX-License-Identifier: GPL-2.0-or-later */ #pragma once /** \file * \ingroup bke */ #include #include #include "DNA_ID.h" #include "DNA_ID_enums.h" #include "BLI_compiler_attrs.h" #include "BLI_function_ref.hh" #include "BLI_span.hh" #include "BLI_string_ref.hh" #include "BLI_sys_types.h" #include "BLI_vector_set.hh" namespace blender { struct BlendDataReader; struct BlendWriter; struct ID; struct IDProperty; struct IDPropertyUIData; struct IDPropertyUIDataEnumItem; namespace io::serialize { class ArrayValue; class Value; } // namespace io::serialize union IDPropertyTemplate { int i; float f; double d; struct { const char *str; /** String length (including the null byte): `strlen(str) + 1`. */ int len; /** #eIDPropertySubType */ char subtype; } string; ID *id; struct { int len; /** #eIDPropertyType */ char type; } array; }; /* ----------- Property Array Type ---------- */ /** * \note as a start to move away from the stupid #IDP_New function, * this type has its own allocation function. */ IDProperty *IDP_NewIDPArray(StringRef name) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(); /** * \param flag: the ID creation/copying flags (`LIB_ID_CREATE_...`), same as passed to * #BKE_id_copy_ex. */ IDProperty *IDP_CopyIDPArray(const IDProperty *array, int flag) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(); /** * Shallow copies item. */ void IDP_SetIndexArray(IDProperty *prop, int index, IDProperty *item) ATTR_NONNULL(); IDProperty *IDP_GetIndexArray(IDProperty *prop, int index) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(); void IDP_AppendArray(IDProperty *prop, IDProperty *item); void IDP_ResizeIDPArray(IDProperty *prop, int newlen); /* ----------- Numeric Array Type ----------- */ /** * This function works for strings too! */ void IDP_ResizeArray(IDProperty *prop, int newlen); /* ---------- String Type ------------ */ /** * \param st: The string to assign. * Doesn't need to be null terminated when clamped by `maxncpy`. * \param st_maxncpy: The maximum size of the string (including the `\0` terminator). * When zero, this is the equivalent of passing in `strlen(st) + 1` * \param name: The property name. * \return The new string property. */ IDProperty *IDP_NewStringMaxSize(const char *st, size_t st_maxncpy, StringRef name, eIDPropertyFlag flags = {}) ATTR_WARN_UNUSED_RESULT; IDProperty *IDP_NewString(const char *st, StringRef name, eIDPropertyFlag flags = {}) ATTR_WARN_UNUSED_RESULT; IDProperty *IDP_NewString(blender::StringRef value, blender::StringRef name, eIDPropertyFlag flags = {}) ATTR_WARN_UNUSED_RESULT; /** * \param st: The string to assign. * Doesn't need to be null terminated when clamped by `maxncpy`. * \param st_maxncpy: The maximum size of the string (including the `\0` terminator). * When zero, this is the equivalent of passing in `strlen(st) + 1` */ void IDP_AssignStringMaxSize(IDProperty *prop, const char *st, size_t st_maxncpy) ATTR_NONNULL(); void IDP_AssignString(IDProperty *prop, const char *st) ATTR_NONNULL(); void IDP_FreeString(IDProperty *prop) ATTR_NONNULL(); /*-------- Enum Type -------*/ const IDPropertyUIDataEnumItem *IDP_EnumItemFind(const IDProperty *prop); bool IDP_EnumItemsValidate(const IDPropertyUIDataEnumItem *items, int items_num, void (*error_fn)(const char *)); /*-------- ID Type -------*/ using IDPWalkFunc = void (*)(void *user_data, IDProperty *idp); /** * \param flag: the ID creation/copying flags (`LIB_ID_CREATE_...`), same as passed to * #BKE_id_copy_ex. */ void IDP_AssignID(IDProperty *prop, ID *id, int flag); /*-------- Group Functions -------*/ /** * Sync values from one group to another when values name and types match, * copy the values, else ignore. * * \note Was used for syncing proxies. */ void IDP_SyncGroupValues(IDProperty *dest, const IDProperty *src) ATTR_NONNULL(); void IDP_SyncGroupTypes(IDProperty *dest, const IDProperty *src, bool do_arraylen) ATTR_NONNULL(); /** * Replaces all properties with the same name in a destination group from a source group. */ void IDP_ReplaceGroupInGroup(IDProperty *dest, const IDProperty *src) ATTR_NONNULL(); void IDP_ReplaceInGroup(IDProperty *group, IDProperty *prop) ATTR_NONNULL(); /** * Checks if a property with the same name as prop exists, and if so replaces it. * Use this to preserve order! * * \param flag: the ID creation/copying flags (`LIB_ID_CREATE_...`), same as passed to * #BKE_id_copy_ex. */ void IDP_ReplaceInGroup_ex(IDProperty *group, IDProperty *prop, IDProperty *prop_exist, int flag); /** * If a property is missing in \a dest, add it. * Do it recursively. */ void IDP_MergeGroup(IDProperty *dest, const IDProperty *src, bool do_overwrite) ATTR_NONNULL(); /** * If a property is missing in \a dest, add it. * Do it recursively. * * \param flag: the ID creation/copying flags (`LIB_ID_CREATE_...`), same as passed to * #BKE_id_copy_ex. */ void IDP_MergeGroup_ex(IDProperty *dest, const IDProperty *src, bool do_overwrite, int flag) ATTR_NONNULL(); /** * This function has a sanity check to make sure ID properties with the same name don't * get added to the group. * * The sanity check just means the property is not added to the group if another property * exists with the same name; the client code using ID properties then needs to detect this * (the function that adds new properties to groups, #IDP_AddToGroup, * returns false if a property can't be added to the group, and true if it can) * and free the property. */ bool IDP_AddToGroup(IDProperty *group, IDProperty *prop) ATTR_NONNULL(); /** * \note this does not free the property! * * To free the property, you have to do: * #IDP_FreeProperty(prop); */ void IDP_RemoveFromGroup(IDProperty *group, IDProperty *prop) ATTR_NONNULL(); /** * Removes the property from the group and frees it. */ void IDP_FreeFromGroup(IDProperty *group, IDProperty *prop) ATTR_NONNULL(); IDProperty *IDP_GetPropertyFromGroup(const IDProperty *prop, StringRef name) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(); /** Same as above, but allows the property to be null, in which case null is returned. */ IDProperty *IDP_GetPropertyFromGroup_null(const IDProperty *prop, StringRef name) ATTR_WARN_UNUSED_RESULT; /** * Same as #IDP_GetPropertyFromGroup but ensure the `type` matches. */ IDProperty *IDP_GetPropertyTypeFromGroup(const IDProperty *prop, StringRef name, char type) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(); /*-------- Main Functions --------*/ /** * Get the Group property that contains the user-defined id properties for ID `id`. */ IDProperty *IDP_GetProperties(ID *id) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1); /** * Ensure the Group property that contains the user-defined id properties for ID `id` exists & * return it. */ IDProperty *IDP_EnsureProperties(ID *id) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1); /** * Get the Group property that contains the system-defined id properties for ID `id`. */ IDProperty *IDP_ID_system_properties_get(ID *id) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1); /** * Ensure the Group property that contains the system-defined id properties for ID `id` exists & * return it. */ IDProperty *IDP_ID_system_properties_ensure(ID *id) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1); IDProperty *IDP_CopyProperty(const IDProperty *prop) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(); /** * \param flag: the ID creation/copying flags (`LIB_ID_CREATE_...`), same as passed to * #BKE_id_copy_ex. */ IDProperty *IDP_CopyProperty_ex(const IDProperty *prop, int flag) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(); /** * Copy content from source #IDProperty into destination one, * freeing destination property's content first. */ void IDP_CopyPropertyContent(IDProperty *dst, const IDProperty *src) ATTR_NONNULL(); /** * \param is_strict: When false treat missing items as a match. */ bool IDP_EqualsProperties_ex(const IDProperty *prop1, const IDProperty *prop2, bool is_strict) ATTR_WARN_UNUSED_RESULT; bool IDP_EqualsProperties(const IDProperty *prop1, const IDProperty *prop2) ATTR_WARN_UNUSED_RESULT; /** * Allocate a new IDProperty. * * This function takes three arguments: the ID property type, a union which defines * its initial value, and a name. * * The union is simple to use; see the top of BKE_idprop.h for its definition. * An example of using this function: * * \code{.c} * IDPropertyTemplate val; * IDProperty *group, *idgroup, *color; * group = IDP_New(IDP_GROUP, val, "group1"); // groups don't need a template. * * val.array.len = 4 * val.array.type = IDP_FLOAT; * color = IDP_New(IDP_ARRAY, val, "color1"); * * idgroup = IDP_EnsureProperties(some_id); * IDP_AddToGroup(idgroup, color); * IDP_AddToGroup(idgroup, group); * \endcode * * Note that you MUST either attach the id property to an id property group with * IDP_AddToGroup or MEM_delete the property, doing anything else might result in * a memory leak. */ IDProperty *IDP_New(char type, const IDPropertyTemplate *val, StringRef name, eIDPropertyFlag flags = {}) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(); /* ----------- Allocators for simple types ----------- */ [[nodiscard]] IDProperty *IDP_NewInt(int value, blender::StringRef name, eIDPropertyFlag flags = {}); /** * \note This will free allocated data, all child properties of arrays and groups, and unlink IDs! * But it does not free the actual #IDProperty struct itself. */ void IDP_FreeProperty_ex(IDProperty *prop, bool do_id_user); void IDP_FreeProperty(IDProperty *prop); void IDP_ClearProperty(IDProperty *prop); void IDP_Reset(IDProperty *prop, const IDProperty *reference); #ifndef NDEBUG const IDProperty *_IDP_assert_type(const IDProperty *prop, char ty); const IDProperty *_IDP_assert_type_and_subtype(const IDProperty *prop, char ty, char sub_ty); const IDProperty *_IDP_assert_type_mask(const IDProperty *prop, int ty_mask); #else # define _IDP_assert_type(prop, ty) (prop) # define _IDP_assert_type_and_subtype(prop, ty, sub_ty) (prop) # define _IDP_assert_type_mask(prop, ty_mask) (prop) #endif #define IDP_int_get(prop) (_IDP_assert_type(prop, IDP_INT)->data.val) #define IDP_int_set(prop, value) \ { \ IDProperty *prop_ = (prop); \ BLI_assert(prop_->type == IDP_INT); \ prop_->data.val = value; \ } \ ((void)0) #define IDP_bool_get(prop) ((_IDP_assert_type(prop, IDP_BOOLEAN))->data.val) #define IDP_bool_set(prop, value) \ { \ IDProperty *prop_ = (prop); \ BLI_assert(prop_->type == IDP_BOOLEAN); \ prop_->data.val = value; \ } \ ((void)0) #define IDP_int_or_bool_get(prop) \ (_IDP_assert_type_mask(prop, (1 << IDP_INT) | (1 << IDP_BOOLEAN))->data.val) #define IDP_int_or_bool_set(prop, value) \ { \ IDProperty *prop_ = (prop); \ BLI_assert(ELEM(prop_->type, IDP_INT, IDP_BOOLEAN)); \ prop_->data.val = value; \ } \ ((void)0) #define IDP_float_get(prop) (*(const float *)&(_IDP_assert_type(prop, IDP_FLOAT)->data.val)) #define IDP_float_set(prop, value) \ { \ IDProperty *prop_ = (prop); \ BLI_assert(prop_->type == IDP_FLOAT); \ (*(float *)&(prop_)->data.val) = value; \ } \ ((void)0) #define IDP_double_get(prop) (*(const double *)&(_IDP_assert_type(prop, IDP_DOUBLE)->data.val)) #define IDP_double_set(prop, value) \ { \ IDProperty *prop_ = (prop); \ BLI_assert(prop_->type == IDP_DOUBLE); \ (*(double *)&(prop_)->data.val) = value; \ } \ ((void)0) /** * Use when the type of the array is not known. * * Avoid using this where possible. */ #define IDP_array_voidp_get(prop) (_IDP_assert_type(prop, IDP_ARRAY)->data.pointer) #define IDP_array_int_get(prop) \ static_cast(_IDP_assert_type_and_subtype(prop, IDP_ARRAY, IDP_INT)->data.pointer) #define IDP_array_bool_get(prop) \ static_cast(_IDP_assert_type_and_subtype(prop, IDP_ARRAY, IDP_BOOLEAN)->data.pointer) #define IDP_array_float_get(prop) \ static_cast(_IDP_assert_type_and_subtype(prop, IDP_ARRAY, IDP_FLOAT)->data.pointer) #define IDP_array_double_get(prop) \ static_cast(_IDP_assert_type_and_subtype(prop, IDP_ARRAY, IDP_DOUBLE)->data.pointer) #define IDP_property_array_get(prop) \ static_cast(_IDP_assert_type(prop, IDP_IDPARRAY)->data.pointer) #define IDP_string_get(prop) ((char *)_IDP_assert_type(prop, IDP_STRING)->data.pointer) /* No `IDP_string_set` needed. */ #define IDP_ID_get(prop) ((void)0, ((ID *)_IDP_assert_type(prop, IDP_ID)->data.pointer)) /* No `IDP_ID_set` needed. */ std::optional IDP_group_lookup_string(const IDProperty &group, StringRef name); std::optional IDP_group_lookup_float(const IDProperty &group, StringRef name); std::optional IDP_group_lookup_int(const IDProperty &group, StringRef name); std::optional IDP_group_lookup_bool(const IDProperty &group, StringRef name); std::optional> IDP_group_lookup_float_array(const IDProperty &group, StringRef name, int required_size); /** * Return an int from an #IDProperty with a compatible type. This should be avoided, but * it's sometimes necessary, for example when legacy files have incorrect property types. */ int IDP_coerce_to_int_or_zero(const IDProperty *prop); /** * Return a float from an #IDProperty with a compatible type. This should be avoided, but * it's sometimes necessary, for example when legacy files have incorrect property types. */ float IDP_coerce_to_float_or_zero(const IDProperty *prop); /** * Return a double from an #IDProperty with a compatible type. This should be avoided, but * it's sometimes necessary, for example when legacy files have incorrect property types. */ double IDP_coerce_to_double_or_zero(const IDProperty *prop); /** * Loop through all ID properties in hierarchy of given \a id_property_root included. * * \note Container types (groups and arrays) are processed after applying the callback on them. * * \param type_filter: If not 0, only apply callback on properties of matching types, see * IDP_TYPE_FILTER_ enum in DNA_ID.h. */ void IDP_foreach_property(IDProperty *id_property_root, int type_filter, FunctionRef callback); /* Format IDProperty as strings */ char *IDP_reprN(const IDProperty *prop, uint *r_len); void IDP_repr_fn(const IDProperty *prop, void (*str_append_fn)(void *user_data, const char *str, uint str_len), void *user_data); void IDP_print(const IDProperty *prop); const char *IDP_type_str(eIDPropertyType type, short sub_type); const char *IDP_type_str(const IDProperty *prop); void IDP_BlendWrite(BlendWriter *writer, const IDProperty *prop); void IDP_BlendReadData_impl(BlendDataReader *reader, IDProperty **prop, const char *caller_func_id); #define IDP_BlendDataRead(reader, prop) IDP_BlendReadData_impl(reader, prop, __func__) enum eIDPropertyUIDataType { /** Other properties types that don't support RNA UI data. */ IDP_UI_DATA_TYPE_UNSUPPORTED = -1, /** IDP_INT or IDP_ARRAY with subtype IDP_INT. */ IDP_UI_DATA_TYPE_INT = 0, /** IDP_FLOAT and IDP_DOUBLE or IDP_ARRAY properties with a float or double sub-types. */ IDP_UI_DATA_TYPE_FLOAT = 1, /** IDP_STRING properties. */ IDP_UI_DATA_TYPE_STRING = 2, /** IDP_ID. */ IDP_UI_DATA_TYPE_ID = 3, /** IDP_BOOLEAN or IDP_ARRAY with subtype IDP_BOOLEAN. */ IDP_UI_DATA_TYPE_BOOLEAN = 4, }; bool IDP_ui_data_supported(const IDProperty *prop); eIDPropertyUIDataType IDP_ui_data_type(const IDProperty *prop); void IDP_ui_data_free(IDProperty *prop); /** * Free allocated pointers in the UI data that isn't shared with the UI data in the `other` * argument. Useful for returning early on failure when updating UI data in place, or when * replacing a subset of the UI data's allocated pointers. */ void IDP_ui_data_free_unique_contents(IDPropertyUIData *ui_data, eIDPropertyUIDataType type, const IDPropertyUIData *other); IDPropertyUIData *IDP_ui_data_ensure(IDProperty *prop); IDPropertyUIData *IDP_ui_data_copy(const IDProperty *prop); /** * Convert UI data like default arrays from the old type to the new type as possible. * Takes ownership of the input data; it can return it directly if the types match. */ IDPropertyUIData *IDP_TryConvertUIData(IDPropertyUIData *src, eIDPropertyUIDataType src_type, eIDPropertyUIDataType dst_type); namespace 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 convert_to_serialize_values( const IDProperty *properties); /** * \brief Convert the given `value` (which must be an array) to an `IDProperty`. * \return The first successfully converted property with further array elements in the * `IDProperty.next` chain, or null if none could be converted or \a value is not an array. */ IDProperty *convert_from_serialize_value(const io::serialize::Value &value); class IDPropertyDeleter { public: void operator()(IDProperty *id_prop) { IDP_FreeProperty(id_prop); } }; struct IDPropertyGroupChildrenSet { struct IDPropNameGetter { StringRef operator()(const IDProperty *value) const { return StringRef(value->name); } }; CustomIDVectorSet children; }; /** \brief Allocate a new IDProperty of type IDP_BOOLEAN, set its name and value. */ std::unique_ptr create_bool(StringRef prop_name, bool value, eIDPropertyFlag flags = {}); /** \brief Allocate a new IDProperty of type IDP_INT, set its name and value. */ std::unique_ptr create(StringRef prop_name, int32_t value, eIDPropertyFlag flags = {}); /** \brief Allocate a new IDProperty of type IDP_FLOAT, set its name and value. */ std::unique_ptr create(StringRef prop_name, float value, eIDPropertyFlag flags = {}); /** \brief Allocate a new IDProperty of type IDP_DOUBLE, set its name and value. */ std::unique_ptr create(StringRef prop_name, double value, eIDPropertyFlag flags = {}); /** \brief Allocate a new IDProperty of type IDP_STRING, set its name and value. */ std::unique_ptr create(StringRef prop_name, StringRefNull value, eIDPropertyFlag flags = {}); /** \brief Allocate a new IDProperty of type IDP_ID, set its name and value. */ std::unique_ptr create(StringRef prop_name, ID *value, eIDPropertyFlag flags = {}); /** * \brief Allocate a new IDProperty of type IDP_ARRAY and sub-type IDP_INT. * * \param values: The values will be copied into the IDProperty. */ std::unique_ptr create(StringRef prop_name, Span values, eIDPropertyFlag flags = {}); /** * \brief Allocate a new IDProperty of type IDP_ARRAY and sub-type IDP_FLOAT. * * \param values: The values will be copied into the IDProperty. */ std::unique_ptr create(StringRef prop_name, Span values, eIDPropertyFlag flags = {}); /** * \brief Allocate a new IDProperty of type IDP_ARRAY and sub-type IDP_DOUBLE. * * \param values: The values will be copied into the IDProperty. */ std::unique_ptr create(StringRef prop_name, Span values, eIDPropertyFlag flags = {}); /** * \brief Allocate a new IDProperty of type IDP_GROUP. * * \param prop_name: The name of the newly created property. */ std::unique_ptr create_group(StringRef prop_name, eIDPropertyFlag flags = {}); } // namespace bke::idprop } // namespace blender