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 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);
|
||||||
|
|||||||
@@ -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: {
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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`. */
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user