ID properties: Support enum values with items

Add support for enum values in ID properties.

This is needed for the "Menu Switch" node implementation (#113445) which
relies on ID properties for the top-level modifier UI.

Enums items can optionally be added to the UI data of integer
properties. Each property stores a full set of the enum items to keep
things simple.

Enum items can be added to properties using the `id_properties_ui`
function in the python API. A detailed example can be found in the
`bl_pyapi_idprop.py` test.

There is currently no support yet for editing enum items through the UI.
This is because the "Edit Property" feature is implemented entirely
through a single operator (`WM_OT_properties_edit`) and its properties.
Buttons to add/remove/move items would be operators changing another
operator's properties. A refactor of the custom properties UI is likely
required to make this work.

Pull Request: https://projects.blender.org/blender/blender/pulls/114362
This commit is contained in:
Lukas Tönne
2023-12-15 10:20:44 +01:00
parent 7346727cfc
commit 92cf9dd2f2
11 changed files with 416 additions and 8 deletions

View File

@@ -21,6 +21,7 @@ struct BlendWriter;
struct ID; struct ID;
struct IDProperty; struct IDProperty;
struct IDPropertyUIData; struct IDPropertyUIData;
struct IDPropertyUIDataEnumItem;
struct Library; struct Library;
typedef union IDPropertyTemplate { typedef union IDPropertyTemplate {
@@ -94,6 +95,14 @@ void IDP_AssignStringMaxSize(struct IDProperty *prop, const char *st, size_t st_
void IDP_AssignString(struct IDProperty *prop, const char *st) ATTR_NONNULL(); void IDP_AssignString(struct IDProperty *prop, const char *st) ATTR_NONNULL();
void IDP_FreeString(struct IDProperty *prop) ATTR_NONNULL(); void IDP_FreeString(struct IDProperty *prop) ATTR_NONNULL();
/*-------- Enum Type -------*/
const struct IDPropertyUIDataEnumItem *IDP_EnumItemFind(const struct IDProperty *prop);
bool IDP_EnumItemsValidate(const struct IDPropertyUIDataEnumItem *items,
int items_num,
void (*error_fn)(const char *));
/*-------- ID Type -------*/ /*-------- ID Type -------*/
typedef void (*IDPWalkFunc)(void *user_data, struct IDProperty *idp); typedef void (*IDPWalkFunc)(void *user_data, struct IDProperty *idp);

View File

@@ -13,9 +13,12 @@
#include <cstdlib> #include <cstdlib>
#include <cstring> #include <cstring>
#include <fmt/format.h>
#include "BLI_endian_switch.h" #include "BLI_endian_switch.h"
#include "BLI_listbase.h" #include "BLI_listbase.h"
#include "BLI_math_base.h" #include "BLI_math_base.h"
#include "BLI_set.hh"
#include "BLI_string.h" #include "BLI_string.h"
#include "BLI_utildefines.h" #include "BLI_utildefines.h"
@@ -275,6 +278,14 @@ IDPropertyUIData *IDP_ui_data_copy(const IDProperty *prop)
const IDPropertyUIDataInt *src = (const IDPropertyUIDataInt *)prop->ui_data; const IDPropertyUIDataInt *src = (const IDPropertyUIDataInt *)prop->ui_data;
IDPropertyUIDataInt *dst = (IDPropertyUIDataInt *)dst_ui_data; IDPropertyUIDataInt *dst = (IDPropertyUIDataInt *)dst_ui_data;
dst->default_array = static_cast<int *>(MEM_dupallocN(src->default_array)); dst->default_array = static_cast<int *>(MEM_dupallocN(src->default_array));
dst->enum_items = static_cast<IDPropertyUIDataEnumItem *>(MEM_dupallocN(src->enum_items));
for (const int64_t i : blender::IndexRange(src->enum_items_num)) {
const IDPropertyUIDataEnumItem &src_item = src->enum_items[i];
IDPropertyUIDataEnumItem &dst_item = dst->enum_items[i];
dst_item.identifier = BLI_strdup(src_item.identifier);
dst_item.name = BLI_strdup_null(src_item.name);
dst_item.description = BLI_strdup_null(src_item.description);
}
break; break;
} }
case IDP_UI_DATA_TYPE_BOOLEAN: { case IDP_UI_DATA_TYPE_BOOLEAN: {
@@ -425,6 +436,78 @@ void IDP_FreeString(IDProperty *prop)
MEM_freeN(prop->data.pointer); MEM_freeN(prop->data.pointer);
} }
} }
/** \} */
/* -------------------------------------------------------------------- */
/** \name Enum Type (IDProperty Enum API)
* \{ */
static void IDP_int_ui_data_free_enum_items(IDPropertyUIDataInt *ui_data)
{
for (const int64_t i : blender::IndexRange(ui_data->enum_items_num)) {
IDPropertyUIDataEnumItem &item = ui_data->enum_items[i];
MEM_SAFE_FREE(item.identifier);
MEM_SAFE_FREE(item.name);
MEM_SAFE_FREE(item.description);
}
MEM_SAFE_FREE(ui_data->enum_items);
}
const IDPropertyUIDataEnumItem *IDP_EnumItemFind(const IDProperty *prop)
{
BLI_assert(prop->type == IDP_INT);
const IDPropertyUIDataInt *ui_data = reinterpret_cast<const IDPropertyUIDataInt *>(
prop->ui_data);
const int value = IDP_Int(prop);
for (const IDPropertyUIDataEnumItem &item :
blender::Span(ui_data->enum_items, ui_data->enum_items_num))
{
if (item.value == value) {
return &item;
}
}
return nullptr;
}
bool IDP_EnumItemsValidate(const IDPropertyUIDataEnumItem *items,
const int items_num,
void (*error_fn)(const char *))
{
blender::Set<int> used_values;
blender::Set<const char *> used_identifiers;
used_values.reserve(items_num);
used_identifiers.reserve(items_num);
bool is_valid = true;
for (const int64_t i : blender::IndexRange(items_num)) {
const IDPropertyUIDataEnumItem &item = items[i];
if (item.identifier == nullptr || item.identifier[0] == '\0') {
if (error_fn) {
const std::string msg = "Item identifier is empty";
error_fn(msg.c_str());
}
is_valid = false;
}
if (!used_identifiers.add(item.identifier)) {
if (error_fn) {
const std::string msg = fmt::format("Item identifier '{}' is already used",
item.identifier);
error_fn(msg.c_str());
}
is_valid = false;
}
if (!used_values.add(item.value)) {
if (error_fn) {
const std::string msg = fmt::format(
"Item value {} for item '{}' is already used", item.value, item.identifier);
error_fn(msg.c_str());
}
is_valid = false;
}
}
return is_valid;
}
/** \} */ /** \} */
@@ -1017,6 +1100,9 @@ void IDP_ui_data_free_unique_contents(IDPropertyUIData *ui_data,
if (ui_data_int->default_array != other_int->default_array) { if (ui_data_int->default_array != other_int->default_array) {
MEM_SAFE_FREE(ui_data_int->default_array); MEM_SAFE_FREE(ui_data_int->default_array);
} }
if (ui_data_int->enum_items != other_int->enum_items) {
IDP_int_ui_data_free_enum_items(ui_data_int);
}
break; break;
} }
case IDP_UI_DATA_TYPE_BOOLEAN: { case IDP_UI_DATA_TYPE_BOOLEAN: {
@@ -1055,6 +1141,7 @@ void IDP_ui_data_free(IDProperty *prop)
case IDP_UI_DATA_TYPE_INT: { case IDP_UI_DATA_TYPE_INT: {
IDPropertyUIDataInt *ui_data_int = (IDPropertyUIDataInt *)prop->ui_data; IDPropertyUIDataInt *ui_data_int = (IDPropertyUIDataInt *)prop->ui_data;
MEM_SAFE_FREE(ui_data_int->default_array); MEM_SAFE_FREE(ui_data_int->default_array);
IDP_int_ui_data_free_enum_items(ui_data_int);
break; break;
} }
case IDP_UI_DATA_TYPE_BOOLEAN: { case IDP_UI_DATA_TYPE_BOOLEAN: {
@@ -1198,6 +1285,14 @@ static void write_ui_data(const IDProperty *prop, BlendWriter *writer)
BLO_write_int32_array( BLO_write_int32_array(
writer, uint(ui_data_int->default_array_len), (int32_t *)ui_data_int->default_array); writer, uint(ui_data_int->default_array_len), (int32_t *)ui_data_int->default_array);
} }
BLO_write_struct_array(
writer, IDPropertyUIDataEnumItem, ui_data_int->enum_items_num, ui_data_int->enum_items);
for (const int64_t i : blender::IndexRange(ui_data_int->enum_items_num)) {
IDPropertyUIDataEnumItem &item = ui_data_int->enum_items[i];
BLO_write_string(writer, item.identifier);
BLO_write_string(writer, item.name);
BLO_write_string(writer, item.description);
}
BLO_write_struct(writer, IDPropertyUIDataInt, ui_data); BLO_write_struct(writer, IDPropertyUIDataInt, ui_data);
break; break;
} }
@@ -1325,6 +1420,13 @@ static void read_ui_data(IDProperty *prop, BlendDataReader *reader)
BLO_read_int32_array( BLO_read_int32_array(
reader, ui_data_int->default_array_len, (int **)&ui_data_int->default_array); reader, ui_data_int->default_array_len, (int **)&ui_data_int->default_array);
} }
BLO_read_data_address(reader, &ui_data_int->enum_items);
for (const int64_t i : blender::IndexRange(ui_data_int->enum_items_num)) {
IDPropertyUIDataEnumItem &item = ui_data_int->enum_items[i];
BLO_read_data_address(reader, &item.identifier);
BLO_read_data_address(reader, &item.name);
BLO_read_data_address(reader, &item.description);
}
break; break;
} }
case IDP_UI_DATA_TYPE_BOOLEAN: { case IDP_UI_DATA_TYPE_BOOLEAN: {

View File

@@ -140,6 +140,11 @@ struct DictionaryEntryParser {
return get_double(IDP_KEY_VALUE); return get_double(IDP_KEY_VALUE);
} }
std::optional<int> get_enum_value() const
{
return get_enum(IDP_KEY_VALUE);
}
const ArrayValue *get_array_value() const const ArrayValue *get_array_value() const
{ {
return get_array(IDP_KEY_VALUE); return get_array(IDP_KEY_VALUE);
@@ -206,6 +211,21 @@ struct DictionaryEntryParser {
return value->as_int_value()->value(); return value->as_int_value()->value();
} }
std::optional<int32_t> get_enum(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 std::optional<double> get_double(StringRef key) const
{ {
const DictionaryValue::LookupValue *value_ptr = lookup.lookup_ptr(key); const DictionaryValue::LookupValue *value_ptr = lookup.lookup_ptr(key);

View File

@@ -100,6 +100,9 @@ static void idp_repr_fn_recursive(ReprState *state, const IDProperty *prop)
break; break;
} }
case IDP_INT: { case IDP_INT: {
if (const IDPropertyUIDataEnumItem *item = IDP_EnumItemFind(prop)) {
STR_APPEND_FMT("%s", item->name);
}
STR_APPEND_FMT("%d", IDP_Int(prop)); STR_APPEND_FMT("%d", IDP_Int(prop));
break; break;
} }

View File

@@ -80,6 +80,7 @@ enum class eValueType {
Boolean, Boolean,
Double, Double,
Dictionary, Dictionary,
Enum,
}; };
class Value; class Value;
@@ -89,6 +90,7 @@ template<typename T, eValueType V> class PrimitiveValue;
using IntValue = PrimitiveValue<int64_t, eValueType::Int>; using IntValue = PrimitiveValue<int64_t, eValueType::Int>;
using DoubleValue = PrimitiveValue<double, eValueType::Double>; using DoubleValue = PrimitiveValue<double, eValueType::Double>;
using BooleanValue = PrimitiveValue<bool, eValueType::Boolean>; using BooleanValue = PrimitiveValue<bool, eValueType::Boolean>;
using EnumValue = PrimitiveValue<int, eValueType::Enum>;
class ArrayValue; class ArrayValue;
/** /**
@@ -146,6 +148,12 @@ class Value {
*/ */
const BooleanValue *as_boolean_value() const; const BooleanValue *as_boolean_value() const;
/**
* Casts to a EnumValue.
* Will return nullptr when it is a different type.
*/
const EnumValue *as_enum_value() const;
/** /**
* Casts to an ArrayValue. * Casts to an ArrayValue.
* Will return nullptr when it is a different type. * Will return nullptr when it is a different type.

View File

@@ -41,6 +41,14 @@ const BooleanValue *Value::as_boolean_value() const
return static_cast<const BooleanValue *>(this); return static_cast<const BooleanValue *>(this);
} }
const EnumValue *Value::as_enum_value() const
{
if (type_ != eValueType::Enum) {
return nullptr;
}
return static_cast<const EnumValue *>(this);
}
const ArrayValue *Value::as_array_value() const const ArrayValue *Value::as_array_value() const
{ {
if (type_ != eValueType::Array) { if (type_ != eValueType::Array) {
@@ -121,6 +129,12 @@ static void convert_to_json(nlohmann::ordered_json &j, const Value &value)
case eValueType::Double: { case eValueType::Double: {
j = value.as_double_value()->value(); j = value.as_double_value()->value();
break;
}
case eValueType::Enum: {
j = value.as_enum_value()->value();
break;
} }
} }
} }

View File

@@ -54,12 +54,25 @@ typedef struct IDPropertyUIData {
char _pad[4]; char _pad[4];
} IDPropertyUIData; } IDPropertyUIData;
/* DNA version of #EnumPropertyItem. */
typedef struct IDPropertyUIDataEnumItem {
/* Unique identifier, used for string lookup. */
char *identifier;
/* UI name of the item. */
char *name;
/* Optional description. */
char *description;
/* Unique integer value, should never change. */
int value;
/* Optional icon. */
int icon;
} IDPropertyUIDataEnumItem;
/* IDP_UI_DATA_TYPE_INT */ /* IDP_UI_DATA_TYPE_INT */
typedef struct IDPropertyUIDataInt { typedef struct IDPropertyUIDataInt {
IDPropertyUIData base; IDPropertyUIData base;
int *default_array; /* Only for array properties. */ int *default_array; /* Only for array properties. */
int default_array_len; int default_array_len;
char _pad[4];
int min; int min;
int max; int max;
@@ -67,6 +80,9 @@ typedef struct IDPropertyUIDataInt {
int soft_max; int soft_max;
int step; int step;
int default_value; int default_value;
int enum_items_num;
IDPropertyUIDataEnumItem *enum_items;
} IDPropertyUIDataInt; } IDPropertyUIDataInt;
/** For #IDP_UI_DATA_TYPE_BOOLEAN Use `int8_t` because DNA does not support `bool`. */ /** For #IDP_UI_DATA_TYPE_BOOLEAN Use `int8_t` because DNA does not support `bool`. */

View File

@@ -1585,6 +1585,11 @@ static void rna_def_ID_properties(BlenderRNA *brna)
RNA_def_property_flag(prop, PROP_IDPROPERTY); RNA_def_property_flag(prop, PROP_IDPROPERTY);
RNA_def_property_array(prop, 1); RNA_def_property_array(prop, 1);
/* IDP_ENUM */
prop = RNA_def_property(srna, "enum", PROP_ENUM, PROP_NONE);
RNA_def_property_enum_items(prop, rna_enum_dummy_DEFAULT_items);
RNA_def_property_flag(prop, PROP_IDPROPERTY);
/* IDP_GROUP */ /* IDP_GROUP */
prop = RNA_def_property(srna, "group", PROP_POINTER, PROP_NONE); prop = RNA_def_property(srna, "group", PROP_POINTER, PROP_NONE);
RNA_def_property_flag(prop, PROP_IDPROPERTY); RNA_def_property_flag(prop, PROP_IDPROPERTY);

View File

@@ -519,6 +519,14 @@ void rna_property_rna_or_id_get(PropertyRNA *prop,
r_prop_rna_or_id->array_len = idprop_evaluated != nullptr ? uint(idprop_evaluated->len) : 0; r_prop_rna_or_id->array_len = idprop_evaluated != nullptr ? uint(idprop_evaluated->len) : 0;
} }
else { else {
/* Special case for int properties with enum items, these are displayed as a PROP_ENUM. */
if (idprop->type == IDP_INT) {
const IDPropertyUIDataInt *ui_data_int = reinterpret_cast<IDPropertyUIDataInt *>(
idprop->ui_data);
if (ui_data_int && ui_data_int->enum_items_num > 0) {
r_prop_rna_or_id->rnaprop = &rna_PropertyGroupItem_enum;
}
}
r_prop_rna_or_id->rnaprop = typemap[int(idprop->type)]; r_prop_rna_or_id->rnaprop = typemap[int(idprop->type)];
} }
} }
@@ -560,6 +568,14 @@ PropertyRNA *rna_ensure_property(PropertyRNA *prop)
if (idprop->type == IDP_ARRAY) { if (idprop->type == IDP_ARRAY) {
return arraytypemap[int(idprop->subtype)]; return arraytypemap[int(idprop->subtype)];
} }
/* Special case for int properties with enum items, these are displayed as a PROP_ENUM. */
if (idprop->type == IDP_INT) {
const IDPropertyUIDataInt *ui_data_int = reinterpret_cast<IDPropertyUIDataInt *>(
idprop->ui_data);
if (ui_data_int && ui_data_int->enum_items_num > 0) {
return &rna_PropertyGroupItem_enum;
}
}
return typemap[int(idprop->type)]; return typemap[int(idprop->type)];
} }
} }
@@ -1501,6 +1517,39 @@ void RNA_property_enum_items_ex(bContext *C,
int *r_totitem, int *r_totitem,
bool *r_free) bool *r_free)
{ {
if (!use_static && prop->magic != RNA_MAGIC) {
const IDProperty *idprop = (IDProperty *)prop;
if (idprop->type == IDP_INT) {
IDPropertyUIDataInt *ui_data = reinterpret_cast<IDPropertyUIDataInt *>(idprop->ui_data);
int totitem = 0;
EnumPropertyItem *result = nullptr;
if (ui_data) {
for (const IDPropertyUIDataEnumItem &idprop_item :
blender::Span(ui_data->enum_items, ui_data->enum_items_num))
{
BLI_assert(idprop_item.identifier != nullptr);
BLI_assert(idprop_item.name != nullptr);
const EnumPropertyItem item = {idprop_item.value,
idprop_item.identifier,
idprop_item.icon,
idprop_item.name,
idprop_item.description ? idprop_item.description : ""};
RNA_enum_item_add(&result, &totitem, &item);
}
}
RNA_enum_item_end(&result, &totitem);
*r_item = result;
if (r_totitem) {
/* Exclude the terminator item. */
*r_totitem = totitem - 1;
}
*r_free = true;
return;
}
}
EnumPropertyRNA *eprop = (EnumPropertyRNA *)rna_ensure_property(prop); EnumPropertyRNA *eprop = (EnumPropertyRNA *)rna_ensure_property(prop);
*r_free = false; *r_free = false;

View File

@@ -66,6 +66,105 @@ static bool idprop_ui_data_update_base(IDPropertyUIData *ui_data,
return true; return true;
} }
/* Utility function for parsing ints in an if statement. */
static bool py_long_as_int(PyObject *py_long, int *r_int)
{
if (PyLong_CheckExact(py_long)) {
*r_int = int(PyLong_AS_LONG(py_long));
return true;
}
return false;
}
/**
* Similar to #enum_items_from_py, which parses enum items for RNA properties.
* This function is simpler, since it doesn't have to parse a default value or handle the case of
* enum flags (PROP_ENUM_FLAG).
*/
static bool try_parse_enum_item(PyObject *py_item, const int index, IDPropertyUIDataEnumItem &item)
{
if (!PyTuple_CheckExact(py_item)) {
return false;
}
Py_ssize_t item_size = PyTuple_GET_SIZE(py_item);
if (item_size < 3 || item_size > 5) {
return false;
}
Py_ssize_t identifier_len;
Py_ssize_t name_len;
Py_ssize_t description_len;
const char *identifier = PyUnicode_AsUTF8AndSize(PyTuple_GET_ITEM(py_item, 0), &identifier_len);
const char *name = PyUnicode_AsUTF8AndSize(PyTuple_GET_ITEM(py_item, 1), &name_len);
const char *description = PyUnicode_AsUTF8AndSize(PyTuple_GET_ITEM(py_item, 2),
&description_len);
if (!identifier || !name || !description) {
return false;
}
const char *icon_name = nullptr;
if (item_size <= 3) {
item.value = index;
}
else if (item_size == 4) {
if (!py_long_as_int(PyTuple_GET_ITEM(py_item, 3), &item.value)) {
return false;
}
}
else if (item_size == 5) {
/* Must have icon value or name. */
if (!py_long_as_int(PyTuple_GET_ITEM(py_item, 3), &item.icon) &&
!(icon_name = PyUnicode_AsUTF8(PyTuple_GET_ITEM(py_item, 3))))
{
return false;
}
if (!py_long_as_int(PyTuple_GET_ITEM(py_item, 4), &item.value)) {
return false;
}
}
item.identifier = BLI_strdup(identifier);
item.name = BLI_strdup(name);
item.description = BLI_strdup_null(description);
if (icon_name) {
RNA_enum_value_from_identifier(rna_enum_icon_items, icon_name, &item.icon);
}
return true;
}
static IDPropertyUIDataEnumItem *idprop_enum_items_from_py(PyObject *seq_fast, int &r_items_num)
{
IDPropertyUIDataEnumItem *items;
const Py_ssize_t seq_len = PySequence_Fast_GET_SIZE(seq_fast);
PyObject **seq_fast_items = PySequence_Fast_ITEMS(seq_fast);
int i;
items = MEM_cnew_array<IDPropertyUIDataEnumItem>(seq_len, __func__);
r_items_num = seq_len;
for (i = 0; i < seq_len; i++) {
IDPropertyUIDataEnumItem item = {nullptr, nullptr, nullptr, 0, 0};
PyObject *py_item = seq_fast_items[i];
if (try_parse_enum_item(py_item, i, item)) {
items[i] = item;
}
else if (py_item == Py_None) {
items[i].identifier = nullptr;
}
else {
MEM_freeN(items);
PyErr_SetString(PyExc_TypeError,
"expected a tuple containing "
"(identifier, name, description) and optionally an "
"icon name and unique number");
return nullptr;
}
}
return items;
}
/** /**
* \note The default value needs special handling because for array IDProperties it can * \note The default value needs special handling because for array IDProperties it can
* be a single value or an array, but for non-array properties it can only be a value. * be a single value or an array, but for non-array properties it can only be a value.
@@ -114,11 +213,22 @@ static bool idprop_ui_data_update_int(IDProperty *idprop, PyObject *args, PyObje
const char *description = nullptr; const char *description = nullptr;
int min, max, soft_min, soft_max, step; int min, max, soft_min, soft_max, step;
PyObject *default_value = nullptr; PyObject *default_value = nullptr;
PyObject *items = nullptr;
const char *kwlist[] = { const char *kwlist[] = {
"min", "max", "soft_min", "soft_max", "step", "default", "subtype", "description", nullptr}; "min",
"max",
"soft_min",
"soft_max",
"step",
"default",
"items",
"subtype",
"description",
nullptr,
};
if (!PyArg_ParseTupleAndKeywords(args, if (!PyArg_ParseTupleAndKeywords(args,
kwargs, kwargs,
"|$iiiiiOzz:update", "|$iiiiiOOzz:update",
(char **)kwlist, (char **)kwlist,
&min, &min,
&max, &max,
@@ -126,6 +236,7 @@ static bool idprop_ui_data_update_int(IDProperty *idprop, PyObject *args, PyObje
&soft_max, &soft_max,
&step, &step,
&default_value, &default_value,
&items,
&rna_subtype, &rna_subtype,
&description)) &description))
{ {
@@ -173,6 +284,32 @@ static bool idprop_ui_data_update_int(IDProperty *idprop, PyObject *args, PyObje
} }
} }
if (!ELEM(items, nullptr, Py_None)) {
PyObject *items_fast;
if (!(items_fast = PySequence_Fast(items, "expected a sequence of tuples for the enum items")))
{
return false;
}
int idprop_items_num = 0;
IDPropertyUIDataEnumItem *idprop_items = idprop_enum_items_from_py(items_fast,
idprop_items_num);
if (!idprop_items) {
Py_DECREF(items_fast);
return false;
}
if (!IDP_EnumItemsValidate(idprop_items, idprop_items_num, [](const char *msg) {
PyErr_SetString(PyExc_ValueError, msg);
}))
{
Py_DECREF(items_fast);
return false;
}
Py_DECREF(items_fast);
ui_data.enum_items = idprop_items;
ui_data.enum_items_num = idprop_items_num;
}
/* Write back to the property's UI data. */ /* Write back to the property's UI data. */
IDP_ui_data_free_unique_contents(&ui_data_orig->base, IDP_ui_data_type(idprop), &ui_data.base); IDP_ui_data_free_unique_contents(&ui_data_orig->base, IDP_ui_data_type(idprop), &ui_data.base);
*ui_data_orig = ui_data; *ui_data_orig = ui_data;
@@ -482,6 +619,7 @@ PyDoc_STRVAR(BPy_IDPropertyUIManager_update_doc,
"step=None, " "step=None, "
"default=None, " "default=None, "
"id_type=None, " "id_type=None, "
"items=None, "
"description=None)\n" "description=None)\n"
"\n" "\n"
" Update the RNA information of the IDProperty used for interaction and\n" " Update the RNA information of the IDProperty used for interaction and\n"
@@ -567,6 +705,27 @@ static void idprop_ui_data_to_dict_int(IDProperty *property, PyObject *dict)
PyDict_SetItemString(dict, "default", item = PyLong_FromLong(ui_data->default_value)); PyDict_SetItemString(dict, "default", item = PyLong_FromLong(ui_data->default_value));
Py_DECREF(item); Py_DECREF(item);
} }
if (ui_data->enum_items_num > 0) {
PyObject *items_list = PyList_New(ui_data->enum_items_num);
for (int i = 0; i < ui_data->enum_items_num; ++i) {
const IDPropertyUIDataEnumItem &item = ui_data->enum_items[i];
BLI_assert(item.identifier != nullptr);
BLI_assert(item.name != nullptr);
PyObject *item_tuple = PyTuple_New(5);
PyTuple_SET_ITEM(item_tuple, 0, PyUnicode_FromString(item.identifier));
PyTuple_SET_ITEM(item_tuple, 1, PyUnicode_FromString(item.name));
PyTuple_SET_ITEM(
item_tuple, 2, PyUnicode_FromString(item.description ? item.description : ""));
PyTuple_SET_ITEM(item_tuple, 3, PyLong_FromLong(item.icon));
PyTuple_SET_ITEM(item_tuple, 4, PyLong_FromLong(item.value));
PyList_SET_ITEM(items_list, i, item_tuple);
}
PyDict_SetItemString(dict, "items", items_list);
Py_DECREF(items_list);
}
} }
static void idprop_ui_data_to_dict_bool(IDProperty *property, PyObject *dict) static void idprop_ui_data_to_dict_bool(IDProperty *property, PyObject *dict)
@@ -636,17 +795,17 @@ static void idprop_ui_data_to_dict_id(IDProperty *property, PyObject *dict)
short id_type_value = ui_data->id_type; short id_type_value = ui_data->id_type;
if (id_type_value == 0) { if (id_type_value == 0) {
/* While UI exposed custom properties do not allow the 'all ID types' `0` value, in py-defined /* While UI exposed custom properties do not allow the 'all ID types' `0` value, in
* IDProperties it is accepted. So force defining a valid id_type value when this function is * py-defined IDProperties it is accepted. So force defining a valid id_type value when this
* called. */ * function is called. */
ID *id = IDP_Id(property); ID *id = IDP_Id(property);
id_type_value = id ? GS(id->name) : ID_OB; id_type_value = id ? GS(id->name) : ID_OB;
} }
const char *id_type = nullptr; const char *id_type = nullptr;
if (!RNA_enum_identifier(rna_enum_id_type_items, id_type_value, &id_type)) { if (!RNA_enum_identifier(rna_enum_id_type_items, id_type_value, &id_type)) {
/* Same fall-back as above, in case it is an unknown ID type (from a future version of Blender /* Same fall-back as above, in case it is an unknown ID type (from a future version of
* e.g.). */ * Blender e.g.). */
RNA_enum_identifier(rna_enum_id_type_items, ID_OB, &id_type); RNA_enum_identifier(rna_enum_id_type_items, ID_OB, &id_type);
} }
PyObject *item = PyUnicode_FromString(id_type); PyObject *item = PyUnicode_FromString(id_type);

