[Bf-blender-cvs] [ebe04bd3caf] master: PyAPI: support Operator.poll functions 'disabled' hint

Campbell Barton noreply at git.blender.org
Tue Apr 20 04:08:32 CEST 2021


Commit: ebe04bd3cafaa1f88bd51eee5b3e7bef38ae69bc
Author: Campbell Barton
Date:   Tue Apr 20 11:57:28 2021 +1000
Branches: master
https://developer.blender.org/rBebe04bd3cafaa1f88bd51eee5b3e7bef38ae69bc

PyAPI: support Operator.poll functions 'disabled' hint

Python scripts can now define the reason it's poll function fails using:

`Operator.poll_message_set(message, ...)`

This supports both regular text as well as delaying message creation
using a callback which should be used in situations where constructing
detailed messages is too much overhead for a poll function.

Ref D11001

===================================================================

M	source/blender/blenkernel/BKE_context.h
M	source/blender/blenkernel/intern/context.c
M	source/blender/editors/interface/interface_region_tooltip.c
M	source/blender/python/intern/CMakeLists.txt
M	source/blender/python/intern/bpy_interface.c
M	source/blender/python/intern/bpy_operator.c
A	source/blender/python/intern/bpy_rna_operator.c
A	source/blender/python/intern/bpy_rna_operator.h
M	source/blender/python/intern/bpy_rna_types_capi.c

===================================================================

diff --git a/source/blender/blenkernel/BKE_context.h b/source/blender/blenkernel/BKE_context.h
index cbc5ea818ea..cbebd1dfe0e 100644
--- a/source/blender/blenkernel/BKE_context.h
+++ b/source/blender/blenkernel/BKE_context.h
@@ -206,8 +206,24 @@ void CTX_wm_area_set(bContext *C, struct ScrArea *area);
 void CTX_wm_region_set(bContext *C, struct ARegion *region);
 void CTX_wm_menu_set(bContext *C, struct ARegion *menu);
 void CTX_wm_gizmo_group_set(bContext *C, struct wmGizmoGroup *gzgroup);
-const char *CTX_wm_operator_poll_msg_get(struct bContext *C);
+
+/**
+ * Values to create the message that describes the reason poll failed.
+ *
+ * \note This must be called in the same context as the poll function that created it.
+ */
+struct bContextPollMsgDyn_Params {
+  /** The result is allocated . */
+  char *(*get_fn)(bContext *C, void *user_data);
+  /** Optionally free the user-data. */
+  void (*free_fn)(bContext *C, void *user_data);
+  void *user_data;
+};
+
+const char *CTX_wm_operator_poll_msg_get(struct bContext *C, bool *r_free);
 void CTX_wm_operator_poll_msg_set(struct bContext *C, const char *msg);
+void CTX_wm_operator_poll_msg_set_dynamic(bContext *C,
+                                          const struct bContextPollMsgDyn_Params *parms);
 void CTX_wm_operator_poll_msg_clear(struct bContext *C);
 
 /* Data Context
diff --git a/source/blender/blenkernel/intern/context.c b/source/blender/blenkernel/intern/context.c
index d46261df959..81830f5bb61 100644
--- a/source/blender/blenkernel/intern/context.c
+++ b/source/blender/blenkernel/intern/context.c
@@ -87,6 +87,10 @@ struct bContext {
      * For more advanced formatting use `operator_poll_msg_dyn_params`.
      */
     const char *operator_poll_msg;
+    /**
+     * Store values to dynamically to create the string (called when a tool-tip is shown).
+     */
+    struct bContextPollMsgDyn_Params operator_poll_msg_dyn_params;
   } wm;
 
   /* data context */
@@ -119,11 +123,16 @@ bContext *CTX_copy(const bContext *C)
 {
   bContext *newC = MEM_dupallocN((void *)C);
 
+  memset(&newC->wm.operator_poll_msg_dyn_params, 0, sizeof(newC->wm.operator_poll_msg_dyn_params));
+
   return newC;
 }
 
 void CTX_free(bContext *C)
 {
+  /* This may contain a dynamically allocated message, free. */
+  CTX_wm_operator_poll_msg_clear(C);
+
   MEM_freeN(C);
 }
 
