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
This commit is contained in:
Josh Maros 2023-04-29 20:31:27 -07:00 committed by Brecht Van Lommel
parent ea937b304d
commit 6ba0346797
6 changed files with 88 additions and 5 deletions

@ -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)

@ -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):

@ -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);

@ -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;

@ -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)

@ -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(