View File

@@ -69,6 +69,11 @@ class TestIdPropertyCreation(TestHelper, unittest.TestCase):
self.assertEqual(self.id["a"], b"Hello World") self.assertEqual(self.id["a"], b"Hello World")
self.assertTrue(isinstance(self.id["a"], bytes)) self.assertTrue(isinstance(self.id["a"], bytes))
def test_enum(self):
self.id["a"] = 5
self.assertEqual(self.id["a"], 5)
self.assertTrue(isinstance(self.id["a"], int))
def test_sequence_double_list(self): def test_sequence_double_list(self):
mylist = [1.2, 3.4, 5.6] mylist = [1.2, 3.4, 5.6]
self.id["a"] = mylist self.id["a"] = mylist
@@ -349,6 +354,24 @@ class TestRNAData(TestHelper, unittest.TestCase):
rna_data = ui_data_test_prop_array.as_dict() rna_data = ui_data_test_prop_array.as_dict()
self.assertEqual(rna_data["default"], [1, 2]) self.assertEqual(rna_data["default"], [1, 2])
# Test RNA data for enum property.
test_object.id_properties_clear()
test_object["test_enum_prop"] = 2
ui_data_test_prop_enum = test_object.id_properties_ui("test_enum_prop")
ui_data_test_prop_enum_items = [
("TOMATOES", "Tomatoes", "Solanum lycopersicum"),
("CUCUMBERS", "Cucumbers", "Cucumis sativus"),
("RADISHES", "Radishes", "Raphanus raphanistrum"),
]
ui_data_test_prop_enum.update(items=ui_data_test_prop_enum_items)
ui_data_test_prop_enum_items_full = [
("TOMATOES", "Tomatoes", "Solanum lycopersicum", 0, 0),
("CUCUMBERS", "Cucumbers", "Cucumis sativus", 0, 1),
("RADISHES", "Radishes", "Raphanus raphanistrum", 0, 2),
]
rna_data = ui_data_test_prop_enum.as_dict()
self.assertEqual(rna_data["items"], ui_data_test_prop_enum_items_full)
if __name__ == '__main__': if __name__ == '__main__':
import sys import sys