UI: Add initial UI support for ID pointers custom properties.

Customprops to IDs are supported since years through code, but were
never exposed directly in the UI of customporperties.

This commit mainly:
* Adds a new `DATA_BLOCK` type to UI customprops types.
* Exposes the existing `id_type` settings to python API.

Pull Request: https://projects.blender.org/blender/blender/pulls/110458
This commit is contained in:
Bastien Montagne
2023-07-25 12:31:16 +02:00
committed by Gitea
parent b404df6989
commit b3c7f3c8a9
3 changed files with 81 additions and 5 deletions

View File

@@ -80,6 +80,7 @@ def rna_idprop_ui_create(
description=None, description=None,
overridable=False, overridable=False,
subtype=None, subtype=None,
id_type='OBJECT',
): ):
"""Create and initialize a custom property with limits, defaults and other settings.""" """Create and initialize a custom property with limits, defaults and other settings."""
@@ -91,11 +92,16 @@ def rna_idprop_ui_create(
proptype, _ = rna_idprop_value_item_type(default) proptype, _ = rna_idprop_value_item_type(default)
if (proptype is bool) or (proptype is str): if (proptype is bool) or (proptype is str):
ui_data = item.id_properties_ui(prop)
ui_data.update( ui_data.update(
description=description, description=description,
default=default, default=default,
) )
elif proptype is type(None) or issubclass(proptype, bpy.types.ID):
ui_data.update(
description=description,
default=default,
id_type=id_type,
)
else: else:
if soft_min is None: if soft_min is None:
soft_min = min soft_min = min
@@ -156,6 +162,7 @@ def draw(layout, context, context_member, property_type, *, use_edit=True):
to_dict = getattr(value, "to_dict", None) to_dict = getattr(value, "to_dict", None)
to_list = getattr(value, "to_list", None) to_list = getattr(value, "to_list", None)
is_datablock = value is None or isinstance(value, bpy.types.ID)
if to_dict: if to_dict:
value = to_dict() value = to_dict()
@@ -178,6 +185,8 @@ def draw(layout, context, context_member, property_type, *, use_edit=True):
props = value_column.operator("wm.properties_edit_value", text="Edit Value") props = value_column.operator("wm.properties_edit_value", text="Edit Value")
props.data_path = context_member props.data_path = context_member
props.property_name = key props.property_name = key
elif is_datablock:
value_column.template_ID(rna_item, '["%s"]' % escape_identifier(key), text="")
else: else:
value_column.prop(rna_item, '["%s"]' % escape_identifier(key), text="") value_column.prop(rna_item, '["%s"]' % escape_identifier(key), text="")

View File

@@ -1387,6 +1387,7 @@ rna_custom_property_type_items = (
('BOOL', "Boolean", "A true or false value"), ('BOOL', "Boolean", "A true or false value"),
('BOOL_ARRAY', "Boolean Array", "An array of true or false values"), ('BOOL_ARRAY', "Boolean Array", "An array of true or false values"),
('STRING', "String", "A string value"), ('STRING', "String", "A string value"),
('DATA_BLOCK', "Data-Block", "A data-block value"),
('PYTHON', "Python", "Edit a Python value directly, for unsupported property types"), ('PYTHON', "Python", "Edit a Python value directly, for unsupported property types"),
) )
@@ -1412,6 +1413,8 @@ rna_custom_property_subtype_vector_items = (
('QUATERNION', "Quaternion Rotation", "Quaternion rotation (affects NLA blending)"), ('QUATERNION', "Quaternion Rotation", "Quaternion rotation (affects NLA blending)"),
) )
rna_id_type_items = tuple((item.identifier, item.name, item.description, item.icon, item.value)
for item in bpy.types.Action.bl_rna.properties['id_root'].enum_items)
class WM_OT_properties_edit(Operator): class WM_OT_properties_edit(Operator):
"""Change a custom property's type, or adjust how it is displayed in the interface""" """Change a custom property's type, or adjust how it is displayed in the interface"""
@@ -1554,6 +1557,14 @@ class WM_OT_properties_edit(Operator):
maxlen=1024, maxlen=1024,
) )
# Data-block properties.
id_type: EnumProperty(
name="ID Type",
items=rna_id_type_items,
default='OBJECT',
)
# Store the value converted to a string as a fallback for otherwise unsupported types. # Store the value converted to a string as a fallback for otherwise unsupported types.
eval_string: StringProperty( eval_string: StringProperty(
name="Value", name="Value",
@@ -1623,9 +1634,21 @@ class WM_OT_properties_edit(Operator):
if is_array: if is_array:
return 'PYTHON' return 'PYTHON'
return 'STRING' return 'STRING'
elif prop_type == type(None) or issubclass(prop_type, bpy.types.ID):
if is_array:
return 'PYTHON'
return 'DATA_BLOCK'
return 'PYTHON' return 'PYTHON'
# For `DATA_BLOCK` types, return the `id_type` or an empty string for non data-block types.
@staticmethod
def get_property_id_type(item, property_name):
ui_data = item.id_properties_ui(property_name)
rna_data = ui_data.as_dict()
# For non `DATA_BLOCK` types, the `id_type` wont exist.
return rna_data.get("id_type", "")
def _init_subtype(self, subtype): def _init_subtype(self, subtype):
self.subtype = subtype or 'NONE' self.subtype = subtype or 'NONE'
@@ -1664,6 +1687,8 @@ class WM_OT_properties_edit(Operator):
self.default_string = rna_data["default"] self.default_string = rna_data["default"]
elif self.property_type in {'BOOL', 'BOOL_ARRAY'}: elif self.property_type in {'BOOL', 'BOOL_ARRAY'}:
self.default_bool = self._convert_new_value_array(rna_data["default"], bool, 32) self.default_bool = self._convert_new_value_array(rna_data["default"], bool, 32)
elif self.property_type == 'DATA_BLOCK':
self.id_type = rna_data["id_type"]
if self.property_type in {'FLOAT_ARRAY', 'INT_ARRAY', 'BOOL_ARRAY'}: if self.property_type in {'FLOAT_ARRAY', 'INT_ARRAY', 'BOOL_ARRAY'}:
self.array_length = len(item[name]) self.array_length = len(item[name])
@@ -1677,7 +1702,7 @@ class WM_OT_properties_edit(Operator):
# When the operator chooses a different type than the original property, # When the operator chooses a different type than the original property,
# attempt to convert the old value to the new type for continuity and speed. # attempt to convert the old value to the new type for continuity and speed.
def _get_converted_value(self, item, name_old, prop_type_new): def _get_converted_value(self, item, name_old, prop_type_new, id_type_old, id_type_new):
if prop_type_new == 'INT': if prop_type_new == 'INT':
return self._convert_new_value_single(item[name_old], int) return self._convert_new_value_single(item[name_old], int)
elif prop_type_new == 'FLOAT': elif prop_type_new == 'FLOAT':
@@ -1700,6 +1725,14 @@ class WM_OT_properties_edit(Operator):
return [False] * self.array_length return [False] * self.array_length
elif prop_type_new == 'STRING': elif prop_type_new == 'STRING':
return self.convert_custom_property_to_string(item, name_old) return self.convert_custom_property_to_string(item, name_old)
elif prop_type_new == 'DATA_BLOCK':
if id_type_old != id_type_new:
return None
old_value = item[name_old]
if not isinstance(old_value, bpy.types.ID):
return None
return old_value
# If all else fails, create an empty string property. That should avoid errors later on anyway. # If all else fails, create an empty string property. That should avoid errors later on anyway.
return "" return ""
@@ -1761,6 +1794,12 @@ class WM_OT_properties_edit(Operator):
default=self.default_string, default=self.default_string,
description=self.description, description=self.description,
) )
elif prop_type_new == 'DATA_BLOCK':
ui_data = item.id_properties_ui(name)
ui_data.update(
description=self.description,
id_type=self.id_type,
)
escaped_name = bpy.utils.escape_identifier(name) escaped_name = bpy.utils.escape_identifier(name)
item.property_overridable_library_set('["%s"]' % escaped_name, self.is_overridable_library) item.property_overridable_library_set('["%s"]' % escaped_name, self.is_overridable_library)
@@ -1824,6 +1863,9 @@ class WM_OT_properties_edit(Operator):
prop_type_new = self.property_type prop_type_new = self.property_type
self._old_prop_name[:] = [name] self._old_prop_name[:] = [name]
id_type_old = self.get_property_id_type(item, name_old)
id_type_new = self.id_type
if prop_type_new == 'PYTHON': if prop_type_new == 'PYTHON':
try: try:
new_value = eval(self.eval_string) new_value = eval(self.eval_string)
@@ -1838,7 +1880,7 @@ class WM_OT_properties_edit(Operator):
if name_old != name: if name_old != name:
del item[name_old] del item[name_old]
else: else:
new_value = self._get_converted_value(item, name_old, prop_type_new) new_value = self._get_converted_value(item, name_old, prop_type_new, id_type_old, id_type_new)
del item[name_old] del item[name_old]
item[name] = new_value item[name] = new_value
@@ -1991,6 +2033,8 @@ class WM_OT_properties_edit(Operator):
layout.prop(self, "default_bool", index=0) layout.prop(self, "default_bool", index=0)
elif self.property_type == 'STRING': elif self.property_type == 'STRING':
layout.prop(self, "default_string") layout.prop(self, "default_string")
elif self.property_type == 'DATA_BLOCK':
layout.prop(self, "id_type")
if self.property_type == 'PYTHON': if self.property_type == 'PYTHON':
layout.prop(self, "eval_string") layout.prop(self, "eval_string")

