diff --git a/source/blender/blenkernel/BKE_idprop.hh b/source/blender/blenkernel/BKE_idprop.hh index a92636f7b7a..2179faf36c6 100644 --- a/source/blender/blenkernel/BKE_idprop.hh +++ b/source/blender/blenkernel/BKE_idprop.hh @@ -10,6 +10,8 @@ #include +#include "DNA_ID_enums.h" + #include "BLI_compiler_attrs.h" #include "BLI_function_ref.hh" #include "BLI_span.hh" @@ -83,9 +85,12 @@ void IDP_FreeArray(IDProperty *prop); */ IDProperty *IDP_NewStringMaxSize(const char *st, size_t st_maxncpy, - const char *name) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(3); -IDProperty *IDP_NewString(const char *st, const char *name) ATTR_WARN_UNUSED_RESULT - ATTR_NONNULL(2); + const char *name, + eIDPropertyFlag flags = {}) ATTR_WARN_UNUSED_RESULT + ATTR_NONNULL(3); +IDProperty *IDP_NewString(const char *st, + const char *name, + eIDPropertyFlag flags = {}) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(2); /** * \param st: The string to assign. * Doesn't need to be null terminated when clamped by `maxncpy`. @@ -236,7 +241,8 @@ bool IDP_EqualsProperties(const IDProperty *prop1, */ IDProperty *IDP_New(char type, const IDPropertyTemplate *val, - const char *name) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(); + const char *name, + eIDPropertyFlag flags = {}) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(); /** * \note This will free allocated data, all child properties of arrays and groups, and unlink IDs! @@ -390,23 +396,34 @@ class IDPropertyDeleter { }; /** \brief Allocate a new IDProperty of type IDP_BOOLEAN, set its name and value. */ -std::unique_ptr create_bool(StringRefNull prop_name, bool value); +std::unique_ptr create_bool(StringRefNull prop_name, + bool value, + eIDPropertyFlag flags = {}); /** \brief Allocate a new IDProperty of type IDP_INT, set its name and value. */ -std::unique_ptr create(StringRefNull prop_name, int32_t value); +std::unique_ptr create(StringRefNull 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(StringRefNull prop_name, float value); +std::unique_ptr create(StringRefNull prop_name, + float value, + eIDPropertyFlag flags = {}); /** \brief Allocate a new IDProperty of type IDP_DOUBLE, set its name and value. */ -std::unique_ptr create(StringRefNull prop_name, double value); +std::unique_ptr create(StringRefNull prop_name, + double value, + eIDPropertyFlag flags = {}); /** \brief Allocate a new IDProperty of type IDP_STRING, set its name and value. */ std::unique_ptr create(StringRefNull prop_name, - const StringRefNull value); + const StringRefNull value, + eIDPropertyFlag flags = {}); /** \brief Allocate a new IDProperty of type IDP_ID, set its name and value. */ -std::unique_ptr create(StringRefNull prop_name, ID *value); +std::unique_ptr create(StringRefNull prop_name, + ID *value, + eIDPropertyFlag flags = {}); /** * \brief Allocate a new IDProperty of type IDP_ARRAY and sub-type IDP_INT. @@ -414,14 +431,17 @@ std::unique_ptr create(StringRefNull prop_name, I * \param values: The values will be copied into the IDProperty. */ std::unique_ptr create(StringRefNull prop_name, - Span values); + 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(StringRefNull prop_name, Span values); +std::unique_ptr create(StringRefNull prop_name, + Span values, + eIDPropertyFlag flags = {}); /** * \brief Allocate a new IDProperty of type IDP_ARRAY and sub-type IDP_DOUBLE. @@ -429,7 +449,8 @@ std::unique_ptr create(StringRefNull prop_name, S * \param values: The values will be copied into the IDProperty. */ std::unique_ptr create(StringRefNull prop_name, - Span values); + Span values, + eIDPropertyFlag flags = {}); /** * \brief Allocate a new IDProperty of type IDP_GROUP. @@ -437,6 +458,7 @@ std::unique_ptr create(StringRefNull prop_name, * \param prop_name: The name of the newly created property. */ -std::unique_ptr create_group(StringRefNull prop_name); +std::unique_ptr create_group(StringRefNull prop_name, + eIDPropertyFlag flags = {}); } // namespace blender::bke::idprop diff --git a/source/blender/blenkernel/intern/idprop.cc b/source/blender/blenkernel/intern/idprop.cc index 0339b1a41fd..f5f61f4d686 100644 --- a/source/blender/blenkernel/intern/idprop.cc +++ b/source/blender/blenkernel/intern/idprop.cc @@ -354,7 +354,10 @@ static IDProperty *IDP_CopyArray(const IDProperty *prop, const int flag) /** \name String Functions (IDProperty String API) * \{ */ -IDProperty *IDP_NewStringMaxSize(const char *st, const size_t st_maxncpy, const char *name) +IDProperty *IDP_NewStringMaxSize(const char *st, + const size_t st_maxncpy, + const char *name, + const eIDPropertyFlag flags) { IDProperty *prop = static_cast( MEM_callocN(sizeof(IDProperty), "IDProperty string")); @@ -382,13 +385,14 @@ IDProperty *IDP_NewStringMaxSize(const char *st, const size_t st_maxncpy, const prop->type = IDP_STRING; STRNCPY(prop->name, name); + prop->flag = short(flags); return prop; } -IDProperty *IDP_NewString(const char *st, const char *name) +IDProperty *IDP_NewString(const char *st, const char *name, const eIDPropertyFlag flags) { - return IDP_NewStringMaxSize(st, 0, name); + return IDP_NewStringMaxSize(st, 0, name, flags); } static IDProperty *IDP_CopyString(const IDProperty *prop, const int flag) @@ -978,7 +982,10 @@ bool IDP_EqualsProperties(const IDProperty *prop1, const IDProperty *prop2) return IDP_EqualsProperties_ex(prop1, prop2, true); } -IDProperty *IDP_New(const char type, const IDPropertyTemplate *val, const char *name) +IDProperty *IDP_New(const char type, + const IDPropertyTemplate *val, + const char *name, + const eIDPropertyFlag flags) { IDProperty *prop = nullptr; @@ -1074,6 +1081,7 @@ IDProperty *IDP_New(const char type, const IDPropertyTemplate *val, const char * prop->type = type; STRNCPY(prop->name, name); + prop->flag = short(flags); return prop; } diff --git a/source/blender/blenkernel/intern/idprop_create.cc b/source/blender/blenkernel/intern/idprop_create.cc index dfa7aef6753..1ab01a620ca 100644 --- a/source/blender/blenkernel/intern/idprop_create.cc +++ b/source/blender/blenkernel/intern/idprop_create.cc @@ -14,62 +14,73 @@ namespace blender::bke::idprop { /** \name Create Functions * \{ */ -std::unique_ptr create(const StringRefNull prop_name, int32_t value) +std::unique_ptr create(const StringRefNull prop_name, + int32_t value, + const eIDPropertyFlag flags) { IDPropertyTemplate prop_template{0}; prop_template.i = value; - IDProperty *property = IDP_New(IDP_INT, &prop_template, prop_name.c_str()); + IDProperty *property = IDP_New(IDP_INT, &prop_template, prop_name.c_str(), flags); return std::unique_ptr(property); } std::unique_ptr create_bool(const StringRefNull prop_name, - bool value) + bool value, + const eIDPropertyFlag flags) { IDPropertyTemplate prop_template{0}; prop_template.i = value; - IDProperty *property = IDP_New(IDP_BOOLEAN, &prop_template, prop_name.c_str()); - return std::unique_ptr(property); -} - -std::unique_ptr 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(property); -} - -std::unique_ptr 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()); + IDProperty *property = IDP_New(IDP_BOOLEAN, &prop_template, prop_name.c_str(), flags); return std::unique_ptr(property); } std::unique_ptr create(const StringRefNull prop_name, - const StringRefNull value) + float value, + const eIDPropertyFlag flags) { - IDProperty *property = IDP_NewString(value.c_str(), prop_name.c_str()); + IDPropertyTemplate prop_template{0}; + prop_template.f = value; + IDProperty *property = IDP_New(IDP_FLOAT, &prop_template, prop_name.c_str(), flags); return std::unique_ptr(property); } -std::unique_ptr create(const StringRefNull prop_name, ID *value) +std::unique_ptr create(const StringRefNull prop_name, + double value, + const eIDPropertyFlag flags) +{ + IDPropertyTemplate prop_template{0}; + prop_template.d = value; + IDProperty *property = IDP_New(IDP_DOUBLE, &prop_template, prop_name.c_str(), flags); + return std::unique_ptr(property); +} + +std::unique_ptr create(const StringRefNull prop_name, + const StringRefNull value, + const eIDPropertyFlag flags) +{ + IDProperty *property = IDP_NewString(value.c_str(), prop_name.c_str(), flags); + return std::unique_ptr(property); +} + +std::unique_ptr create(const StringRefNull prop_name, + ID *value, + const eIDPropertyFlag flags) { IDPropertyTemplate prop_template{0}; prop_template.id = value; - IDProperty *property = IDP_New(IDP_ID, &prop_template, prop_name.c_str()); + IDProperty *property = IDP_New(IDP_ID, &prop_template, prop_name.c_str(), flags); return std::unique_ptr(property); } static std::unique_ptr array_create(const StringRefNull prop_name, eIDPropertyType subtype, - size_t array_len) + size_t array_len, + const eIDPropertyFlag flags) { 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()); + IDProperty *property = IDP_New(IDP_ARRAY, &prop_template, prop_name.c_str(), flags); return std::unique_ptr(property); } @@ -92,7 +103,8 @@ template< /** Sub-type of the #ID_ARRAY. Must match #PrimitiveType. */ eIDPropertyType id_property_subtype> std::unique_ptr create_array(StringRefNull prop_name, - Span values) + Span values, + const eIDPropertyFlag flags) { static_assert(std::is_same_v || std::is_same_v || std::is_same_v, @@ -107,34 +119,38 @@ std::unique_ptr create_array(StringRefNull prop_n const int64_t values_len = values.size(); BLI_assert(values_len > 0); std::unique_ptr property = array_create( - prop_name.c_str(), id_property_subtype, values_len); + prop_name.c_str(), id_property_subtype, values_len, flags); array_values_set( property.get(), static_cast(values.data()), values_len, sizeof(PrimitiveType)); return property; } std::unique_ptr create(const StringRefNull prop_name, - Span values) + Span values, + const eIDPropertyFlag flags) { - return create_array(prop_name, values); + return create_array(prop_name, values, flags); } std::unique_ptr create(const StringRefNull prop_name, - Span values) + Span values, + const eIDPropertyFlag flags) { - return create_array(prop_name, values); + return create_array(prop_name, values, flags); } std::unique_ptr create(const StringRefNull prop_name, - Span values) + Span values, + const eIDPropertyFlag flags) { - return create_array(prop_name, values); + return create_array(prop_name, values, flags); } -std::unique_ptr create_group(const StringRefNull prop_name) +std::unique_ptr create_group(const StringRefNull prop_name, + const eIDPropertyFlag flags) { IDPropertyTemplate prop_template{0}; - IDProperty *property = IDP_New(IDP_GROUP, &prop_template, prop_name.c_str()); + IDProperty *property = IDP_New(IDP_GROUP, &prop_template, prop_name.c_str(), flags); return std::unique_ptr(property); } diff --git a/source/blender/makesdna/DNA_ID_enums.h b/source/blender/makesdna/DNA_ID_enums.h index c8499a4abd8..b291e19678f 100644 --- a/source/blender/makesdna/DNA_ID_enums.h +++ b/source/blender/makesdna/DNA_ID_enums.h @@ -71,6 +71,19 @@ typedef enum eIDPropertyFlag { * local-inserted ones, as many operations are not allowed on the former. */ IDP_FLAG_OVERRIDELIBRARY_LOCAL = 1 << 1, + /** + * This #IDProperty has a static type, i.e. its #eIDPropertyType cannot be changed by assigning a + * new value to it. + * + * Currently, array len is also considered as fixed (i.e. part of the type) when this flag is + * set. This allows to avoid IDProperty storing vectors e.g. to see their length modified. + * + * \note Currently, all overridable IDProp are also statically typed. IDProps used as storage for + * dynamic RNA properties are also always dynamically typed. + * + * \note Internal flag, user have no direct way to define or edit it. + */ + IDP_FLAG_STATIC_TYPE = 1 << 4, /** * This means the property is set but RNA will return false when checking * #RNA_property_is_set, currently this is a runtime flag. diff --git a/source/blender/python/generic/idprop_py_api.cc b/source/blender/python/generic/idprop_py_api.cc index f50815abfb4..2ba8e0f8e77 100644 --- a/source/blender/python/generic/idprop_py_api.cc +++ b/source/blender/python/generic/idprop_py_api.cc @@ -341,7 +341,9 @@ static char idp_sequence_type(PyObject *seq_fast) for (i = 0; i < len; i++) { item = seq_fast_items[i]; if (PyFloat_Check(item)) { - if (type == IDP_IDPARRAY) { /* mixed dict/int */ + /* Mixed float and any other type but int. + * NOTE: Mixed float/int is allowed, and considered as float values. */ + if (!ELEM(type, IDP_INT, IDP_DOUBLE)) { return -1; } type = IDP_DOUBLE; @@ -354,7 +356,9 @@ static char idp_sequence_type(PyObject *seq_fast) type = IDP_BOOLEAN; } else if (PyLong_Check(item)) { - if (type == IDP_IDPARRAY) { /* mixed dict/int */ + /* Mixed int and any other type but float. + * NOTE: Mixed float/int is allowed, and considered as float values. */ + if (!ELEM(type, IDP_INT, IDP_DOUBLE)) { return -1; } } @@ -408,53 +412,206 @@ static const char *idp_try_read_name(PyObject *name_obj) /** * The 'idp_from_Py*' functions expect that the input type has been checked before * and return nullptr if the IDProperty can't be created. + * + * \param prop_exist If not null, attempt to assign given `ob` value to this property first, and + * only create a new one if not possible. + * If no assignment (or conversion and assignment) is possible, the current + * value remains unchanged. + * + * \param do_conversion If `true`, allow some 'reasonable' conversion of input value to match the + * `prop_exist` property type. E.g. can convert an `int` to a `float`, but not + * the other way around. + * + * \param can_create Whether creating a new IDProperty is allowed. + * + * \return `prop_exist` if given and it could be assigned given `ob` value, a new IDProperty + * otherwise. */ -static IDProperty *idp_from_PyFloat(const char *name, PyObject *ob) +static IDProperty *idp_from_PyFloat(IDProperty *prop_exist, + const char *name, + PyObject *ob, + const bool do_conversion, + const bool can_create) { - return blender::bke::idprop::create(name, PyFloat_AsDouble(ob)).release(); -} - -static IDProperty *idp_from_PyBool(const char *name, PyObject *ob) -{ - return blender::bke::idprop::create_bool(name, PyC_Long_AsBool(ob)).release(); -} - -static IDProperty *idp_from_PyLong(const char *name, PyObject *ob) -{ - const int value = PyC_Long_AsI32(ob); - if (value == -1 && PyErr_Occurred()) { - return nullptr; + IDProperty *prop = nullptr; + const double value = PyFloat_AsDouble(ob); + if (prop_exist) { + if (prop_exist->type == IDP_DOUBLE) { + IDP_Double(prop_exist) = value; + prop = prop_exist; + } + else if (do_conversion) { + switch (prop_exist->type) { + case IDP_FLOAT: + IDP_Float(prop_exist) = float(value); + prop = prop_exist; + break; + case IDP_STRING: + case IDP_INT: + case IDP_ARRAY: + case IDP_GROUP: + case IDP_ID: + case IDP_DOUBLE: + case IDP_IDPARRAY: + case IDP_BOOLEAN: + break; + } + } + } + if (!prop && can_create) { + prop = blender::bke::idprop::create(name, value).release(); } - return blender::bke::idprop::create(name, value).release(); -} - -static IDProperty *idp_from_PyUnicode(const char *name, PyObject *ob) -{ - IDProperty *prop; - IDPropertyTemplate val = {0}; -#ifdef USE_STRING_COERCE - Py_ssize_t value_len; - PyObject *value_coerce = nullptr; - val.string.str = PyC_UnicodeAsBytesAndSize(ob, &value_len, &value_coerce); - val.string.len = int(value_len) + 1; - val.string.subtype = IDP_STRING_SUB_UTF8; - prop = IDP_New(IDP_STRING, &val, name); - Py_XDECREF(value_coerce); -#else - val.str = PyUnicode_AsUTF8(ob); - prop = IDP_New(IDP_STRING, val, name); -#endif return prop; } -static IDProperty *idp_from_PyBytes(const char *name, PyObject *ob) +static IDProperty *idp_from_PyBool(IDProperty *prop_exist, + const char *name, + PyObject *ob, + const bool do_conversion, + const bool can_create) { - IDPropertyTemplate val = {0}; - val.string.str = PyBytes_AS_STRING(ob); - val.string.len = PyBytes_GET_SIZE(ob); - val.string.subtype = IDP_STRING_SUB_BYTE; - return IDP_New(IDP_STRING, &val, name); + IDProperty *prop = nullptr; + const bool value = PyC_Long_AsBool(ob); + if (prop_exist) { + if (prop_exist->type == IDP_BOOLEAN) { + IDP_Bool(prop_exist) = value; + prop = prop_exist; + } + else if (do_conversion) { + switch (prop_exist->type) { + case IDP_INT: + IDP_Int(prop_exist) = int(value); + prop = prop_exist; + break; + case IDP_STRING: + case IDP_FLOAT: + case IDP_ARRAY: + case IDP_GROUP: + case IDP_ID: + case IDP_DOUBLE: + case IDP_IDPARRAY: + case IDP_BOOLEAN: + break; + } + } + } + if (!prop && can_create) { + prop = blender::bke::idprop::create_bool(name, value).release(); + } + return prop; +} + +static IDProperty *idp_from_PyLong(IDProperty *prop_exist, + const char *name, + PyObject *ob, + const bool do_conversion, + const bool can_create) +{ + IDProperty *prop = nullptr; + const int value = PyC_Long_AsI32(ob); + if (value == -1 && PyErr_Occurred()) { + return prop; + } + if (prop_exist) { + if (prop_exist->type == IDP_INT) { + IDP_Int(prop_exist) = value; + prop = prop_exist; + } + else if (do_conversion) { + switch (prop_exist->type) { + case IDP_FLOAT: + IDP_Float(prop_exist) = float(value); + prop = prop_exist; + break; + case IDP_DOUBLE: + IDP_Double(prop_exist) = double(value); + prop = prop_exist; + break; + case IDP_STRING: + case IDP_INT: + case IDP_ARRAY: + case IDP_GROUP: + case IDP_ID: + case IDP_IDPARRAY: + case IDP_BOOLEAN: + break; + } + } + } + if (!prop && can_create) { + prop = blender::bke::idprop::create(name, value).release(); + } + return prop; +} + +static IDProperty *idp_from_PyUnicode(IDProperty *prop_exist, + const char *name, + PyObject *ob, + const bool /*do_conversion*/, + const bool can_create) +{ + IDProperty *prop = nullptr; + Py_ssize_t value_len = 0; + const char *value = nullptr; + +#ifdef USE_STRING_COERCE + PyObject *value_coerce = nullptr; + value = PyC_UnicodeAsBytesAndSize(ob, &value_len, &value_coerce); +#else + value = PyUnicode_AsUTF8AndSize(ob, &value_len); +#endif + + if (prop_exist) { + if (prop_exist->type == IDP_STRING && prop_exist->subtype == IDP_STRING_SUB_UTF8) { + IDP_AssignStringMaxSize(prop_exist, value, int(value_len) + 1); + prop = prop_exist; + } + /* No conversion. */ + } + + if (!prop && can_create) { + IDPropertyTemplate val = {0}; + val.string.str = value; + val.string.len = int(value_len) + 1; + val.string.subtype = IDP_STRING_SUB_UTF8; + prop = IDP_New(IDP_STRING, &val, name); + } + +#ifdef USE_STRING_COERCE + Py_XDECREF(value_coerce); +#endif + + return prop; +} + +static IDProperty *idp_from_PyBytes(IDProperty *prop_exist, + const char *name, + PyObject *ob, + const bool /*do_conversion*/, + const bool can_create) +{ + IDProperty *prop = nullptr; + Py_ssize_t value_len = PyBytes_GET_SIZE(ob); + const char *value = PyBytes_AS_STRING(ob); + + if (prop_exist) { + if (prop_exist->type == IDP_STRING && prop_exist->subtype == IDP_STRING_SUB_BYTE) { + IDP_AssignStringMaxSize(prop_exist, value, int(value_len) + 1); + prop = prop_exist; + } + /* No conversion. */ + } + + if (!prop && can_create) { + IDPropertyTemplate val = {0}; + val.string.str = value; + val.string.len = int(value_len); + val.string.subtype = IDP_STRING_SUB_BYTE; + prop = IDP_New(IDP_STRING, &val, name); + } + + return prop; } static int idp_array_type_from_formatstr_and_size(const char *typestr, Py_ssize_t itemsize) @@ -496,28 +653,41 @@ static const char *idp_format_from_array_type(int type) return nullptr; } -static IDProperty *idp_from_PySequence_Buffer(const char *name, Py_buffer *buffer) +static IDProperty *idp_from_PySequence_Buffer(IDProperty *prop_exist, + const char *name, + Py_buffer &buffer, + const int idp_type, + const bool /*do_conversion*/, + const bool can_create) { - IDProperty *prop; - IDPropertyTemplate val = {0}; + BLI_assert(idp_type != -1); + IDProperty *prop = nullptr; - const int id_type = idp_array_type_from_formatstr_and_size(buffer->format, buffer->itemsize); - if (id_type == -1) { - /* should never happen as the type has been checked before */ - return nullptr; + if (prop_exist) { + if (prop_exist->type == IDP_ARRAY && prop_exist->subtype == idp_type) { + BLI_assert(buffer.len == prop_exist->len); + memcpy(IDP_Array(prop_exist), buffer.buf, buffer.len); + prop = prop_exist; + } + /* No conversion. */ + } + if (!prop && can_create) { + IDPropertyTemplate val = {0}; + val.array.type = idp_type; + val.array.len = buffer.len / buffer.itemsize; + prop = IDP_New(IDP_ARRAY, &val, name); + memcpy(IDP_Array(prop), buffer.buf, buffer.len); } - - val.array.type = id_type; - val.array.len = buffer->len / buffer->itemsize; - - prop = IDP_New(IDP_ARRAY, &val, name); - memcpy(IDP_Array(prop), buffer->buf, buffer->len); return prop; } -static IDProperty *idp_from_PySequence_Fast(const char *name, PyObject *ob) +static IDProperty *idp_from_PySequence_Fast(IDProperty *prop_exist, + const char *name, + PyObject *ob, + const bool do_conversion, + const bool can_create) { - IDProperty *prop; + IDProperty *prop = nullptr; IDPropertyTemplate val = {0}; PyObject **ob_seq_fast_items; @@ -528,18 +698,115 @@ static IDProperty *idp_from_PySequence_Fast(const char *name, PyObject *ob) /* IDProperties do not support mixed type of data in an array. Try to extract a single type from * the whole sequence, or error. */ - if ((val.array.type = idp_sequence_type(ob)) == char(-1)) { + val.array.type = idp_sequence_type(ob); + if (val.array.type == char(-1)) { PyErr_SetString(PyExc_TypeError, "only floats, ints, booleans and dicts are allowed in ID property arrays"); return nullptr; } - - /* validate sequence and derive type. - * we assume IDP_INT unless we hit a float - * number; then we assume it's */ + if (!ELEM(val.array.type, IDP_DOUBLE, IDP_INT, IDP_BOOLEAN, IDP_IDPARRAY)) { + /* Should never happen. */ + PyErr_SetString(PyExc_RuntimeError, "internal error with idp array.type"); + BLI_assert_unreachable(); + return nullptr; + } val.array.len = PySequence_Fast_GET_SIZE(ob); + /* NOTE: For now do not consider resizing existing array property. Also do not handle IDPARRAY. + * - 'static type' also means 'fixed length' (e.g. vectors or matrices cases). + * - For 'dynamic type' case, it's not really a problem if array properties get replaced + * currently. + */ + if (prop_exist && prop_exist->len == val.array.len) { + switch (val.array.type) { + case IDP_DOUBLE: { + const bool to_float = (prop_exist->subtype == IDP_FLOAT); + if (!(prop_exist->type == IDP_ARRAY && + (prop_exist->subtype == IDP_DOUBLE || (do_conversion && to_float)))) + { + break; + } + prop = prop_exist; + void *prop_data = IDP_Array(prop); + for (i = 0; i < val.array.len; i++) { + item = ob_seq_fast_items[i]; + const double value = PyFloat_AsDouble(item); + if ((value == -1.0) && PyErr_Occurred()) { + continue; + } + if (to_float) { + static_cast(prop_data)[i] = float(value); + } + else { + static_cast(prop_data)[i] = value; + } + } + break; + } + case IDP_INT: { + const bool to_float = (prop_exist->subtype == IDP_FLOAT); + const bool to_double = (prop_exist->subtype == IDP_DOUBLE); + if (!(prop_exist->type == IDP_ARRAY && + (prop_exist->subtype == IDP_INT || (do_conversion && (to_float || to_double))))) + { + break; + } + prop = prop_exist; + void *prop_data = IDP_Array(prop); + for (i = 0; i < val.array.len; i++) { + item = ob_seq_fast_items[i]; + const int value = PyC_Long_AsI32(item); + if ((value == -1) && PyErr_Occurred()) { + continue; + } + if (to_float) { + static_cast(prop_data)[i] = float(value); + } + else if (to_double) { + static_cast(prop_data)[i] = double(value); + } + else { + static_cast(prop_data)[i] = value; + } + } + break; + } + case IDP_BOOLEAN: { + const bool to_int = (prop_exist->subtype == IDP_INT); + if (!(prop_exist->type == IDP_ARRAY && + (prop_exist->subtype == IDP_BOOLEAN || (do_conversion && to_int)))) + { + break; + } + prop = prop_exist; + void *prop_data = IDP_Array(prop); + for (i = 0; i < val.array.len; i++) { + item = ob_seq_fast_items[i]; + const int value = PyC_Long_AsBool(item); + if ((value == -1) && PyErr_Occurred()) { + continue; + } + if (to_int) { + static_cast(prop_data)[i] = value; + } + else { + static_cast(prop_data)[i] = bool(value); + } + } + break; + } + case IDP_IDPARRAY: { + /* TODO? */ + break; + } + } + } + + if (prop || !can_create) { + return prop; + } + switch (val.array.type) { case IDP_DOUBLE: { double *prop_data; @@ -592,18 +859,19 @@ static IDProperty *idp_from_PySequence_Fast(const char *name, PyObject *ob) } break; } - default: - /* should never happen */ - PyErr_SetString(PyExc_RuntimeError, "internal error with idp array.type"); - return nullptr; } return prop; } -static IDProperty *idp_from_PySequence(const char *name, PyObject *ob) +static IDProperty *idp_from_PySequence(IDProperty *prop_exist, + const char *name, + PyObject *ob, + const bool do_conversion, + const bool can_create) { Py_buffer buffer; bool use_buffer = false; + int idp_buffer_type = -1; if (PyObject_CheckBuffer(ob)) { if (PyObject_GetBuffer(ob, &buffer, PyBUF_ND | PyBUF_FORMAT) == -1) { @@ -612,11 +880,18 @@ static IDProperty *idp_from_PySequence(const char *name, PyObject *ob) PyErr_Clear(); } else { - const char format = PyC_StructFmt_type_from_str(buffer.format); - if (PyC_StructFmt_type_is_float_any(format) || - (PyC_StructFmt_type_is_int_any(format) && buffer.itemsize == 4)) - { - use_buffer = true; + idp_buffer_type = idp_array_type_from_formatstr_and_size(buffer.format, buffer.itemsize); + if (idp_buffer_type != -1) { + /* If creating a new IDProp is not allowed, and the existing one is not usable (same size + * and type), then the 'buffer assignment' process cannot be used. */ + if (!can_create && (!prop_exist || (prop_exist->type != idp_buffer_type) || + (prop_exist->len != buffer.len))) + { + PyBuffer_Release(&buffer); + } + else { + use_buffer = true; + } } else { PyBuffer_Release(&buffer); @@ -625,14 +900,16 @@ static IDProperty *idp_from_PySequence(const char *name, PyObject *ob) } if (use_buffer) { - IDProperty *prop = idp_from_PySequence_Buffer(name, &buffer); + IDProperty *prop = idp_from_PySequence_Buffer( + prop_exist, name, buffer, idp_buffer_type, do_conversion, can_create); PyBuffer_Release(&buffer); return prop; } PyObject *ob_seq_fast = PySequence_Fast(ob, "py -> idprop"); if (ob_seq_fast != nullptr) { - IDProperty *prop = idp_from_PySequence_Fast(name, ob_seq_fast); + IDProperty *prop = idp_from_PySequence_Fast( + prop_exist, name, ob_seq_fast, do_conversion, can_create); Py_DECREF(ob_seq_fast); return prop; } @@ -640,10 +917,16 @@ static IDProperty *idp_from_PySequence(const char *name, PyObject *ob) return nullptr; } -static IDProperty *idp_from_PyMapping(const char *name, PyObject *ob) +static IDProperty *idp_from_PyMapping(IDProperty * /*prop_exist*/, + const char *name, + PyObject *ob, + const bool /*do_conversion*/, + const bool /*can_create*/) { IDProperty *prop; + /* TODO: Handle editing in-place of existing property (#IDP_FLAG_STATIC_TYPE flag). */ + PyObject *keys, *vals, *key, *pval; int i, len; /* yay! we get into recursive stuff now! */ @@ -674,43 +957,61 @@ static IDProperty *idp_from_PyMapping(const char *name, PyObject *ob) return prop; } -static IDProperty *idp_from_DatablockPointer(const char *name, PyObject *ob) +static IDProperty *idp_from_DatablockPointer(IDProperty *prop_exist, + const char *name, + PyObject *ob, + const bool /*do_conversion*/, + const bool can_create) { - ID *id = nullptr; - pyrna_id_FromPyObject(ob, &id); - return blender::bke::idprop::create(name, id).release(); + IDProperty *prop = nullptr; + ID *value = nullptr; + pyrna_id_FromPyObject(ob, &value); + if (prop_exist) { + if (prop_exist->type == IDP_ID) { + IDP_AssignID(prop_exist, value, 0); + prop = prop_exist; + } + /* No conversion. */ + } + if (!prop && can_create) { + prop = blender::bke::idprop::create(name, value).release(); + } + return prop; } -static IDProperty *idp_from_PyObject(PyObject *name_obj, PyObject *ob) +static IDProperty *idp_from_PyObject(IDProperty *prop_exist, + const char *name, + PyObject *ob, + const bool do_conversion, + const bool can_create) { - const char *name = idp_try_read_name(name_obj); if (name == nullptr) { return nullptr; } if (PyFloat_Check(ob)) { - return idp_from_PyFloat(name, ob); + return idp_from_PyFloat(prop_exist, name, ob, do_conversion, can_create); } if (PyBool_Check(ob)) { - return idp_from_PyBool(name, ob); + return idp_from_PyBool(prop_exist, name, ob, do_conversion, can_create); } if (PyLong_Check(ob)) { - return idp_from_PyLong(name, ob); + return idp_from_PyLong(prop_exist, name, ob, do_conversion, can_create); } if (PyUnicode_Check(ob)) { - return idp_from_PyUnicode(name, ob); + return idp_from_PyUnicode(prop_exist, name, ob, do_conversion, can_create); } if (PyBytes_Check(ob)) { - return idp_from_PyBytes(name, ob); + return idp_from_PyBytes(prop_exist, name, ob, do_conversion, can_create); } if (PySequence_Check(ob)) { - return idp_from_PySequence(name, ob); + return idp_from_PySequence(prop_exist, name, ob, do_conversion, can_create); } if (ob == Py_None || pyrna_id_CheckPyObject(ob)) { - return idp_from_DatablockPointer(name, ob); + return idp_from_DatablockPointer(prop_exist, name, ob, do_conversion, can_create); } if (PyMapping_Check(ob)) { - return idp_from_PyMapping(name, ob); + return idp_from_PyMapping(prop_exist, name, ob, do_conversion, can_create); } PyErr_Format( @@ -726,55 +1027,72 @@ static IDProperty *idp_from_PyObject(PyObject *name_obj, PyObject *ob) bool BPy_IDProperty_Map_ValidateAndCreate(PyObject *name_obj, IDProperty *group, PyObject *ob) { - IDProperty *prop = idp_from_PyObject(name_obj, ob); - if (prop == nullptr) { + const char *name = idp_try_read_name(name_obj); + if (!name) { return false; } + /* If the container is an array of IDProperties, always add a new property to it. */ if (group->type == IDP_IDPARRAY) { - IDP_AppendArray(group, prop); + IDProperty *new_prop = idp_from_PyObject(nullptr, name, ob, false, true); + if (new_prop == nullptr) { + return false; + } + + IDP_AppendArray(group, new_prop); /* IDP_AppendArray does a shallow copy (memcpy), only free memory */ - MEM_freeN(prop); - } - else { - IDProperty *prop_exist; + MEM_freeN(new_prop); - /* avoid freeing when types match in case they are referenced by the UI, see: #37073 - * obviously this isn't a complete solution, but helps for common cases. */ - prop_exist = IDP_GetPropertyFromGroup(group, prop->name); - - if (prop_exist && prop_exist->ui_data) { - /* Take ownership of the existing property's UI data. */ - const eIDPropertyUIDataType src_type = IDP_ui_data_type(prop_exist); - IDPropertyUIData *ui_data = prop_exist->ui_data; - prop_exist->ui_data = nullptr; - - prop->ui_data = IDP_TryConvertUIData(ui_data, src_type, IDP_ui_data_type(prop)); - } - - if ((prop_exist != nullptr) && (prop_exist->type == prop->type) && - (prop_exist->subtype == prop->subtype)) - { - /* Preserve prev/next links!!! See #42593. */ - prop->prev = prop_exist->prev; - prop->next = prop_exist->next; - prop->flag = prop_exist->flag; - - IDP_FreePropertyContent(prop_exist); - *prop_exist = *prop; - MEM_freeN(prop); - } - else { - const bool overridable = prop_exist ? - (prop_exist->flag & IDP_FLAG_OVERRIDABLE_LIBRARY) != 0 : - false; - IDP_ReplaceInGroup_ex(group, prop, prop_exist); - if (overridable) { - prop->flag |= IDP_FLAG_OVERRIDABLE_LIBRARY; - } - } + return true; } + IDProperty *prop_exist = IDP_GetPropertyFromGroup(group, name); + + /* If existing property is flagged to be statically typed, do not re-type it. Assign the value if + * possible (potentially converting it), or fail. See #122743. */ + if (prop_exist && (prop_exist->flag & IDP_FLAG_STATIC_TYPE) != 0) { + IDProperty *prop = idp_from_PyObject(prop_exist, name, ob, true, false); + BLI_assert(ELEM(prop, prop_exist, nullptr)); + if (prop != prop_exist) { + PyErr_Format(PyExc_TypeError, + "Cannot assign a '%.200s' value to the existing '%s' %s IDProperty", + Py_TYPE(ob)->tp_name, + name, + IDP_type_str(prop_exist)); + return false; + } + return true; + } + + /* Attempt to assign new value in existing IDProperty, if types (and potentially subtypes) match + * exactly. Otherwise, create a new IDProperty. */ + IDProperty *new_prop = idp_from_PyObject(prop_exist, name, ob, false, true); + if (new_prop == nullptr) { + return false; + } + if (new_prop == prop_exist) { + return true; + } + + /* Property was created with no existing counterpart, just insert it in the group container. */ + if (!prop_exist) { + IDP_ReplaceInGroup_ex(group, new_prop, nullptr); + return true; + } + + /* Try to preserve UI data from the existing, replaced property. See: #37073. */ + if (prop_exist->ui_data) { + /* Take ownership of the existing property's UI data. */ + const eIDPropertyUIDataType src_type = IDP_ui_data_type(prop_exist); + IDPropertyUIData *ui_data = prop_exist->ui_data; + prop_exist->ui_data = nullptr; + + new_prop->ui_data = IDP_TryConvertUIData(ui_data, src_type, IDP_ui_data_type(new_prop)); + } + /* Copy over the 'overridable' flag from existing property. */ + new_prop->flag |= (prop_exist->flag & IDP_FLAG_OVERRIDABLE_LIBRARY); + + IDP_ReplaceInGroup_ex(group, new_prop, prop_exist); return true; }