@@ -1011,15 +1020,43 @@ void CTX_wm_gizmo_group_set(bContext *C, struct wmGizmoGroup *gzgroup)
 
 void CTX_wm_operator_poll_msg_clear(bContext *C)
 {
+  struct bContextPollMsgDyn_Params *params = &C->wm.operator_poll_msg_dyn_params;
+  if (params->free_fn != NULL) {
+    params->free_fn(C, params->user_data);
+  }
+  params->get_fn = NULL;
+  params->free_fn = NULL;
+  params->user_data = NULL;
+
   C->wm.operator_poll_msg = NULL;
 }
 void CTX_wm_operator_poll_msg_set(bContext *C, const char *msg)
 {
+  CTX_wm_operator_poll_msg_clear(C);
+
   C->wm.operator_poll_msg = msg;
 }
 
-const char *CTX_wm_operator_poll_msg_get(bContext *C)
+void CTX_wm_operator_poll_msg_set_dynamic(bContext *C,
+                                          const struct bContextPollMsgDyn_Params *params)
 {
+  CTX_wm_operator_poll_msg_clear(C);
+
+  C->wm.operator_poll_msg_dyn_params = *params;
+}
+
+const char *CTX_wm_operator_poll_msg_get(bContext *C, bool *r_free)
+{
+  struct bContextPollMsgDyn_Params *params = &C->wm.operator_poll_msg_dyn_params;
+  if (params->get_fn != NULL) {
+    char *msg = params->get_fn(C, params->user_data);
+    if (msg != NULL) {
+      *r_free = true;
+    }
+    return msg;
+  }
+
+  *r_free = false;
   return IFACE_(C->wm.operator_poll_msg);
 }
 
diff --git a/source/blender/editors/interface/interface_region_tooltip.c b/source/blender/editors/interface/interface_region_tooltip.c
index e5287ee6733..accfb78ab94 100644
--- a/source/blender/editors/interface/interface_region_tooltip.c
+++ b/source/blender/editors/interface/interface_region_tooltip.c
@@ -947,12 +947,13 @@ static uiTooltipData *ui_tooltip_data_from_button(bContext *C, uiBut *but)
   /* button is disabled, we may be able to tell user why */
   if (but->flag & UI_BUT_DISABLED) {
     const char *disabled_msg = NULL;
+    bool disabled_msg_free = false;
 
     /* if operator poll check failed, it can give pretty precise info why */
     if (but->optype) {
       CTX_wm_operator_poll_msg_clear(C);
       WM_operator_poll_context(C, but->optype, but->opcontext);
-      disabled_msg = CTX_wm_operator_poll_msg_get(C);
+      disabled_msg = CTX_wm_operator_poll_msg_get(C, &disabled_msg_free);
     }
     /* alternatively, buttons can store some reasoning too */
     else if (but->disabled_info) {
@@ -967,6 +968,9 @@ static uiTooltipData *ui_tooltip_data_from_button(bContext *C, uiBut *but)
                                              });
       field->text = BLI_sprintfN(TIP_("Disabled: %s"), disabled_msg);
     }
