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