IDProps: Add 'static type' option to IDProperties.

This implements (most of) the proposal in #122743:

* Add a new `IDP_FLAG_STATIC_TYPE` IDProperty flag.
* Update `BPy_IDProperty_Map_ValidateAndCreate` and related to never
  change an existing property type if statically typed.

The biggest change happens in bpy assignement code, since instead of
replacing the old exisitng property by a newly created one, and copying
over a few settings, now the old property is kept if possible, and a new
one is only created if needed.

And in case the existing property is statically typed, if it cannot be
re-used to store the given value, and error is reported and it remains
unchanged.

`IDP_ARRAY` is also supported for basic numeric types, so 'vector'
properties and such work as expected. Lentgh is considered as part of
the static type (i.e. one can only assign a 3 components py sequence to
a 3-len array property, etc.).

Such in-place update is not yet implemented for `IDP_IDPARRAY` and
`IDP_GROUP` types. While important (especially the group one), they are
not that critical for the current issues related to changing IDProperty
types.
This commit is contained in:
Bastien Montagne 2024-06-13 19:02:41 +02:00 committed by Bastien Montagne
parent a6d3feda8e
commit 4d1fe98604
5 changed files with 560 additions and 183 deletions

@ -10,6 +10,8 @@
#include <memory>
#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<IDProperty, IDPropertyDeleter> create_bool(StringRefNull prop_name, bool value);
std::unique_ptr<IDProperty, IDPropertyDeleter> 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<IDProperty, IDPropertyDeleter> create(StringRefNull prop_name, int32_t value);
std::unique_ptr<IDProperty, IDPropertyDeleter> 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<IDProperty, IDPropertyDeleter> create(StringRefNull prop_name, float value);
std::unique_ptr<IDProperty, IDPropertyDeleter> 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<IDProperty, IDPropertyDeleter> create(StringRefNull prop_name, double value);
std::unique_ptr<IDProperty, IDPropertyDeleter> 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<IDProperty, IDPropertyDeleter> 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<IDProperty, IDPropertyDeleter> create(StringRefNull prop_name, ID *value);
std::unique_ptr<IDProperty, IDPropertyDeleter> 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<IDProperty, IDPropertyDeleter> create(StringRefNull prop_name, I
* \param values: The values will be copied into the IDProperty.
*/
std::unique_ptr<IDProperty, IDPropertyDeleter> create(StringRefNull prop_name,
Span<int32_t> values);
Span<int32_t> 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<IDProperty, IDPropertyDeleter> create(StringRefNull prop_name, Span<float> values);
std::unique_ptr<IDProperty, IDPropertyDeleter> create(StringRefNull prop_name,
Span<float> values,
eIDPropertyFlag flags = {});
/**
* \brief Allocate a new IDProperty of type IDP_ARRAY and sub-type IDP_DOUBLE.
@ -429,7 +449,8 @@ std::unique_ptr<IDProperty, IDPropertyDeleter> create(StringRefNull prop_name, S
* \param values: The values will be copied into the IDProperty.
*/
std::unique_ptr<IDProperty, IDPropertyDeleter> create(StringRefNull prop_name,
Span<double> values);
Span<double> values,
eIDPropertyFlag flags = {});
/**
* \brief Allocate a new IDProperty of type IDP_GROUP.
@ -437,6 +458,7 @@ std::unique_ptr<IDProperty, IDPropertyDeleter> create(StringRefNull prop_name,
* \param prop_name: The name of the newly created property.
*/
std::unique_ptr<IDProperty, IDPropertyDeleter> create_group(StringRefNull prop_name);
std::unique_ptr<IDProperty, IDPropertyDeleter> create_group(StringRefNull prop_name,
eIDPropertyFlag flags = {});
} // namespace blender::bke::idprop

@ -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<IDProperty *>(
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;
}

@ -14,62 +14,73 @@ namespace blender::bke::idprop {
/** \name Create Functions
* \{ */
std::unique_ptr<IDProperty, IDPropertyDeleter> create(const StringRefNull prop_name, int32_t value)
std::unique_ptr<IDProperty, IDPropertyDeleter> 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<IDProperty, IDPropertyDeleter>(property);
}
std::unique_ptr<IDProperty, IDPropertyDeleter> 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<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());
IDProperty *property = IDP_New(IDP_BOOLEAN, &prop_template, prop_name.c_str(), flags);
return std::unique_ptr<IDProperty, IDPropertyDeleter>(property);
}
std::unique_ptr<IDProperty, IDPropertyDeleter> 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<IDProperty, IDPropertyDeleter>(property);
}
std::unique_ptr<IDProperty, IDPropertyDeleter> create(const StringRefNull prop_name, ID *value)
std::unique_ptr<IDProperty, IDPropertyDeleter> 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<IDProperty, IDPropertyDeleter>(property);
}
std::unique_ptr<IDProperty, IDPropertyDeleter> 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<IDProperty, IDPropertyDeleter>(property);
}
std::unique_ptr<IDProperty, IDPropertyDeleter> 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<IDProperty, IDPropertyDeleter>(property);
}
static std::unique_ptr<IDProperty, IDPropertyDeleter> 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<IDProperty, IDPropertyDeleter>(property);
}
@ -92,7 +103,8 @@ template<
/** Sub-type of the #ID_ARRAY. Must match #PrimitiveType. */
eIDPropertyType id_property_subtype>
std::unique_ptr<IDProperty, IDPropertyDeleter> create_array(StringRefNull prop_name,
Span<PrimitiveType> values)
Span<PrimitiveType> values,
const eIDPropertyFlag flags)
{
static_assert(std::is_same_v<PrimitiveType, int32_t> || std::is_same_v<PrimitiveType, float> ||
std::is_same_v<PrimitiveType, double>,
@ -107,34 +119,38 @@ std::unique_ptr<IDProperty, IDPropertyDeleter> create_array(StringRefNull prop_n
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);
prop_name.c_str(), id_property_subtype, values_len, flags);
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)
Span<int32_t> values,
const eIDPropertyFlag flags)
{
return create_array<int32_t, IDP_INT>(prop_name, values);
return create_array<int32_t, IDP_INT>(prop_name, values, flags);
}
std::unique_ptr<IDProperty, IDPropertyDeleter> create(const StringRefNull prop_name,
Span<float> values)
Span<float> values,
const eIDPropertyFlag flags)
{
return create_array<float, IDP_FLOAT>(prop_name, values);
return create_array<float, IDP_FLOAT>(prop_name, values, flags);
}
std::unique_ptr<IDProperty, IDPropertyDeleter> create(const StringRefNull prop_name,
Span<double> values)
Span<double> values,
const eIDPropertyFlag flags)
{
return create_array<double, IDP_DOUBLE>(prop_name, values);
return create_array<double, IDP_DOUBLE>(prop_name, values, flags);
}
std::unique_ptr<IDProperty, IDPropertyDeleter> create_group(const StringRefNull prop_name)
std::unique_ptr<IDProperty, IDPropertyDeleter> 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<IDProperty, IDPropertyDeleter>(property);
}

@ -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.

@ -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<float *>(prop_data)[i] = float(value);
}
else {
static_cast<double *>(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<float *>(prop_data)[i] = float(value);
}
else if (to_double) {
static_cast<double *>(prop_data)[i] = double(value);
}
else {
static_cast<int *>(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<int *>(prop_data)[i] = value;
}
else {
static_cast<bool *>(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;
}