Custom Properties: Add boolean type

A proper boolean custom property type is commonly requested. This
commit simply adds a new `IDP_BOOLEAN` type that can be used for
boolean and boolean array custom properties. This can also be used
for exposing boolean node sockets in the geometry nodes modifier.

I've just extended the places existing IDProperty types are used, and
tested with the custom property edit operator and the python console.
Adding another IDProperty type is a straightforward extension of the
existing design.

Differential Revision: https://developer.blender.org/D12815
This commit is contained in:
Hans Goudey 2023-01-13 12:31:27 -06:00
parent 4961e5f91d
commit ef68a37e5d
Notes: blender-bot 2023-03-02 15:18:01 +01:00
Referenced by commit 2ea47e0def, Geometry Nodes: Use checkbox for exposed boolean sockets
Referenced by issue #103911, Issues with boolean custom property
Referenced by issue #85306, Use boolean in the modifier for exposed boolean node group inputs
Referenced by pull request #104995, Fix #104902: Use forward compatible integers for boolean RNA properties
Referenced by commit 720dbea429, Fix #104902: Use forward compatible integers for boolean RNA properties
Referenced by commit d90d6f8674, Fix #105259: Copy & paste boolean custom property as driver doesn't work
Referenced by pull request #105370, Fix #105278: IDProperty UI missing library overridable toggle
Referenced by issue #105278, Regression: IDProperty UI missing library overridable toggle for types other than float or int
Referenced by commit 422f3d0b0f, Fix #105278: IDProperty UI missing library overridable toggle
16 changed files with 457 additions and 39 deletions

View File

