From 6ba0346797f43cd73eb1003ce65a62b8409203fb Mon Sep 17 00:00:00 2001 From: Josh Maros <60271685+joshua-maros@users.noreply.github.com> Date: Sat, 29 Apr 2023 20:31:27 -0700 Subject: [PATCH] Python API: add bpy.context.property, for property under the mouse cursor This can be useful for example to add custom operators to the property context menu. Pull Request: https://projects.blender.org/blender/blender/pulls/107280 --- .../examples/bpy.context.property.py | 10 +++++++ doc/python_api/sphinx_doc_gen.py | 7 ++++- source/blender/blenkernel/BKE_context.h | 11 ++++++- source/blender/blenkernel/intern/context.cc | 12 +++++++- .../blender/editors/screen/screen_context.c | 23 ++++++++++++++ source/blender/python/intern/bpy_rna.c | 30 +++++++++++++++++-- 6 files changed, 88 insertions(+), 5 deletions(-) create mode 100644 doc/python_api/examples/bpy.context.property.py diff --git a/doc/python_api/examples/bpy.context.property.py b/doc/python_api/examples/bpy.context.property.py new file mode 100644 index 00000000000..ae6daa04819 --- /dev/null +++ b/doc/python_api/examples/bpy.context.property.py @@ -0,0 +1,10 @@ +""" +Get the property associated with a hovered button. +Returns a tuple of the datablock, data path to the property, and array index. +""" + +# Example inserting keyframe for the hovered property. +active_property = bpy.context.property +if active_property: + datablock, data_path, index = active_property + datablock.keyframe_insert(data_path=data_path, index=index, frame=1) diff --git a/doc/python_api/sphinx_doc_gen.py b/doc/python_api/sphinx_doc_gen.py index 1b3f41d75d0..74436c55178 100644 --- a/doc/python_api/sphinx_doc_gen.py +++ b/doc/python_api/sphinx_doc_gen.py @@ -1202,6 +1202,7 @@ context_type_map = { "particle_settings": ("ParticleSettings", False), "particle_system": ("ParticleSystem", False), "particle_system_editable": ("ParticleSystem", False), + "property": ("(:class:`bpy.types.ID`, :class:`string`, :class:`int`)", False), "pointcloud": ("PointCloud", False), "pose_bone": ("PoseBone", False), "pose_object": ("Object", False), @@ -1347,7 +1348,11 @@ def pycontext2sphinx(basepath): raise SystemExit( "Error: context key %r not found in context_type_map; update %s" % (member, __file__)) from None - fw(" :type: %s :class:`bpy.types.%s`\n\n" % ("sequence of " if is_seq else "", member_type)) + + if member_type.isidentifier(): + member_type = ":class:`bpy.types.%s`" % member_type + fw(" :type: %s %s\n\n" % ("sequence of " if is_seq else "", member_type)) + write_example_ref(" ", fw, "bpy.context." + member) # Generate type-map: # for member in sorted(unique_context_strings): diff --git a/source/blender/blenkernel/BKE_context.h b/source/blender/blenkernel/BKE_context.h index 0b58adf7ca7..555bc7b6eb8 100644 --- a/source/blender/blenkernel/BKE_context.h +++ b/source/blender/blenkernel/BKE_context.h @@ -244,6 +244,7 @@ void CTX_wm_operator_poll_msg_clear(struct bContext *C); enum { CTX_DATA_TYPE_POINTER = 0, CTX_DATA_TYPE_COLLECTION, + CTX_DATA_TYPE_PROPERTY, }; PointerRNA CTX_data_pointer_get(const bContext *C, const char *member); @@ -261,7 +262,7 @@ ListBase CTX_data_collection_get(const bContext *C, const char *member); ListBase CTX_data_dir_get_ex(const bContext *C, bool use_store, bool use_rna, bool use_all); ListBase CTX_data_dir_get(const bContext *C); int /*eContextResult*/ CTX_data_get( - const bContext *C, const char *member, PointerRNA *r_ptr, ListBase *r_lb, short *r_type); + const bContext *C, const char *member, PointerRNA *r_ptr, ListBase *r_lb, PropertyRNA **r_prop, int *r_index, short *r_type); void CTX_data_id_pointer_set(bContextDataResult *result, struct ID *id); void CTX_data_pointer_set_ptr(bContextDataResult *result, const PointerRNA *ptr); @@ -271,6 +272,14 @@ void CTX_data_id_list_add(bContextDataResult *result, struct ID *id); void CTX_data_list_add_ptr(bContextDataResult *result, const PointerRNA *ptr); void CTX_data_list_add(bContextDataResult *result, struct ID *id, StructRNA *type, void *data); +/** + * Stores a property in a result. Make sure to also call 'CTX_data_type_set(result, CTX_DATA_TYPE_PROPERTY)'. + * \param result: The result to store the property in. + * \param prop: The property to store. + * \param index: The particular index in the property to store. + */ +void CTX_data_prop_set(bContextDataResult *result, PropertyRNA *prop, int index); + void CTX_data_dir_set(bContextDataResult *result, const char **dir); void CTX_data_type_set(struct bContextDataResult *result, short type); diff --git a/source/blender/blenkernel/intern/context.cc b/source/blender/blenkernel/intern/context.cc index 13aae983dd1..9c191c4f833 100644 --- a/source/blender/blenkernel/intern/context.cc +++ b/source/blender/blenkernel/intern/context.cc @@ -247,6 +247,8 @@ void CTX_py_state_pop(bContext *C, bContext_PyState *pystate) struct bContextDataResult { PointerRNA ptr; ListBase list; + PropertyRNA *prop; + int index; const char **dir; short type; /* 0: normal, 1: seq */ }; @@ -497,7 +499,7 @@ ListBase CTX_data_collection_get(const bContext *C, const char *member) } int /*eContextResult*/ CTX_data_get( - const bContext *C, const char *member, PointerRNA *r_ptr, ListBase *r_lb, short *r_type) + const bContext *C, const char *member, PointerRNA *r_ptr, ListBase *r_lb, PropertyRNA **r_prop, int *r_index, short *r_type) { bContextDataResult result; eContextResult ret = ctx_data_get((bContext *)C, member, &result); @@ -505,6 +507,8 @@ int /*eContextResult*/ CTX_data_get( if (ret == CTX_RESULT_OK) { *r_ptr = result.ptr; *r_lb = result.list; + *r_prop = result.prop; + *r_index = result.index; *r_type = result.type; } else { @@ -675,6 +679,12 @@ int ctx_data_list_count(const bContext *C, bool (*func)(const bContext *, ListBa return 0; } +void CTX_data_prop_set(bContextDataResult *result, PropertyRNA *prop, int index) +{ + result->prop = prop; + result->index = index; +} + void CTX_data_dir_set(bContextDataResult *result, const char **dir) { result->dir = dir; diff --git a/source/blender/editors/screen/screen_context.c b/source/blender/editors/screen/screen_context.c index ba07697c6e3..7fe099573c3 100644 --- a/source/blender/editors/screen/screen_context.c +++ b/source/blender/editors/screen/screen_context.c @@ -110,6 +110,7 @@ const char *screen_context_dir[] = { "active_editable_fcurve", "selected_editable_keyframes", "ui_list", + "property", "asset_library_ref", NULL, }; @@ -537,6 +538,27 @@ static eContextResult screen_ctx_active_object(const bContext *C, bContextDataRe return CTX_RESULT_OK; } + +static eContextResult screen_ctx_property(const bContext *C, bContextDataResult *result) +{ + PointerRNA ptr; + PropertyRNA *prop; + int index; + + UI_context_active_but_prop_get(C, &ptr, &prop, &index); + /* UI_context_active_but_prop_get returns an index of 0 if the property is not + * an array, but other functions expect -1 for non-arrays. */ + if (!RNA_property_array_check(prop)) { + index = -1; + } + + CTX_data_type_set(result, CTX_DATA_TYPE_PROPERTY); + CTX_data_pointer_set_ptr(result, &ptr); + CTX_data_prop_set(result, prop, index); + + return CTX_RESULT_OK; +} + static eContextResult screen_ctx_object(const bContext *C, bContextDataResult *result) { wmWindow *win = CTX_wm_window(C); @@ -1347,6 +1369,7 @@ static void ensure_ed_screen_context_functions(void) register_context_function("selected_editable_keyframes", screen_ctx_selected_editable_keyframes); register_context_function("asset_library_ref", screen_ctx_asset_library); register_context_function("ui_list", screen_ctx_ui_list); + register_context_function("property", screen_ctx_property); } int ed_screen_context(const bContext *C, const char *member, bContextDataResult *result) diff --git a/source/blender/python/intern/bpy_rna.c b/source/blender/python/intern/bpy_rna.c index b4be0e4e844..69c764f6f99 100644 --- a/source/blender/python/intern/bpy_rna.c +++ b/source/blender/python/intern/bpy_rna.c @@ -4378,13 +4378,15 @@ static PyObject *pyrna_struct_getattro(BPy_StructRNA *self, PyObject *pyname) else { PointerRNA newptr; ListBase newlb; + PropertyRNA *newprop; + int newindex; short newtype; /* An empty string is used to implement #CTX_data_dir_get, * without this check `getattr(context, "")` succeeds. */ eContextResult done; if (name[0]) { - done = CTX_data_get(C, name, &newptr, &newlb, &newtype); + done = CTX_data_get(C, name, &newptr, &newlb, &newprop, &newindex, &newtype); } else { /* Fall through to built-in `getattr`. */ @@ -4413,6 +4415,27 @@ static PyObject *pyrna_struct_getattro(BPy_StructRNA *self, PyObject *pyname) } break; } + case CTX_DATA_TYPE_PROPERTY: { + if (newprop != NULL) { + /* Create pointer to parent ID, and path from ID to property. */ + PointerRNA idptr; + RNA_id_pointer_create(newptr.owner_id, &idptr); + char *path_str = RNA_path_from_ID_to_property(&newptr, newprop); + + ret = PyTuple_New(3); + PyTuple_SET_ITEMS(ret, + pyrna_struct_CreatePyObject(&idptr), + PyUnicode_FromString(path_str), + PyLong_FromLong(newindex)); + + MEM_freeN(path_str); + } + else { + ret = Py_None; + Py_INCREF(ret); + } + break; + } default: /* Should never happen. */ BLI_assert_msg(0, "Invalid context type"); @@ -4605,9 +4628,12 @@ static int pyrna_struct_setattro(BPy_StructRNA *self, PyObject *pyname, PyObject PointerRNA newptr; ListBase newlb; + PropertyRNA *newprop; + int newindex; short newtype; - const eContextResult done = CTX_data_get(C, name, &newptr, &newlb, &newtype); + const eContextResult done = CTX_data_get( + C, name, &newptr, &newlb, &newprop, &newindex, &newtype); if (done == CTX_RESULT_OK) { PyErr_Format(