diff --git a/scripts/modules/rna_prop_ui.py b/scripts/modules/rna_prop_ui.py index d83123d60d6..5340b0e37da 100644 --- a/scripts/modules/rna_prop_ui.py +++ b/scripts/modules/rna_prop_ui.py @@ -80,6 +80,7 @@ def rna_idprop_ui_create( description=None, overridable=False, subtype=None, + id_type='OBJECT', ): """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) if (proptype is bool) or (proptype is str): - ui_data = item.id_properties_ui(prop) ui_data.update( description=description, 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: if soft_min is None: 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_list = getattr(value, "to_list", None) + is_datablock = value is None or isinstance(value, bpy.types.ID) if 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.data_path = context_member props.property_name = key + elif is_datablock: + value_column.template_ID(rna_item, '["%s"]' % escape_identifier(key), text="") else: value_column.prop(rna_item, '["%s"]' % escape_identifier(key), text="") diff --git a/scripts/startup/bl_operators/wm.py b/scripts/startup/bl_operators/wm.py index bc199836433..eb14bcebac1 100644 --- a/scripts/startup/bl_operators/wm.py +++ b/scripts/startup/bl_operators/wm.py @@ -1387,6 +1387,7 @@ rna_custom_property_type_items = ( ('BOOL', "Boolean", "A true or false value"), ('BOOL_ARRAY', "Boolean Array", "An array of true or false values"), ('STRING', "String", "A string value"), + ('DATABLOCK', "Data-Block", "A data-block value"), ('PYTHON', "Python", "Edit a Python value directly, for unsupported property types"), ) @@ -1412,6 +1413,11 @@ rna_custom_property_subtype_vector_items = ( ('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): """Change a custom property's type, or adjust how it is displayed in the interface""" @@ -1554,6 +1560,14 @@ class WM_OT_properties_edit(Operator): 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. eval_string: StringProperty( name="Value", @@ -1623,9 +1637,21 @@ class WM_OT_properties_edit(Operator): if is_array: return 'PYTHON' return 'STRING' + elif prop_type == type(None) or issubclass(prop_type, bpy.types.ID): + if is_array: + return 'PYTHON' + return 'DATABLOCK' return 'PYTHON' + # For `DATABLOCK` 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 `DATABLOCK` types, the `id_type` wont exist. + return rna_data.get("id_type", "") + def _init_subtype(self, subtype): self.subtype = subtype or 'NONE' @@ -1664,6 +1690,8 @@ class WM_OT_properties_edit(Operator): self.default_string = rna_data["default"] elif self.property_type in {'BOOL', 'BOOL_ARRAY'}: self.default_bool = self._convert_new_value_array(rna_data["default"], bool, 32) + elif self.property_type == 'DATABLOCK': + self.id_type = rna_data["id_type"] if self.property_type in {'FLOAT_ARRAY', 'INT_ARRAY', 'BOOL_ARRAY'}: self.array_length = len(item[name]) @@ -1677,7 +1705,7 @@ class WM_OT_properties_edit(Operator): # 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. - 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': return self._convert_new_value_single(item[name_old], int) elif prop_type_new == 'FLOAT': @@ -1700,6 +1728,14 @@ class WM_OT_properties_edit(Operator): return [False] * self.array_length elif prop_type_new == 'STRING': return self.convert_custom_property_to_string(item, name_old) + elif prop_type_new == 'DATABLOCK': + 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. return "" @@ -1761,6 +1797,12 @@ class WM_OT_properties_edit(Operator): default=self.default_string, description=self.description, ) + elif prop_type_new == 'DATABLOCK': + 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) item.property_overridable_library_set('["%s"]' % escaped_name, self.is_overridable_library) @@ -1824,6 +1866,9 @@ class WM_OT_properties_edit(Operator): prop_type_new = self.property_type 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': try: new_value = eval(self.eval_string) @@ -1838,7 +1883,7 @@ class WM_OT_properties_edit(Operator): if name_old != name: del item[name_old] 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] item[name] = new_value @@ -1991,6 +2036,8 @@ class WM_OT_properties_edit(Operator): layout.prop(self, "default_bool", index=0) elif self.property_type == 'STRING': layout.prop(self, "default_string") + elif self.property_type == 'DATABLOCK': + layout.prop(self, "id_type") if self.property_type == 'PYTHON': layout.prop(self, "eval_string") diff --git a/source/blender/python/generic/idprop_py_ui_api.cc b/source/blender/python/generic/idprop_py_ui_api.cc index b187c0d10f8..455d1969dfb 100644 --- a/source/blender/python/generic/idprop_py_ui_api.cc +++ b/source/blender/python/generic/idprop_py_ui_api.cc @@ -439,9 +439,10 @@ static bool idprop_ui_data_update_id(IDProperty *idprop, PyObject *args, PyObjec { const char *rna_subtype = 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( - args, kwargs, "|$zz:update", (char **)kwlist, &rna_subtype, &description)) + args, kwargs, "|$zzz:update", (char **)kwlist, &rna_subtype, &description, &id_type)) { return false; } @@ -455,6 +456,15 @@ static bool idprop_ui_data_update_id(IDProperty *idprop, PyObject *args, PyObjec 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. */ IDP_ui_data_free_unique_contents(&ui_data_orig->base, IDP_ui_data_type(idprop), &ui_data.base); *ui_data_orig = ui_data; @@ -471,6 +481,7 @@ PyDoc_STRVAR(BPy_IDPropertyUIManager_update_doc, "precision=None, " "step=None, " "default=None, " + "id_type=None, " "description=None)\n" "\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); } +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, ".. method:: as_dict()\n" "\n" @@ -655,6 +677,7 @@ static PyObject *BPy_IDIDPropertyUIManager_as_dict(BPy_IDPropertyUIManager *self idprop_ui_data_to_dict_string(property, dict); break; case IDP_UI_DATA_TYPE_ID: + idprop_ui_data_to_dict_id(property, dict); break; case IDP_UI_DATA_TYPE_INT: idprop_ui_data_to_dict_int(property, dict);