+    if (disabled_msg_free) {
+      MEM_freeN((void *)disabled_msg);
+    }
   }
 
   if ((U.flag & USER_TOOLTIPS_PYTHON) && !but->optype && rna_struct.strinfo) {
diff --git a/source/blender/python/intern/CMakeLists.txt b/source/blender/python/intern/CMakeLists.txt
index 9ac8d4d9f47..2be2105d327 100644
--- a/source/blender/python/intern/CMakeLists.txt
+++ b/source/blender/python/intern/CMakeLists.txt
@@ -79,6 +79,7 @@ set(SRC
   bpy_rna_driver.c
   bpy_rna_gizmo.c
   bpy_rna_id_collection.c
+  bpy_rna_operator.c
   bpy_rna_types_capi.c
   bpy_rna_ui.c
   bpy_traceback.c
@@ -118,6 +119,7 @@ set(SRC
   bpy_rna_driver.h
   bpy_rna_gizmo.h
   bpy_rna_id_collection.h
+  bpy_rna_operator.h
   bpy_rna_types_capi.h
   bpy_rna_ui.h
   bpy_traceback.h
diff --git a/source/blender/python/intern/bpy_interface.c b/source/blender/python/intern/bpy_interface.c
index 5f31e0bb74d..4144063cf5c 100644
--- a/source/blender/python/intern/bpy_interface.c
+++ b/source/blender/python/intern/bpy_interface.c
@@ -167,6 +167,14 @@ void bpy_context_clear(bContext *UNUSED(C), const PyGILState_STATE *gilstate)
   }
 }
 
+static void bpy_context_end(bContext *C)
+{
+  if (UNLIKELY(C == NULL)) {
+    return;
+  }
+  CTX_wm_operator_poll_msg_clear(C);
+}
+
 /**
  * Use for `CTX_*_set(..)` functions need to set values which are later read back as expected.
  * In this case we don't want the Python context to override the values as it causes problems
@@ -524,6 +532,9 @@ void BPY_python_end(void)
   /* finalizing, no need to grab the state, except when we are a module */
   gilstate = PyGILState_Ensure();
 
+  /* Clear Python values in the context so freeing the context after Python exits doesn't crash. */
+  bpy_context_end(BPY_context_get());
+
   /* Decrement user counts of all callback functions. */
   BPY_rna_props_clear_all();
 
diff --git a/source/blender/python/intern/bpy_operator.c b/source/blender/python/intern/bpy_operator.c
index a4622915b73..4a5e2552598 100644
--- a/source/blender/python/intern/bpy_operator.c
+++ b/source/blender/python/intern/bpy_operator.c
@@ -244,12 +244,16 @@ static PyObject *pyop_call(PyObject *UNUSED(self), PyObject *args)
   }
 
   if (WM_operator_poll_context((bContext *)C, ot, context) == false) {
-    const char *msg = CTX_wm_operator_poll_msg_get(C);
+    bool msg_free = false;
+    const char *msg = CTX_wm_operator_poll_msg_get(C, &msg_free);
     PyErr_Format(PyExc_RuntimeError,
                  "Operator bpy.ops.%.200s.poll() %.200s",
                  opname,
                  msg ? msg : "failed, context is incorrect");
     CTX_wm_operator_poll_msg_clear(C);
+    if (msg_free) {
+      MEM_freeN((void *)msg);
+    }
     error_val = -1;
   }
   else {
diff --git a/source/blender/python/intern/bpy_rna_operator.c b/source/blender/python/intern/bpy_rna_operator.c
new file mode 100644
index 00000000000..6e0db3eca49
--- /dev/null
+++ b/source/blender/python/intern/bpy_rna_operator.c
@@ -0,0 +1,150 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+/** \file
+ * \ingroup pythonintern
+ *
+ * This file extends `bpy.types.Operator` with C/Python API methods and attributes.
+ */
+
+#include <Python.h>
+
+#include "BLI_string.h"
+
+#include "BKE_context.h"
+
+#include "../generic/python_utildefines.h"
+
+#include "BPY_extern.h"
+#include "bpy_capi_utils.h"
+
+/* -------------------------------------------------------------------- */
+/** \name Operator `poll_message_set` Method
+ * \{ */
+
+static char *pyop_poll_message_get_fn(bContext *UNUSED(C), void *user_data)
+{
+  PyGILState_STATE gilstate = PyGILState_Ensure();
+
+  PyObject *py_args = user_data;
+  PyObject *py_func_or_msg = PyTuple_GET_ITEM(py_args, 0);
+
+  if (PyUnicode_Check(py_func_or_msg)) {
+    return BLI_strdup(PyUnicode_AsUTF8(py_func_or_msg));
+  }
+
+  PyObject *py_args_after_first = PyTuple_GetSlice(py_args, 1, PY_SSIZE_T_MAX);
+  PyObject *py_msg = PyObject_CallObject(py_func_or_msg, py_args_after_first);
+  Py_DECREF(py_args_after_first);
+
+  char *msg = NULL;
+  bool error = false;
+
+  /* NULL for no string. */
+  if (py_msg == NULL) {
+    error = true;
+  }
+  else {
+    if (py_msg == Py_None) {
+      /* pass */
+    }
+    else if (PyUnicode_Check(py_msg)) {
+      msg = BLI_strdup(PyUnicode_AsUTF8(py_msg));
+    }
+    else {
+      PyErr_Format(PyExc_TypeError,
+                   "poll_message_set(function, ...): expected string or None, got %.200s",
+                   Py_TYPE(py_msg)->tp_name);
+      error = true;
+    }
+    Py_DECREF(py_msg);
+  }
+
+  if (error) {
+    PyErr_Print();
+    PyErr_Clear();
+  }
+
+  PyGILState_Release(gilstate);
+  return msg;
+}
+
+static void pyop_poll_message_free_fn(bCo

@@ Diff output truncated at 10240 characters. @@



More information about the Bf-blender-cvs mailing list