View File

@@ -439,9 +439,10 @@ static bool idprop_ui_data_update_id(IDProperty *idprop, PyObject *args, PyObjec
{ {
const char *rna_subtype = nullptr; const char *rna_subtype = nullptr;
const char *description = nullptr; const char *description = nullptr;
const char *kwlist[] = {"subtype", "description", nullptr}; const char *id_type = nullptr;
const char *kwlist[] = {"subtype", "description", "id_type", nullptr};
if (!PyArg_ParseTupleAndKeywords( if (!PyArg_ParseTupleAndKeywords(
args, kwargs, "|$zz:update", (char **)kwlist, &rna_subtype, &description)) args, kwargs, "|$zzz:update", (char **)kwlist, &rna_subtype, &description, &id_type))
{ {
return false; return false;
} }
@@ -455,6 +456,15 @@ static bool idprop_ui_data_update_id(IDProperty *idprop, PyObject *args, PyObjec
return false; return false;
} }
int id_type_tmp;
if (pyrna_enum_value_from_id(
rna_enum_id_type_items, id_type, &id_type_tmp, "IDPropertyUIManager.update") == -1)
{
return false;
}
ui_data.id_type = short(id_type_tmp);
/* 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;
@@ -471,6 +481,7 @@ PyDoc_STRVAR(BPy_IDPropertyUIManager_update_doc,
"precision=None, " "precision=None, "
"step=None, " "step=None, "
"default=None, " "default=None, "
"id_type=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"
@@ -619,6 +630,17 @@ static void idprop_ui_data_to_dict_string(IDProperty *property, PyObject *dict)
Py_DECREF(item); Py_DECREF(item);
} }
static void idprop_ui_data_to_dict_id(IDProperty *property, PyObject *dict)
{
IDPropertyUIDataID *ui_data = (IDPropertyUIDataID *)property->ui_data;
const char *id_type = nullptr;
RNA_enum_identifier(rna_enum_id_type_items, ui_data->id_type, &id_type);
PyObject *item = PyUnicode_FromString(id_type);
PyDict_SetItemString(dict, "id_type", item);
Py_DECREF(item);
}
PyDoc_STRVAR(BPy_IDPropertyUIManager_as_dict_doc, PyDoc_STRVAR(BPy_IDPropertyUIManager_as_dict_doc,
".. method:: as_dict()\n" ".. method:: as_dict()\n"
"\n" "\n"
@@ -655,6 +677,7 @@ static PyObject *BPy_IDIDPropertyUIManager_as_dict(BPy_IDPropertyUIManager *self
idprop_ui_data_to_dict_string(property, dict); idprop_ui_data_to_dict_string(property, dict);
break; break;
case IDP_UI_DATA_TYPE_ID: case IDP_UI_DATA_TYPE_ID:
idprop_ui_data_to_dict_id(property, dict);
break; break;
case IDP_UI_DATA_TYPE_INT: case IDP_UI_DATA_TYPE_INT:
idprop_ui_data_to_dict_int(property, dict); idprop_ui_data_to_dict_int(property, dict);