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:
@@ -21,6 +21,7 @@ struct BlendWriter;
|
||||
struct ID;
|
||||
struct IDProperty;
|
||||
struct IDPropertyUIData;
|
||||
struct IDPropertyUIDataEnumItem;
|
||||
struct Library;
|
||||
|
||||
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_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 -------*/
|
||||
|
||||
typedef void (*IDPWalkFunc)(void *user_data, struct IDProperty *idp);
|
||||
|
||||
@@ -13,9 +13,12 @@
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
|
||||
#include <fmt/format.h>
|
||||
|
||||
#include "BLI_endian_switch.h"
|
||||
#include "BLI_listbase.h"
|
||||
#include "BLI_math_base.h"
|
||||
#include "BLI_set.hh"
|
||||
#include "BLI_string.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;
|
||||
IDPropertyUIDataInt *dst = (IDPropertyUIDataInt *)dst_ui_data;
|
||||
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;
|
||||
}
|
||||
case IDP_UI_DATA_TYPE_BOOLEAN: {
|
||||
@@ -425,6 +436,78 @@ void IDP_FreeString(IDProperty *prop)
|
||||
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) {
|
||||
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;
|
||||
}
|
||||
case IDP_UI_DATA_TYPE_BOOLEAN: {
|
||||
@@ -1055,6 +1141,7 @@ void IDP_ui_data_free(IDProperty *prop)
|
||||
case IDP_UI_DATA_TYPE_INT: {
|
||||
IDPropertyUIDataInt *ui_data_int = (IDPropertyUIDataInt *)prop->ui_data;
|
||||
MEM_SAFE_FREE(ui_data_int->default_array);
|
||||
IDP_int_ui_data_free_enum_items(ui_data_int);
|
||||
break;
|
||||
}
|
||||
case IDP_UI_DATA_TYPE_BOOLEAN: {
|
||||
@@ -1198,6 +1285,14 @@ static void write_ui_data(const IDProperty *prop, BlendWriter *writer)
|
||||
BLO_write_int32_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);
|
||||
break;
|
||||
}
|
||||
@@ -1325,6 +1420,13 @@ static void read_ui_data(IDProperty *prop, BlendDataReader *reader)
|
||||
BLO_read_int32_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;
|
||||
}
|
||||
case IDP_UI_DATA_TYPE_BOOLEAN: {
|
||||
|
||||
@@ -140,6 +140,11 @@ struct DictionaryEntryParser {
|
||||
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
|
||||
{
|
||||
return get_array(IDP_KEY_VALUE);
|
||||
@@ -206,6 +211,21 @@ struct DictionaryEntryParser {
|
||||
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
|
||||
{
|
||||
const DictionaryValue::LookupValue *value_ptr = lookup.lookup_ptr(key);
|
||||
|
||||
@@ -100,6 +100,9 @@ static void idp_repr_fn_recursive(ReprState *state, const IDProperty *prop)
|
||||
break;
|
||||
}
|
||||
case IDP_INT: {
|
||||
if (const IDPropertyUIDataEnumItem *item = IDP_EnumItemFind(prop)) {
|
||||
STR_APPEND_FMT("%s", item->name);
|
||||
}
|
||||
STR_APPEND_FMT("%d", IDP_Int(prop));
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -80,6 +80,7 @@ enum class eValueType {
|
||||
Boolean,
|
||||
Double,
|
||||
Dictionary,
|
||||
Enum,
|
||||
};
|
||||
|
||||
class Value;
|
||||
@@ -89,6 +90,7 @@ template<typename T, eValueType V> class PrimitiveValue;
|
||||
using IntValue = PrimitiveValue<int64_t, eValueType::Int>;
|
||||
using DoubleValue = PrimitiveValue<double, eValueType::Double>;
|
||||
using BooleanValue = PrimitiveValue<bool, eValueType::Boolean>;
|
||||
using EnumValue = PrimitiveValue<int, eValueType::Enum>;
|
||||
class ArrayValue;
|
||||
|
||||
/**
|
||||
@@ -146,6 +148,12 @@ class Value {
|
||||
*/
|
||||
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.
|
||||
* Will return nullptr when it is a different type.
|
||||
|
||||
@@ -41,6 +41,14 @@ const BooleanValue *Value::as_boolean_value() const
|
||||
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
|
||||
{
|
||||
if (type_ != eValueType::Array) {
|
||||
@@ -121,6 +129,12 @@ static void convert_to_json(nlohmann::ordered_json &j, const Value &value)
|
||||
|
||||
case eValueType::Double: {
|
||||
j = value.as_double_value()->value();
|
||||
break;
|
||||
}
|
||||
|
||||
case eValueType::Enum: {
|
||||
j = value.as_enum_value()->value();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,12 +54,25 @@ typedef struct IDPropertyUIData {
|
||||
char _pad[4];
|
||||
} 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 */
|
||||
typedef struct IDPropertyUIDataInt {
|
||||
IDPropertyUIData base;
|
||||
int *default_array; /* Only for array properties. */
|
||||
int default_array_len;
|
||||
char _pad[4];
|
||||
|
||||
int min;
|
||||
int max;
|
||||
@@ -67,6 +80,9 @@ typedef struct IDPropertyUIDataInt {
|
||||
int soft_max;
|
||||
int step;
|
||||
int default_value;
|
||||
|
||||
int enum_items_num;
|
||||
IDPropertyUIDataEnumItem *enum_items;
|
||||
} IDPropertyUIDataInt;
|
||||
|
||||
/** For #IDP_UI_DATA_TYPE_BOOLEAN Use `int8_t` because DNA does not support `bool`. */
|
||||
|
||||
@@ -1585,6 +1585,11 @@ static void rna_def_ID_properties(BlenderRNA *brna)
|
||||
RNA_def_property_flag(prop, PROP_IDPROPERTY);
|
||||
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 */
|
||||
prop = RNA_def_property(srna, "group", PROP_POINTER, PROP_NONE);
|
||||
RNA_def_property_flag(prop, PROP_IDPROPERTY);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
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)];
|
||||
}
|
||||
}
|
||||
@@ -560,6 +568,14 @@ PropertyRNA *rna_ensure_property(PropertyRNA *prop)
|
||||
if (idprop->type == IDP_ARRAY) {
|
||||
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)];
|
||||
}
|
||||
}
|
||||
@@ -1501,6 +1517,39 @@ void RNA_property_enum_items_ex(bContext *C,
|
||||
int *r_totitem,
|
||||
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);
|
||||
|
||||
*r_free = false;
|
||||
|
||||
@@ -66,6 +66,105 @@ static bool idprop_ui_data_update_base(IDPropertyUIData *ui_data,
|
||||
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
|
||||
* 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;
|
||||
int min, max, soft_min, soft_max, step;
|
||||
PyObject *default_value = nullptr;
|
||||
PyObject *items = nullptr;
|
||||
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,
|
||||
kwargs,
|
||||
"|$iiiiiOzz:update",
|
||||
"|$iiiiiOOzz:update",
|
||||
(char **)kwlist,
|
||||
&min,
|
||||
&max,
|
||||
@@ -126,6 +236,7 @@ static bool idprop_ui_data_update_int(IDProperty *idprop, PyObject *args, PyObje
|
||||
&soft_max,
|
||||
&step,
|
||||
&default_value,
|
||||
&items,
|
||||
&rna_subtype,
|
||||
&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. */
|
||||
IDP_ui_data_free_unique_contents(&ui_data_orig->base, IDP_ui_data_type(idprop), &ui_data.base);
|
||||
*ui_data_orig = ui_data;
|
||||
@@ -482,6 +619,7 @@ PyDoc_STRVAR(BPy_IDPropertyUIManager_update_doc,
|
||||
"step=None, "
|
||||
"default=None, "
|
||||
"id_type=None, "
|
||||
"items=None, "
|
||||
"description=None)\n"
|
||||
"\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));
|
||||
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)
|
||||
@@ -636,17 +795,17 @@ static void idprop_ui_data_to_dict_id(IDProperty *property, PyObject *dict)
|
||||
|
||||
short id_type_value = ui_data->id_type;
|
||||
if (id_type_value == 0) {
|
||||
/* While UI exposed custom properties do not allow the 'all ID types' `0` value, in py-defined
|
||||
* IDProperties it is accepted. So force defining a valid id_type value when this function is
|
||||
* called. */
|
||||
/* While UI exposed custom properties do not allow the 'all ID types' `0` value, in
|
||||
* py-defined IDProperties it is accepted. So force defining a valid id_type value when this
|
||||
* function is called. */
|
||||
ID *id = IDP_Id(property);
|
||||
id_type_value = id ? GS(id->name) : ID_OB;
|
||||
}
|
||||
|
||||
const char *id_type = nullptr;
|
||||
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
|
||||
* e.g.). */
|
||||
/* Same fall-back as above, in case it is an unknown ID type (from a future version of
|
||||
* Blender e.g.). */
|
||||
RNA_enum_identifier(rna_enum_id_type_items, ID_OB, &id_type);
|
||||
}
|
||||
PyObject *item = PyUnicode_FromString(id_type);
|
||||
|
||||
@@ -69,6 +69,11 @@ class TestIdPropertyCreation(TestHelper, unittest.TestCase):
|
||||
self.assertEqual(self.id["a"], b"Hello World")
|
||||
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):
|
||||
mylist = [1.2, 3.4, 5.6]
|
||||
self.id["a"] = mylist
|
||||
@@ -349,6 +354,24 @@ class TestRNAData(TestHelper, unittest.TestCase):
|
||||
rna_data = ui_data_test_prop_array.as_dict()
|
||||
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__':
|
||||
import sys
|
||||
|
||||
Reference in New Issue
Block a user