Python API: implement an Operator callback for dynamic description.

Blender UI Layout API allows supplying parameters to operators via
button definitions. If an operator behavior strongly depends on its
parameters, it may be difficult to write a tooltip that covers all
of its operation modes. Thus it is useful to provide a way for the
operator to produce different descriptions based on the input info.

Reviewers: campbellbarton

Differential Revision: https://developer.blender.org/D5709
This commit is contained in:
Alexander Gavrilov 2019-09-06 16:26:10 +03:00
parent b0b24b77ff
commit 9ecbd67dfb
Notes: blender-bot 2023-02-14 11:21:40 +01:00
Referenced by issue #69808, Python-defined nodes implementing a draw_label function causes a segmentation fault when drawn.
Referenced by issue #69633, New Operator Description function alters behavior of operator_menu_enums
Referenced by issue #66606, Blender crashes in LookDev/EEVEE but not in Solid/Cycles
Referenced by issue #61853, Crash on Eevee render (MacOS)
8 changed files with 114 additions and 14 deletions

View File

@ -4250,7 +4250,7 @@ static uiBut *ui_def_but_operator_ptr(uiBlock *block,
}
}
if ((!tip || tip[0] == '\0') && ot && ot->srna) {
if ((!tip || tip[0] == '\0') && ot && ot->srna && !ot->get_description) {
tip = RNA_struct_ui_description(ot->srna);
}
@ -6350,6 +6350,9 @@ void UI_but_string_info_get(bContext *C, uiBut *but, ...)
else if (but->tip && but->tip[0]) {
tmp = BLI_strdup(but->tip);
}
else if (but->optype && but->optype->get_description) {
tmp = WM_operatortype_description(C, but->optype, but->opptr);
}
else {
type = BUT_GET_RNA_TIP; /* Fail-safe solution... */
}

View File

@ -932,18 +932,14 @@ static uiTooltipData *ui_tooltip_data_from_gizmo(bContext *C, wmGizmo *gz)
NULL;
if (gzop != NULL) {
/* Description */
const char *info = RNA_struct_ui_description(gzop->type->srna);
if (!(info && info[0])) {
info = RNA_struct_ui_name(gzop->type->srna);
}
char *info = WM_operatortype_description(C, gzop->type, &gzop->ptr);
if (info != NULL) {
char *text = info;
if (info && info[0]) {
char *text = NULL;
if (gzop_actions[i].prefix != NULL) {
text = BLI_sprintfN("%s: %s", gzop_actions[i].prefix, info);
}
else {
text = BLI_strdup(info);
MEM_freeN(info);
}
if (text != NULL) {

View File

@ -1420,6 +1420,39 @@ static void rna_operator_cancel_cb(bContext *C, wmOperator *op)
RNA_parameter_list_free(&list);
}
static char *rna_operator_description_cb(bContext *C, wmOperatorType *ot, PointerRNA *prop_ptr)
{
extern FunctionRNA rna_Operator_description_func;
PointerRNA ptr;
ParameterList list;
FunctionRNA *func;
void *ret;
char *result;
RNA_pointer_create(NULL, ot->ext.srna, NULL, &ptr); /* dummy */
func = &rna_Operator_description_func; /* RNA_struct_find_function(&ptr, "description"); */
RNA_parameter_list_create(&list, &ptr, func);
RNA_parameter_set_lookup(&list, "context", &C);
RNA_parameter_set_lookup(&list, "properties", prop_ptr);
ot->ext.call(C, &ptr, func, &list);
RNA_parameter_get_lookup(&list, "result", &ret);
result = (char *)ret;
if (result && result[0]) {
result = BLI_strdup(result);
}
else {
result = NULL;
}
RNA_parameter_list_free(&list);
return result;
}
static void rna_Operator_unregister(struct Main *bmain, StructRNA *type);
/* bpy_operator_wrap.c */
@ -1437,7 +1470,7 @@ static StructRNA *rna_Operator_register(Main *bmain,
wmOperatorType dummyot = {NULL};
wmOperator dummyop = {NULL};
PointerRNA dummyotr;
int have_function[7];
int have_function[8];
struct {
char idname[OP_MAX_TYPENAME];
@ -1531,6 +1564,7 @@ static StructRNA *rna_Operator_register(Main *bmain,
dummyot.modal = (have_function[4]) ? rna_operator_modal_cb : NULL;
dummyot.ui = (have_function[5]) ? rna_operator_draw_cb : NULL;
dummyot.cancel = (have_function[6]) ? rna_operator_cancel_cb : NULL;
dummyot.get_description = (have_function[7]) ? rna_operator_description_cb : NULL;
WM_operatortype_append_ptr(BPY_RNA_operator_wrapper, (void *)&dummyot);
/* update while blender is running */

View File

@ -957,6 +957,18 @@ void RNA_api_operator(StructRNA *srna)
RNA_def_function_flag(func, FUNC_REGISTER_OPTIONAL | FUNC_ALLOW_WRITE);
parm = RNA_def_pointer(func, "context", "Context", "", "");
RNA_def_parameter_flags(parm, PROP_NEVER_NULL, PARM_REQUIRED);
/* description */
func = RNA_def_function(srna, "description", NULL);
RNA_def_function_ui_description(func, "Compute a description string that depends on parameters");
RNA_def_function_flag(func, FUNC_NO_SELF | FUNC_REGISTER_OPTIONAL);
parm = RNA_def_string(func, "result", NULL, 4096, "result", "");
RNA_def_parameter_flags(parm, PROP_THICK_WRAP, 0);
RNA_def_function_output(func, parm);
parm = RNA_def_pointer(func, "context", "Context", "", "");
RNA_def_parameter_flags(parm, PROP_NEVER_NULL, PARM_REQUIRED);
parm = RNA_def_pointer(func, "properties", "OperatorProperties", "", "");
RNA_def_parameter_flags(parm, PROP_NEVER_NULL, PARM_REQUIRED | PARM_RNAPTR);
}
void RNA_api_macro(StructRNA *srna)

View File

@ -1768,7 +1768,12 @@ static int pyrna_py_to_prop(
if (value == Py_None) {
if ((RNA_property_flag(prop) & PROP_NEVER_NULL) == 0) {
if (data) {
*((char **)data) = (char *)NULL;
if (RNA_property_flag(prop) & PROP_THICK_WRAP) {
*(char *)data = 0;
}
else {
*((char **)data) = (char *)NULL;
}
}
else {
RNA_property_string_set(ptr, prop, NULL);
@ -1813,7 +1818,12 @@ static int pyrna_py_to_prop(
}
else {
if (data) {
*((char **)data) = (char *)param;
if (RNA_property_flag(prop) & PROP_THICK_WRAP) {
BLI_strncpy((char *)data, (char *)param, RNA_property_string_maxlength(prop));
}
else {
*((char **)data) = (char *)param;
}
}
else {
RNA_property_string_set_bytes(ptr, prop, param, PyBytes_Size(value));
@ -1862,7 +1872,12 @@ static int pyrna_py_to_prop(
/* XXX, this is suspect, but needed for function calls,
* need to see if there's a better way. */
if (data) {
*((char **)data) = (char *)param;
if (RNA_property_flag(prop) & PROP_THICK_WRAP) {
BLI_strncpy((char *)data, (char *)param, RNA_property_string_maxlength(prop));
}
else {
*((char **)data) = (char *)param;
}
}
else {
RNA_property_string_set(ptr, prop, param);

View File

@ -547,6 +547,9 @@ struct wmOperatorTypeMacro *WM_operatortype_macro_define(struct wmOperatorType *
const char *idname);
const char *WM_operatortype_name(struct wmOperatorType *ot, struct PointerRNA *properties);
char *WM_operatortype_description(struct bContext *C,
struct wmOperatorType *ot,
struct PointerRNA *properties);
/* wm_uilist_type.c */
void WM_uilisttype_init(void);

View File

@ -737,6 +737,12 @@ typedef struct wmOperatorType {
*/
const char *(*get_name)(struct wmOperatorType *, struct PointerRNA *);
/**
* Return a different description to use in the user interface, based on property values.
* The returned string must be freed by the caller, unless NULL.
*/
char *(*get_description)(struct bContext *C, struct wmOperatorType *, struct PointerRNA *);
/** rna for properties */
struct StructRNA *srna;

View File

@ -595,4 +595,35 @@ const char *WM_operatortype_name(struct wmOperatorType *ot, struct PointerRNA *p
return (name && name[0]) ? name : RNA_struct_ui_name(ot->srna);
}
char *WM_operatortype_description(struct bContext *C,
struct wmOperatorType *ot,
struct PointerRNA *properties)
{
if (ot->get_description && properties) {
char *description = ot->get_description(C, ot, properties);
if (description) {
if (description[0]) {
return description;
}
else {
MEM_freeN(description);
}
}
}
const char *info = RNA_struct_ui_description(ot->srna);
if (!(info && info[0])) {
info = RNA_struct_ui_name(ot->srna);
}
if (info && info[0]) {
return BLI_strdup(info);
}
else {
return NULL;
}
}
/** \} */