@ -81,25 +81,26 @@ def rna_idprop_ui_create(
):
"""Create and initialize a custom property with limits, defaults and other settings."""
# Assign the value
item[prop] = default
rna_idprop_ui_prop_update(item, prop)
ui_data = item.id_properties_ui(prop)
proptype, _ = rna_idprop_value_item_type(default)
# Sanitize limits
if proptype is bool:
min = soft_min = False
max = soft_max = True
ui_data = item.id_properties_ui(prop)
ui_data.update(
description=description,
default=default,
)
return
if soft_min is None:
soft_min = min
if soft_max is None:
soft_max = max
# Assign the value
item[prop] = default
rna_idprop_ui_prop_update(item, prop)
# Update the UI settings.
ui_data = item.id_properties_ui(prop)
ui_data.update(
subtype=subtype,
min=min,

View File

@ -14,6 +14,7 @@ from bpy.props import (
FloatProperty,
IntProperty,
StringProperty,
BoolVectorProperty,
IntVectorProperty,
FloatVectorProperty,
)
@ -1325,6 +1326,8 @@ rna_custom_property_type_items = (
('FLOAT_ARRAY', "Float Array", "An array of floating-point values"),
('INT', "Integer", "A single integer"),
('INT_ARRAY', "Integer Array", "An array of integers"),
('BOOL', "Boolean", "A true or false value"),
('BOOL_ARRAY', "Boolean Array", "An array of true or false values"),
('STRING', "String", "A string value"),
('PYTHON', "Python", "Edit a python value directly, for unsupported property types"),
)
@ -1410,6 +1413,14 @@ class WM_OT_properties_edit(Operator):
default=1,
)
# Boolean properties.
# This property stores values for both array and non-array properties.
default_bool: BoolVectorProperty(
name="Default Value",
size=32,
)
# Float properties.
# This property stores values for both array and non-array properties.
@ -1520,6 +1531,10 @@ class WM_OT_properties_edit(Operator):
if is_array:
return 'FLOAT_ARRAY'
return 'FLOAT'
elif prop_type == bool:
if is_array:
return 'BOOL_ARRAY'
return 'BOOL'
elif prop_type == str:
if is_array:
return 'PYTHON'
@ -1571,8 +1586,10 @@ class WM_OT_properties_edit(Operator):
self.default_int = self._convert_new_value_array(rna_data["default"], int, 32)
elif self.property_type == 'STRING':
self.default_string = rna_data["default"]
elif self.property_type in {'BOOL', 'BOOL_ARRAY'}:
self.default_int = self._convert_new_value_array(rna_data["default"], bool, 32)
if self.property_type in {'FLOAT_ARRAY', 'INT_ARRAY'}:
if self.property_type in {'FLOAT_ARRAY', 'INT_ARRAY', 'BOOL_ARRAY'}:
self.array_length = len(item[name])
# The dictionary does not contain the description if it was empty.
@ -1591,16 +1608,26 @@ class WM_OT_properties_edit(Operator):
if prop_type_new == 'FLOAT':
return self._convert_new_value_single(item[name_old], float)
if prop_type_new == 'BOOL':
return self._convert_new_value_single(item[name_old], bool)
if prop_type_new == 'INT_ARRAY':
prop_type_old = self.get_property_type(item, name_old)
if prop_type_old in {'INT', 'FLOAT', 'INT_ARRAY', 'FLOAT_ARRAY'}:
if prop_type_old in {'INT', 'FLOAT', 'INT_ARRAY', 'FLOAT_ARRAY', 'BOOL_ARRAY'}:
return self._convert_new_value_array(item[name_old], int, self.array_length)
if prop_type_new == 'FLOAT_ARRAY':
prop_type_old = self.get_property_type(item, name_old)
if prop_type_old in {'INT', 'FLOAT', 'FLOAT_ARRAY', 'INT_ARRAY'}:
if prop_type_old in {'INT', 'FLOAT', 'FLOAT_ARRAY', 'INT_ARRAY', 'BOOL_ARRAY'}:
return self._convert_new_value_array(item[name_old], float, self.array_length)
if prop_type_new == 'BOOL_ARRAY':
prop_type_old = self.get_property_type(item, name_old)
if prop_type_old in {'INT', 'FLOAT', 'FLOAT_ARRAY', 'INT_ARRAY'}:
return self._convert_new_value_array(item[name_old], bool, self.array_length)
else:
return [False] * self.array_length
if prop_type_new == 'STRING':
return self.convert_custom_property_to_string(item, name_old)
@ -1622,6 +1649,9 @@ class WM_OT_properties_edit(Operator):
self.soft_min_float = float(self.soft_min_int)
self.soft_max_float = float(self.soft_max_int)
self.default_float = self._convert_new_value_array(self.default_int, float, 32)
elif prop_type_new in {'BOOL', 'BOOL_ARRAY'} and prop_type_old in {'INT', 'INT_ARRAY'}:
self.default_bool = self._convert_new_value_array(self.default_int, bool, 32)
# Don't convert between string and float/int defaults here, it's not expected like the other conversions.
# Fill the property's UI data with the values chosen in the operator.
@ -1637,6 +1667,12 @@ class WM_OT_properties_edit(Operator):
default=self.default_int[0] if prop_type_new == 'INT' else self.default_int[:self.array_length],
description=self.description,
)
if prop_type_new in {'BOOL', 'BOOL_ARRAY'}:
ui_data = item.id_properties_ui(name)
ui_data.update(
default=self.default_bool[0] if prop_type_new == 'BOOL' else self.default_bool[:self.array_length],
description=self.description,
)
elif prop_type_new in {'FLOAT', 'FLOAT_ARRAY'}:
ui_data = item.id_properties_ui(name)
ui_data.update(
@ -1879,6 +1915,15 @@ class WM_OT_properties_edit(Operator):
col.prop(self, "soft_max_int", text="Max")
layout.prop(self, "step_int")
elif self.property_type in {'BOOL', 'BOOL_ARRAY'}:
if self.property_type == 'BOOL_ARRAY':
layout.prop(self, "array_length")
col = layout.column(align=True)
col.prop(self, "default_bool", index=0, text="Default")
for i in range(1, self.array_length):
col.prop(self, "default_bool", index=i, text=" ")
else:
layout.prop(self, "default_bool", index=0)
elif self.property_type == 'STRING':
layout.prop(self, "default_string")

View File

@ -243,6 +243,7 @@ void IDP_ClearProperty(struct IDProperty *prop);
void IDP_Reset(struct IDProperty *prop, const struct IDProperty *reference);
#define IDP_Int(prop) ((prop)->data.val)
#define IDP_Bool(prop) ((prop)->data.val)
#define IDP_Array(prop) ((prop)->data.pointer)
/* C11 const correctness for casts */
#if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L)
@ -334,6 +335,8 @@ typedef enum eIDPropertyUIDataType {
IDP_UI_DATA_TYPE_STRING = 2,
/** IDP_ID. */
IDP_UI_DATA_TYPE_ID = 3,
/** IDP_BOOLEAN or IDP_ARRAY with subtype IDP_BOOLEAN. */
IDP_UI_DATA_TYPE_BOOLEAN = 4,
} eIDPropertyUIDataType;
bool IDP_ui_data_supported(const struct IDProperty *prop);

View File

@ -50,6 +50,8 @@ static size_t idp_size_table[] = {
sizeof(ListBase), /* Group type. */
sizeof(void *),
sizeof(double),
0,
sizeof(int8_t), /* Boolean type. */
};
/* -------------------------------------------------------------------- */
@ -272,6 +274,12 @@ IDPropertyUIData *IDP_ui_data_copy(const IDProperty *prop)
dst->default_array = MEM_dupallocN(src->default_array);
break;
}
case IDP_UI_DATA_TYPE_BOOLEAN: {
const IDPropertyUIDataBool *src = (const IDPropertyUIDataBool *)prop->ui_data;
IDPropertyUIDataBool *dst = (IDPropertyUIDataBool *)dst_ui_data;
dst->default_array = MEM_dupallocN(src->default_array);
break;
}
case IDP_UI_DATA_TYPE_FLOAT: {
const IDPropertyUIDataFloat *src = (const IDPropertyUIDataFloat *)prop->ui_data;
IDPropertyUIDataFloat *dst = (IDPropertyUIDataFloat *)dst_ui_data;
@ -497,6 +505,7 @@ void IDP_SyncGroupValues(IDProperty *dest, const IDProperty *src)
case IDP_INT:
case IDP_FLOAT:
case IDP_DOUBLE:
case IDP_BOOLEAN:
other->data = prop->data;
break;
case IDP_GROUP:
@ -708,6 +717,8 @@ int IDP_coerce_to_int_or_zero(const IDProperty *prop)
return (int)IDP_Double(prop);
case IDP_FLOAT:
return (int)IDP_Float(prop);
case IDP_BOOLEAN:
return (int)IDP_Bool(prop);
default:
return 0;
}
@ -722,6 +733,8 @@ double IDP_coerce_to_double_or_zero(const IDProperty *prop)
return (double)IDP_Float(prop);
case IDP_INT:
return (double)IDP_Int(prop);
case IDP_BOOLEAN:
return (double)IDP_Bool(prop);
default:
return 0.0;
}
@ -736,6 +749,8 @@ float IDP_coerce_to_float_or_zero(const IDProperty *prop)
return (float)IDP_Double(prop);
case IDP_INT:
return (float)IDP_Int(prop);
case IDP_BOOLEAN:
return (float)IDP_Bool(prop);
default:
return 0.0f;
}
@ -826,6 +841,8 @@ bool IDP_EqualsProperties_ex(IDProperty *prop1, IDProperty *prop2, const bool is
return (IDP_Float(prop1) == IDP_Float(prop2));
case IDP_DOUBLE:
return (IDP_Double(prop1) == IDP_Double(prop2));
case IDP_BOOLEAN:
return (IDP_Bool(prop1) == IDP_Bool(prop2));
case IDP_STRING: {
return ((prop1->len == prop2->len) &&
STREQLEN(IDP_String(prop1), IDP_String(prop2), (size_t)prop1->len));
@ -899,9 +916,12 @@ IDProperty *IDP_New(const char type, const IDPropertyTemplate *val, const char *
prop = MEM_callocN(sizeof(IDProperty), "IDProperty double");
*(double *)&prop->data.val = val->d;
break;
case IDP_BOOLEAN:
prop = MEM_callocN(sizeof(IDProperty), "IDProperty boolean");
prop->data.val = (bool)val->i;
break;
case IDP_ARRAY: {
/* for now, we only support float and int and double arrays */
if (ELEM(val->array.type, IDP_FLOAT, IDP_INT, IDP_DOUBLE, IDP_GROUP)) {
if (ELEM(val->array.type, IDP_FLOAT, IDP_INT, IDP_DOUBLE, IDP_GROUP, IDP_BOOLEAN)) {
prop = MEM_callocN(sizeof(IDProperty), "IDProperty array");
prop->subtype = val->array.type;
if (val->array.len) {
@ -1004,6 +1024,14 @@ void IDP_ui_data_free_unique_contents(IDPropertyUIData *ui_data,
}
break;
}
case IDP_UI_DATA_TYPE_BOOLEAN: {
const IDPropertyUIDataBool *other_bool = (const IDPropertyUIDataBool *)other;
IDPropertyUIDataBool *ui_data_bool = (IDPropertyUIDataBool *)ui_data;
if (ui_data_bool->default_array != other_bool->default_array) {
MEM_SAFE_FREE(ui_data_bool->default_array);
}
break;
}
case IDP_UI_DATA_TYPE_FLOAT: {
const IDPropertyUIDataFloat *other_float = (const IDPropertyUIDataFloat *)other;
IDPropertyUIDataFloat *ui_data_float = (IDPropertyUIDataFloat *)ui_data;
@ -1034,6 +1062,11 @@ void IDP_ui_data_free(IDProperty *prop)
MEM_SAFE_FREE(ui_data_int->default_array);
break;
}
case IDP_UI_DATA_TYPE_BOOLEAN: {
IDPropertyUIDataBool *ui_data_bool = (IDPropertyUIDataBool *)prop->ui_data;
MEM_SAFE_FREE(ui_data_bool->default_array);
break;
}
case IDP_UI_DATA_TYPE_FLOAT: {
IDPropertyUIDataFloat *ui_data_float = (IDPropertyUIDataFloat *)prop->ui_data;
MEM_SAFE_FREE(ui_data_float->default_array);
@ -1173,6 +1206,16 @@ static void write_ui_data(const IDProperty *prop, BlendWriter *writer)
BLO_write_struct(writer, IDPropertyUIDataInt, ui_data);
break;
}
case IDP_UI_DATA_TYPE_BOOLEAN: {
IDPropertyUIDataBool *ui_data_bool = (IDPropertyUIDataBool *)ui_data;
if (prop->type == IDP_ARRAY) {
BLO_write_int8_array(writer,
(uint)ui_data_bool->default_array_len,
(const int8_t *)ui_data_bool->default_array);
}
BLO_write_struct(writer, IDPropertyUIDataBool, ui_data);
break;
}
case IDP_UI_DATA_TYPE_FLOAT: {
IDPropertyUIDataFloat *ui_data_float = (IDPropertyUIDataFloat *)ui_data;
if (prop->type == IDP_ARRAY) {
@ -1285,6 +1328,14 @@ static void read_ui_data(IDProperty *prop, BlendDataReader *reader)
}
break;
}
case IDP_UI_DATA_TYPE_BOOLEAN: {
IDPropertyUIDataBool *ui_data_bool = (IDPropertyUIDataBool *)prop->ui_data;
if (prop->type == IDP_ARRAY) {
BLO_read_int8_array(
reader, ui_data_bool->default_array_len, (int8_t **)&ui_data_bool->default_array);
}
break;
}
case IDP_UI_DATA_TYPE_FLOAT: {
IDPropertyUIDataFloat *ui_data_float = (IDPropertyUIDataFloat *)prop->ui_data;
if (prop->type == IDP_ARRAY) {
@ -1336,10 +1387,13 @@ static void IDP_DirectLinkArray(IDProperty *prop, BlendDataReader *reader)
else if (prop->subtype == IDP_DOUBLE) {
BLO_read_double_array(reader, prop->len, (double **)&prop->data.pointer);
}
else {
else if (ELEM(prop->subtype, IDP_INT, IDP_FLOAT)) {
/* also used for floats */
BLO_read_int32_array(reader, prop->len, (int **)&prop->data.pointer);
}
else if (prop->subtype == IDP_BOOLEAN) {
BLO_read_int8_array(reader, prop->len, (int8_t **)&prop->data.pointer);
}
}
static void IDP_DirectLinkString(IDProperty *prop, BlendDataReader *reader)
@ -1392,6 +1446,7 @@ static void IDP_DirectLinkProperty(IDProperty *prop, BlendDataReader *reader)
break;
case IDP_INT:
case IDP_FLOAT:
case IDP_BOOLEAN:
case IDP_ID:
break; /* Nothing special to do here. */
default:
@ -1502,6 +1557,9 @@ eIDPropertyUIDataType IDP_ui_data_type(const IDProperty *prop)
(prop->type == IDP_ARRAY && ELEM(prop->subtype, IDP_FLOAT, IDP_DOUBLE))) {
return IDP_UI_DATA_TYPE_FLOAT;
}
if (prop->type == IDP_BOOLEAN || (prop->type == IDP_ARRAY && prop->subtype == IDP_BOOLEAN)) {
return IDP_UI_DATA_TYPE_BOOLEAN;
}
return IDP_UI_DATA_TYPE_UNSUPPORTED;
}
@ -1536,6 +1594,11 @@ IDPropertyUIData *IDP_ui_data_ensure(IDProperty *prop)
prop->ui_data = (IDPropertyUIData *)ui_data;
break;
}
case IDP_UI_DATA_TYPE_BOOLEAN: {
IDPropertyUIDataBool *ui_data = MEM_callocN(sizeof(IDPropertyUIDataBool), __func__);
prop->ui_data = (IDPropertyUIData *)ui_data;
break;
}
case IDP_UI_DATA_TYPE_FLOAT: {
IDPropertyUIDataFloat *ui_data = MEM_callocN(sizeof(IDPropertyUIDataFloat), __func__);
ui_data->min = -FLT_MAX;

View File

@ -111,6 +111,10 @@ static void idp_repr_fn_recursive(struct ReprState *state, const IDProperty *pro
STR_APPEND_FMT("%g", IDP_Double(prop));
break;
}
case IDP_BOOLEAN: {
STR_APPEND_FMT("%s", IDP_Bool(prop) ? "True" : "False");
break;
}
case IDP_ARRAY: {
STR_APPEND_STR("[");
switch (prop->subtype) {
@ -138,6 +142,14 @@ static void idp_repr_fn_recursive(struct ReprState *state, const IDProperty *pro
STR_APPEND_FMT("%g", *v);
}
break;
case IDP_BOOLEAN:
for (const double *v = prop->data.pointer, *v_end = v + prop->len; v != v_end; v++) {
if (v != prop->data.pointer) {
STR_APPEND_STR(", ");
}
STR_APPEND_FMT("%s", IDP_Bool(prop) ? "True" : "False");
}
break;
}
STR_APPEND_STR("]");
break;

View File

@ -162,6 +162,7 @@ void blo_write_id_struct(BlendWriter *writer,
* Write raw data.
*/
void BLO_write_raw(BlendWriter *writer, size_t size_in_bytes, const void *data_ptr);
void BLO_write_int8_array(BlendWriter *writer, uint num, const int8_t *data_ptr);
void BLO_write_int32_array(BlendWriter *writer, uint num, const int32_t *data_ptr);
void BLO_write_uint32_array(BlendWriter *writer, uint num, const uint32_t *data_ptr);
void BLO_write_float_array(BlendWriter *writer, uint num, const float *data_ptr);
@ -228,6 +229,7 @@ void BLO_read_list(BlendDataReader *reader, struct ListBase *list);
/* Update data pointers and correct byte-order if necessary. */
void BLO_read_int8_array(BlendDataReader *reader, int array_size, int8_t **ptr_p);
void BLO_read_int32_array(BlendDataReader *reader, int array_size, int32_t **ptr_p);
void BLO_read_uint32_array(BlendDataReader *reader, int array_size, uint32_t **ptr_p);
void BLO_read_float_array(BlendDataReader *reader, int array_size, float **ptr_p);

View File

@ -4976,6 +4976,11 @@ void BLO_read_int32_array(BlendDataReader *reader, int array_size, int32_t **ptr
}
}
void BLO_read_int8_array(BlendDataReader *reader, int /*array_size*/, int8_t **ptr_p)
{
BLO_read_data_address(reader, ptr_p);
}
void BLO_read_uint32_array(BlendDataReader *reader, int array_size, uint32_t **ptr_p)
{
BLO_read_data_address(reader, ptr_p);

View File

@ -248,6 +248,7 @@ static void version_idproperty_ui_data(IDProperty *idprop_group)
case IDP_UI_DATA_TYPE_FLOAT:
version_idproperty_move_data_float((IDPropertyUIDataFloat *)ui_data, prop_ui_data);
break;
case IDP_UI_DATA_TYPE_BOOLEAN:
case IDP_UI_DATA_TYPE_UNSUPPORTED:
BLI_assert_unreachable();
break;

View File

@ -1639,6 +1639,11 @@ int BLO_get_struct_id_by_name(BlendWriter *writer, const char *struct_name)
return struct_id;
}
void BLO_write_int8_array(BlendWriter *writer, uint num, const int8_t *data_ptr)
{
BLO_write_raw(writer, sizeof(int8_t) * size_t(num), data_ptr);
}
void BLO_write_int32_array(BlendWriter *writer, uint num, const int32_t *data_ptr)
{
BLO_write_raw(writer, sizeof(int32_t) * size_t(num), data_ptr);

View File

@ -70,6 +70,9 @@ void CustomPropertiesExporter::write(const IDProperty *id_property)
set_scalar_property<ODoubleArrayProperty, double>(id_property->name,
IDP_Double(id_property));
break;
case IDP_BOOLEAN:
set_scalar_property<OBoolArrayProperty, bool>(id_property->name, IDP_Bool(id_property));
break;
case IDP_ARRAY:
write_array(id_property);
break;
@ -100,6 +103,11 @@ void CustomPropertiesExporter::write_array(const IDProperty *id_property)
set_array_property<ODoubleArrayProperty, double>(id_property->name, array, id_property->len);
break;
}
case IDP_BOOLEAN: {
const int8_t *array = static_cast<const int8_t *>(IDP_Array(id_property));
set_array_property<OBoolArrayProperty, int8_t>(id_property->name, array, id_property->len);
break;
}
}
}
@ -165,7 +173,7 @@ void CustomPropertiesExporter::write_idparray_of_numbers(const IDProperty *idp_a
BLI_assert(idp_rows[0].type == IDP_ARRAY);
const int subtype = idp_rows[0].subtype;
if (!ELEM(subtype, IDP_INT, IDP_FLOAT, IDP_DOUBLE)) {
if (!ELEM(subtype, IDP_INT, IDP_FLOAT, IDP_DOUBLE, IDP_BOOLEAN)) {
/* Non-numerical types are not supported. */
return;
}
@ -181,6 +189,9 @@ void CustomPropertiesExporter::write_idparray_of_numbers(const IDProperty *idp_a
case IDP_DOUBLE:
write_idparray_flattened_typed<ODoubleArrayProperty, double>(idp_array);
break;
case IDP_BOOLEAN:
write_idparray_flattened_typed<OBoolArrayProperty, int8_t>(idp_array);
break;
}
}
@ -192,7 +203,7 @@ void CustomPropertiesExporter::write_idparray_flattened_typed(const IDProperty *
const IDProperty *idp_rows = (IDProperty *)IDP_Array(idp_array);
BLI_assert(idp_rows[0].type == IDP_ARRAY);
BLI_assert(ELEM(idp_rows[0].subtype, IDP_INT, IDP_FLOAT, IDP_DOUBLE));
BLI_assert(ELEM(idp_rows[0].subtype, IDP_INT, IDP_FLOAT, IDP_DOUBLE, IDP_BOOLEAN));
const uint64_t num_rows = idp_array->len;
std::vector<BlenderValueType> matrix_values;

View File

@ -718,6 +718,9 @@ float bc_get_property(Bone *bone, std::string key, float def)
case IDP_DOUBLE:
result = float(IDP_Double(property));
break;
case IDP_BOOLEAN:
result = (float)(IDP_Bool(property));
break;
default:
result = def;
}

View File

@ -68,6 +68,16 @@ typedef struct IDPropertyUIDataInt {
int default_value;
} IDPropertyUIDataInt;
/* IDP_UI_DATA_TYPE_BOOLEAN Use "int8_t" because DNA does not support "bool". */
typedef struct IDPropertyUIDataBool {
IDPropertyUIData base;
int8_t *default_array; /* Only for array properties. */
int default_array_len;
char _pad[3];
int8_t default_value;
} IDPropertyUIDataBool;
/* IDP_UI_DATA_TYPE_FLOAT */
typedef struct IDPropertyUIDataFloat {
IDPropertyUIData base;
@ -142,8 +152,13 @@ typedef enum eIDPropertyType {
IDP_ID = 7,
IDP_DOUBLE = 8,
IDP_IDPARRAY = 9,
/**
* True or false value, backed by an `int8_t` underlying type for arrays. Values are expected to
* be 0 or 1.
*/
IDP_BOOLEAN = 10,
} eIDPropertyType;
#define IDP_NUMTYPES 10
#define IDP_NUMTYPES 11
/** Used by some IDP utils, keep values in sync with type enum above. */
enum {
@ -155,6 +170,7 @@ enum {
IDP_TYPE_FILTER_ID = 1 << 7,
IDP_TYPE_FILTER_DOUBLE = 1 << 8,
IDP_TYPE_FILTER_IDPARRAY = 1 << 9,
IDP_TYPE_FILTER_BOOLEAN = 1 << 10,
};
/*->subtype */

View File

@ -1485,6 +1485,15 @@ static void rna_def_ID_properties(BlenderRNA *brna)
RNA_def_property_flag(prop, PROP_IDPROPERTY);
RNA_def_property_array(prop, 1);
/* IDP_BOOLEAN */
prop = RNA_def_property(srna, "bool", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_flag(prop, PROP_IDPROPERTY);
RNA_def_property_array(prop, 1);
prop = RNA_def_property(srna, "bool_array", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_flag(prop, PROP_IDPROPERTY);
RNA_def_property_array(prop, 1);
/* IDP_GROUP */
prop = RNA_def_property(srna, "group", PROP_POINTER, PROP_NONE);
RNA_def_property_flag(prop, PROP_IDPROPERTY);

View File

@ -369,6 +369,9 @@ static bool rna_idproperty_verify_valid(PointerRNA *ptr, PropertyRNA *prop, IDPr
if (idprop->subtype == IDP_FLOAT && prop->type != PROP_FLOAT) {
return false;
}
if (idprop->subtype == IDP_BOOLEAN && prop->type != PROP_BOOLEAN) {
return false;
}
if (idprop->subtype == IDP_INT && !ELEM(prop->type, PROP_BOOLEAN, PROP_INT, PROP_ENUM)) {
return false;
}
@ -379,6 +382,11 @@ static bool rna_idproperty_verify_valid(PointerRNA *ptr, PropertyRNA *prop, IDPr
return false;
}
break;
case IDP_BOOLEAN:
if (prop->type != PROP_BOOLEAN) {
return false;
}
break;
case IDP_FLOAT:
case IDP_DOUBLE:
if (prop->type != PROP_FLOAT) {
@ -414,6 +422,7 @@ static PropertyRNA *typemap[IDP_NUMTYPES] = {
&rna_PropertyGroupItem_id,
&rna_PropertyGroupItem_double,
&rna_PropertyGroupItem_idp_array,
&rna_PropertyGroupItem_bool,
};
static PropertyRNA *arraytypemap[IDP_NUMTYPES] = {
@ -426,6 +435,8 @@ static PropertyRNA *arraytypemap[IDP_NUMTYPES] = {
&rna_PropertyGroupItem_collection,
NULL,
&rna_PropertyGroupItem_double_array,
NULL,
(PropertyRNA *)&rna_PropertyGroupItem_bool_array,
};
void rna_property_rna_or_id_get(PropertyRNA *prop,
@ -2160,7 +2171,7 @@ bool RNA_property_boolean_get(PointerRNA *ptr, PropertyRNA *prop)
BLI_assert(RNA_property_array_check(prop) == false);
if ((idprop = rna_idproperty_check(&prop, ptr))) {
value = IDP_Int(idprop) != 0;
value = IDP_Bool(idprop);
}
else if (bprop->get) {
value = bprop->get(ptr);
@ -2190,7 +2201,7 @@ void RNA_property_boolean_set(PointerRNA *ptr, PropertyRNA *prop, bool value)
BLI_assert(ELEM(value, true, false));
if ((idprop = rna_idproperty_check(&prop, ptr))) {
IDP_Int(idprop) = (int)value;
IDP_Bool(idprop) = value;
rna_idproperty_touch(idprop);
}
else if (bprop->set) {
@ -2207,7 +2218,7 @@ void RNA_property_boolean_set(PointerRNA *ptr, PropertyRNA *prop, bool value)
group = RNA_struct_idprops(ptr, 1);
if (group) {
IDP_AddToGroup(group, IDP_New(IDP_INT, &val, prop->identifier));
IDP_AddToGroup(group, IDP_New(IDP_BOOLEAN, &val, prop->identifier));
}
}
}
@ -2251,12 +2262,20 @@ void RNA_property_boolean_get_array(PointerRNA *ptr, PropertyRNA *prop, bool *va
if (prop->arraydimension == 0) {
values[0] = RNA_property_boolean_get(ptr, prop);
}
else {
else if (idprop->subtype == IDP_INT) {
/* Some boolean IDProperty arrays might be saved in files as an integer
* array property, since the boolean IDProperty type was added later. */
int *values_src = IDP_Array(idprop);
for (uint i = 0; i < idprop->len; i++) {
values[i] = (bool)values_src[i];
}
}
else if (idprop->subtype == IDP_BOOLEAN) {
bool *values_src = IDP_Array(idprop);
for (int i = 0; i < idprop->len; i++) {
values[i] = values_src[i];
}
}
}
else if (prop->arraydimension == 0) {
values[0] = RNA_property_boolean_get(ptr, prop);
@ -2314,9 +2333,17 @@ void RNA_property_boolean_set_array(PointerRNA *ptr, PropertyRNA *prop, const bo
IDP_Int(idprop) = values[0];
}
else {
int *values_dst = IDP_Array(idprop);
for (uint i = 0; i < idprop->len; i++) {
values_dst[i] = (int)values[i];
BLI_assert(idprop->type = IDP_ARRAY);
if (idprop->subtype == IDP_BOOLEAN) {
memcpy(IDP_Array(idprop), values, sizeof(int8_t) * idprop->len);
}
else if (idprop->subtype == IDP_INT) {
/* Support writing to integer and boolean IDProperties, since boolean
RNA properties used to be stored with integer IDProperties. */
int *values_dst = IDP_Array(idprop);
for (uint i = 0; i < idprop->len; i++) {
values_dst[i] = (int)values[i];
}
}
}
rna_idproperty_touch(idprop);
@ -2335,15 +2362,15 @@ void RNA_property_boolean_set_array(PointerRNA *ptr, PropertyRNA *prop, const bo
IDProperty *group;
val.array.len = prop->totarraylength;
val.array.type = IDP_INT;
val.array.type = IDP_BOOLEAN;
group = RNA_struct_idprops(ptr, 1);
if (group) {
idprop = IDP_New(IDP_ARRAY, &val, prop->identifier);
IDP_AddToGroup(group, idprop);
int *values_dst = IDP_Array(idprop);
bool *values_dst = IDP_Array(idprop);
for (uint i = 0; i < idprop->len; i++) {
values_dst[i] = (int)values[i];
values_dst[i] = values[i];
}
}
}
@ -2378,12 +2405,23 @@ void RNA_property_boolean_set_index(PointerRNA *ptr, PropertyRNA *prop, int inde
bool RNA_property_boolean_get_default(PointerRNA *UNUSED(ptr), PropertyRNA *prop)
{
/* TODO: Make defaults work for IDProperties. */
BoolPropertyRNA *bprop = (BoolPropertyRNA *)rna_ensure_property(prop);
BLI_assert(RNA_property_type(prop) == PROP_BOOLEAN);
BLI_assert(RNA_property_array_check(prop) == false);
BLI_assert(ELEM(bprop->defaultvalue, false, true));
if (prop->magic != RNA_MAGIC) {
const IDProperty *idprop = (const IDProperty *)prop;
BLI_assert(idprop->type == IDP_BOOLEAN);
if (idprop->ui_data) {
const IDPropertyUIDataBool *ui_data = (const IDPropertyUIDataBool *)idprop->ui_data;
return ui_data->default_value;
}
return false;
}
return bprop->defaultvalue;
}
@ -2394,7 +2432,26 @@ void RNA_property_boolean_get_default_array(PointerRNA *ptr, PropertyRNA *prop,
BLI_assert(RNA_property_type(prop) == PROP_BOOLEAN);
BLI_assert(RNA_property_array_check(prop) != false);
if (prop->arraydimension == 0) {
if (prop->magic != RNA_MAGIC) {
const IDProperty *idprop = (const IDProperty *)prop;
if (idprop->ui_data) {
BLI_assert(idprop->type == IDP_ARRAY);
BLI_assert(idprop->subtype == IDP_BOOLEAN);
const IDPropertyUIDataBool *ui_data = (const IDPropertyUIDataBool *)idprop->ui_data;
if (ui_data->default_array) {
rna_property_boolean_fill_default_array_values((bool *)ui_data->default_array,
ui_data->default_array_len,
ui_data->default_value,
idprop->len,
values);
}
else {
rna_property_boolean_fill_default_array_values(
NULL, 0, ui_data->default_value, idprop->len, values);
}
}
}
else if (prop->arraydimension == 0) {
values[0] = bprop->defaultvalue;
}
else {

View File

@ -75,6 +75,11 @@ static PyObject *idprop_py_from_idp_double(const IDProperty *prop)
return PyFloat_FromDouble(IDP_Double(prop));
}
static PyObject *idprop_py_from_idp_bool(const IDProperty *prop)
{
return PyBool_FromLong(IDP_Bool(prop));
}
static PyObject *idprop_py_from_idp_group(ID *id, IDProperty *prop, IDProperty *parent)
{
BPy_IDProperty *group = PyObject_New(BPy_IDProperty, &BPy_IDGroup_Type);
@ -155,6 +160,8 @@ PyObject *BPy_IDGroup_WrapData(ID *id, IDProperty *prop, IDProperty *parent)
return idprop_py_from_idp_float(prop);
case IDP_DOUBLE:
return idprop_py_from_idp_double(prop);
case IDP_BOOLEAN:
return idprop_py_from_idp_bool(prop);
case IDP_GROUP:
return idprop_py_from_idp_group(id, prop, parent);
case IDP_ARRAY:
@ -333,6 +340,12 @@ static char idp_sequence_type(PyObject *seq_fast)
}
type = IDP_DOUBLE;
}
else if (PyBool_Check(item)) {
if (i != 0 && (type != IDP_BOOLEAN)) {
return -1;
}
type = IDP_BOOLEAN;
}
else if (PyLong_Check(item)) {
if (type == IDP_IDPARRAY) { /* mixed dict/int */
return -1;
@ -396,6 +409,13 @@ static IDProperty *idp_from_PyFloat(const char *name, PyObject *ob)
return IDP_New(IDP_DOUBLE, &val, name);
}
static IDProperty *idp_from_PyBool(const char *name, PyObject *ob)
{
IDPropertyTemplate val = {0};
val.i = PyC_Long_AsBool(ob);
return IDP_New(IDP_BOOLEAN, &val, name);
}
static IDProperty *idp_from_PyLong(const char *name, PyObject *ob)
{
IDPropertyTemplate val = {0};
@ -466,6 +486,9 @@ static const char *idp_format_from_array_type(int type)
if (type == IDP_DOUBLE) {
return "d";
}
if (type == IDP_BOOLEAN) {
return "b";
}
return NULL;
}
@ -549,6 +572,20 @@ static IDProperty *idp_from_PySequence_Fast(const char *name, PyObject *ob)
}
break;
}
case IDP_BOOLEAN: {
prop = IDP_New(IDP_ARRAY, &val, name);
bool *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()) {
IDP_FreeProperty(prop);
return NULL;
}
prop_data[i] = (value != 0);
}
break;
}
default:
/* should never happen */
PyErr_SetString(PyExc_RuntimeError, "internal error with idp array.type");
@ -642,6 +679,9 @@ static IDProperty *idp_from_PyObject(PyObject *name_obj, PyObject *ob)
if (PyFloat_Check(ob)) {
return idp_from_PyFloat(name, ob);
}
if (PyBool_Check(ob)) {
return idp_from_PyBool(name, ob);
}
if (PyLong_Check(ob)) {
return idp_from_PyLong(name, ob);
}
@ -779,6 +819,8 @@ PyObject *BPy_IDGroup_MapDataToPy(IDProperty *prop)
return idprop_py_from_idp_float(prop);
case IDP_DOUBLE:
return idprop_py_from_idp_double(prop);
case IDP_BOOLEAN:
return idprop_py_from_idp_bool(prop);
case IDP_ID:
return idprop_py_from_idp_id(prop);
case IDP_ARRAY: {
@ -813,6 +855,13 @@ PyObject *BPy_IDGroup_MapDataToPy(IDProperty *prop)
}
break;
}
case IDP_BOOLEAN: {
const int8_t *array = (const int8_t *)IDP_Array(prop);
for (i = 0; i < prop->len; i++) {
PyList_SET_ITEM(seq, i, PyBool_FromLong(array[i]));
}
break;
}
default:
PyErr_Format(
PyExc_RuntimeError, "%s: invalid/corrupt array type '%d'!", __func__, prop->subtype);
@ -1629,20 +1678,23 @@ PyTypeObject BPy_IDGroup_Type = {
/** \name ID Array Methods
* \{ */
static PyTypeObject *idp_array_py_type(BPy_IDArray *self, bool *r_is_double)
static PyTypeObject *idp_array_py_type(BPy_IDArray *self, size_t *elem_size)
{
switch (self->prop->subtype) {
case IDP_FLOAT:
*r_is_double = false;
*elem_size = sizeof(float);
return &PyFloat_Type;
case IDP_DOUBLE:
*r_is_double = true;
*elem_size = sizeof(double);
return &PyFloat_Type;
case IDP_BOOLEAN:
*elem_size = sizeof(int8_t);
return &PyBool_Type;
case IDP_INT:
*r_is_double = false;
*elem_size = sizeof(int);
return &PyLong_Type;
default:
*r_is_double = false;
*elem_size = 0;
return NULL;
}
}
@ -1653,7 +1705,7 @@ static PyObject *BPy_IDArray_repr(BPy_IDArray *self)
}
PyDoc_STRVAR(BPy_IDArray_get_typecode_doc,
"The type of the data in the array {'f': float, 'd': double, 'i': int}.");
"The type of the data in the array {'f': float, 'd': double, 'i': int, 'b': bool}.");
static PyObject *BPy_IDArray_get_typecode(BPy_IDArray *self)
{
switch (self->prop->subtype) {
@ -1663,6 +1715,8 @@ static PyObject *BPy_IDArray_get_typecode(BPy_IDArray *self)
return PyUnicode_FromString("d");
case IDP_INT:
return PyUnicode_FromString("i");
case IDP_BOOLEAN:
return PyUnicode_FromString("b");
}
PyErr_Format(
@ -1714,6 +1768,8 @@ static PyObject *BPy_IDArray_GetItem(BPy_IDArray *self, Py_ssize_t index)
return PyFloat_FromDouble(((double *)IDP_Array(self->prop))[index]);
case IDP_INT:
return PyLong_FromLong((long)((int *)IDP_Array(self->prop))[index]);
case IDP_BOOLEAN:
return PyBool_FromLong((long)((int8_t *)IDP_Array(self->prop))[index]);
}
PyErr_Format(
@ -1755,6 +1811,15 @@ static int BPy_IDArray_SetItem(BPy_IDArray *self, Py_ssize_t index, PyObject *va
((int *)IDP_Array(self->prop))[index] = i;
break;
}
case IDP_BOOLEAN: {
const int i = PyC_Long_AsBool(value);
if (i == -1 && PyErr_Occurred()) {
return -1;
}
((int8_t *)IDP_Array(self->prop))[index] = i;
break;
}
}
return 0;
}
@ -1810,6 +1875,13 @@ static PyObject *BPy_IDArray_slice(BPy_IDArray *self, int begin, int end)
}
break;
}
case IDP_BOOLEAN: {
const int8_t *array = (const int8_t *)IDP_Array(prop);
for (count = begin; count < end; count++) {
PyTuple_SET_ITEM(tuple, count - begin, PyBool_FromLong((long)array[count]));
}
break;
}
}
return tuple;
@ -1818,9 +1890,8 @@ static PyObject *BPy_IDArray_slice(BPy_IDArray *self, int begin, int end)
static int BPy_IDArray_ass_slice(BPy_IDArray *self, int begin, int end, PyObject *seq)
{
IDProperty *prop = self->prop;
bool is_double;
const PyTypeObject *py_type = idp_array_py_type(self, &is_double);
const size_t elem_size = is_double ? sizeof(double) : sizeof(float);
size_t elem_size;
const PyTypeObject *py_type = idp_array_py_type(self, &elem_size);
size_t alloc_len;
size_t size;
void *vec;
@ -1933,6 +2004,9 @@ static int itemsize_by_idarray_type(int array_type)
if (array_type == IDP_DOUBLE) {
return sizeof(double);
}
if (array_type == IDP_BOOLEAN) {
return sizeof(bool);
}
return -1; /* should never happen */
}

View File

@ -181,6 +181,89 @@ static bool idprop_ui_data_update_int(IDProperty *idprop, PyObject *args, PyObje
return true;
}
/**
* \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.
*/
static bool idprop_ui_data_update_bool_default(IDProperty *idprop,
IDPropertyUIDataBool *ui_data,
PyObject *default_value)
{
if (PySequence_Check(default_value)) {
if (idprop->type != IDP_ARRAY) {
PyErr_SetString(PyExc_TypeError, "Only array properties can have array default values");
return false;
}
Py_ssize_t len = PySequence_Size(default_value);
int8_t *new_default_array = (int8_t *)MEM_malloc_arrayN(len, sizeof(int8_t), __func__);
if (PyC_AsArray(new_default_array,
sizeof(int8_t),
default_value,
len,
&PyBool_Type,
"ui_data_update") == -1) {
MEM_freeN(new_default_array);
return false;
}
ui_data->default_array_len = len;
ui_data->default_array = new_default_array;
}
else {
const int value = PyC_Long_AsBool(default_value);
if ((value == -1) && PyErr_Occurred()) {
PyErr_SetString(PyExc_ValueError, "Error converting \"default\" argument to integer");
return false;
}
ui_data->default_value = (value != 0);
}
return true;
}
/**
* \return False when parsing fails, in which case caller should return NULL.
*/
static bool idprop_ui_data_update_bool(IDProperty *idprop, PyObject *args, PyObject *kwargs)
{
const char *rna_subtype = NULL;
const char *description = NULL;
PyObject *default_value = NULL;
const char *kwlist[] = {"default", "subtype", "description", NULL};
if (!PyArg_ParseTupleAndKeywords(args,
kwargs,
"|$Ozz:update",
(char **)kwlist,
&default_value,
&rna_subtype,
&description)) {
return false;
}
/* Write to a temporary copy of the UI data in case some part of the parsing fails. */
IDPropertyUIDataBool *ui_data_orig = (IDPropertyUIDataBool *)idprop->ui_data;
IDPropertyUIDataBool ui_data = *ui_data_orig;
if (!idprop_ui_data_update_base(&ui_data.base, rna_subtype, description)) {
IDP_ui_data_free_unique_contents(&ui_data.base, IDP_ui_data_type(idprop), &ui_data_orig->base);
return false;
}
if (!ELEM(default_value, NULL, Py_None)) {
if (!idprop_ui_data_update_bool_default(idprop, &ui_data, default_value)) {
IDP_ui_data_free_unique_contents(
&ui_data.base, IDP_ui_data_type(idprop), &ui_data_orig->base);
return false;
}
}
/* 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;
return true;
}
/**
* \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.
@ -403,6 +486,12 @@ static PyObject *BPy_IDPropertyUIManager_update(BPy_IDPropertyUIManager *self,
return NULL;
}
Py_RETURN_NONE;
case IDP_UI_DATA_TYPE_BOOLEAN:
IDP_ui_data_ensure(property);
if (!idprop_ui_data_update_bool(property, args, kwargs)) {
return NULL;
}
Py_RETURN_NONE;
case IDP_UI_DATA_TYPE_FLOAT:
IDP_ui_data_ensure(property);
if (!idprop_ui_data_update_float(property, args, kwargs)) {
@ -465,6 +554,25 @@ static void idprop_ui_data_to_dict_int(IDProperty *property, PyObject *dict)
}
}
static void idprop_ui_data_to_dict_bool(IDProperty *property, PyObject *dict)
{
IDPropertyUIDataBool *ui_data = (IDPropertyUIDataBool *)property->ui_data;
PyObject *item;
if (property->type == IDP_ARRAY) {
PyObject *list = PyList_New(ui_data->default_array_len);
for (int i = 0; i < ui_data->default_array_len; i++) {
PyList_SET_ITEM(list, i, PyBool_FromLong(ui_data->default_array[i]));
}
PyDict_SetItemString(dict, "default", list);
Py_DECREF(list);
}
else {
PyDict_SetItemString(dict, "default", item = PyBool_FromLong(ui_data->default_value));
Py_DECREF(item);
}
}
static void idprop_ui_data_to_dict_float(IDProperty *property, PyObject *dict)
{
IDPropertyUIDataFloat *ui_data = (IDPropertyUIDataFloat *)property->ui_data;
@ -547,6 +655,9 @@ static PyObject *BPy_IDIDPropertyUIManager_as_dict(BPy_IDPropertyUIManager *self
case IDP_UI_DATA_TYPE_INT:
idprop_ui_data_to_dict_int(property, dict);
break;
case IDP_UI_DATA_TYPE_BOOLEAN:
idprop_ui_data_to_dict_bool(property, dict);
break;
case IDP_UI_DATA_TYPE_FLOAT:
idprop_ui_data_to_dict_float(property, dict);
break;