From 5a6d5d20de2cfa43d14d67970b2f2eb24048b230 Mon Sep 17 00:00:00 2001 From: Campbell Barton Date: Wed, 31 Mar 2021 00:44:29 +1100 Subject: UI: add description methods for `wm.context_*` operators Generic context operators now look-up the RNA properties to extract their description (when it's available). Add `bl_rna_utils.data_path.property_definition_from_data_path()` to handle the details of accessing the RNA property definition. --- release/scripts/modules/bl_rna_utils/__init__.py | 0 release/scripts/modules/bl_rna_utils/data_path.py | 80 ++++++++++++++++++++ release/scripts/startup/bl_operators/wm.py | 90 +++++++++++++++++++++++ 3 files changed, 170 insertions(+) create mode 100644 release/scripts/modules/bl_rna_utils/__init__.py create mode 100644 release/scripts/modules/bl_rna_utils/data_path.py (limited to 'release') diff --git a/release/scripts/modules/bl_rna_utils/__init__.py b/release/scripts/modules/bl_rna_utils/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/release/scripts/modules/bl_rna_utils/data_path.py b/release/scripts/modules/bl_rna_utils/data_path.py new file mode 100644 index 00000000000..76306e379f0 --- /dev/null +++ b/release/scripts/modules/bl_rna_utils/data_path.py @@ -0,0 +1,80 @@ +# ##### BEGIN GPL LICENSE BLOCK ##### +# +# 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. +# +# ##### END GPL LICENSE BLOCK ##### + +# + +__all__ = ( + "property_definition_from_data_path", +) + +class _TokenizeDataPath: + """Class to split up tokens of a data-path.""" + __slots__ = ( + "data_path", + ) + + def __init__(self, attrs): + self.data_path = attrs + + def __getattr__(self, attr): + return _TokenizeDataPath(self.data_path + ((".%s" % attr),)) + + def __getitem__(self, key): + return _TokenizeDataPath(self.data_path + (("[%r]" % (key,)),)) + + def __call__(self, *args, **kw): + value_str = ", ".join([ + val for val in ( + repr(args)[1:-1], + ", ".join(["%s=%r" % (key, value) for key, value in kw.items()]) + ) if val]) + return _TokenizeDataPath(self.data_path + ('(%s)' % value_str, )) + + def __iter__(self): + return iter(self.data_path) + + +def property_definition_from_data_path(base, data_path): + """ + Return an RNA property definition from an object and a data path. + + In Blender this is often used with ``context`` as the base and a + path that it references, for example ``.space_data.lock_camera``. + """ + base_tokenize = _TokenizeDataPath(()) + data = list(eval("base_tokenize" + data_path)) + del base_tokenize + while data and (not data[-1].startswith(".")): + data.pop() + + if (not data) or (not data[-1].startswith(".")) or (len(data) < 2): + return None + + data_path_head = "".join(data[:-1]) + data_path_tail = data[-1] + + value_head = eval("base" + data_path_head) + value_head_rna = getattr(value_head, "bl_rna", None) + if value_head_rna is None: + return None + + value_tail = value_head.bl_rna.properties.get(data_path_tail[1:]) + if not value_tail: + return None + + return value_tail diff --git a/release/scripts/startup/bl_operators/wm.py b/release/scripts/startup/bl_operators/wm.py index 2f97942faa4..18e938ac9b2 100644 --- a/release/scripts/startup/bl_operators/wm.py +++ b/release/scripts/startup/bl_operators/wm.py @@ -95,6 +95,28 @@ def context_path_validate(context, data_path): return value +def context_path_description(context, data_path): + from bl_rna_utils.data_path import property_definition_from_data_path + rna_prop = property_definition_from_data_path(context, "." + data_path) + if rna_prop is not None: + description = rna_prop.description + if description: + return description + return None + + +def description_from_data_path(base, data_path, *, prefix, value=Ellipsis): + if context_path_validate(base, data_path) is Ellipsis: + return None + description = context_path_description(base, data_path) + if description: + description = "%s: %s" % (prefix, description) + if value != Ellipsis: + description = "%s\n%s: %s" % (description, iface_("Value"), str(value)) + return description + return None + + def operator_value_is_undo(value): if value in {None, Ellipsis}: return False @@ -168,6 +190,10 @@ class WM_OT_context_set_boolean(Operator): default=True, ) + @classmethod + def description(cls, context, props): + return description_from_data_path(context, props.data_path, prefix=iface_("Assign"), value=props.value) + execute = execute_context_assign @@ -185,6 +211,10 @@ class WM_OT_context_set_int(Operator): # same as enum ) relative: rna_relative_prop + @classmethod + def description(cls, context, props): + return description_from_data_path(context, props.data_path, prefix="Assign", value=props.value) + execute = execute_context_assign @@ -201,6 +231,10 @@ class WM_OT_context_scale_float(Operator): default=1.0, ) + @classmethod + def description(cls, context, props): + return description_from_data_path(context, props.data_path, prefix=iface_("Scale"), value=props.value) + def execute(self, context): data_path = self.data_path if context_path_validate(context, data_path) is Ellipsis: @@ -235,6 +269,10 @@ class WM_OT_context_scale_int(Operator): options={'SKIP_SAVE'}, ) + @classmethod + def description(cls, context, props): + return description_from_data_path(context, props.data_path, prefix=iface_("Scale"), value=props.value) + def execute(self, context): data_path = self.data_path if context_path_validate(context, data_path) is Ellipsis: @@ -274,6 +312,10 @@ class WM_OT_context_set_float(Operator): # same as enum ) relative: rna_relative_prop + @classmethod + def description(cls, context, props): + return description_from_data_path(context, props.data_path, prefix="Assign", value=props.value) + execute = execute_context_assign @@ -290,6 +332,10 @@ class WM_OT_context_set_string(Operator): # same as enum maxlen=1024, ) + @classmethod + def description(cls, context, props): + return description_from_data_path(context, props.data_path, prefix=iface_("Assign"), value=props.value) + execute = execute_context_assign @@ -306,6 +352,10 @@ class WM_OT_context_set_enum(Operator): maxlen=1024, ) + @classmethod + def description(cls, context, props): + return description_from_data_path(context, props.data_path, prefix=iface_("Assign"), value=props.value) + execute = execute_context_assign @@ -322,6 +372,10 @@ class WM_OT_context_set_value(Operator): maxlen=1024, ) + @classmethod + def description(cls, context, props): + return description_from_data_path(context, props.data_path, prefix=iface_("Assign"), value=props.value) + def execute(self, context): data_path = self.data_path if context_path_validate(context, data_path) is Ellipsis: @@ -339,6 +393,13 @@ class WM_OT_context_toggle(Operator): data_path: rna_path_prop module: rna_module_prop + @classmethod + def description(cls, context, props): + # Currently unsupported, it might be possible to extract this. + if props.module: + return None + return description_from_data_path(context, props.data_path, prefix=iface_("Toggle")) + def execute(self, context): data_path = self.data_path @@ -375,6 +436,11 @@ class WM_OT_context_toggle_enum(Operator): maxlen=1024, ) + @classmethod + def description(cls, context, props): + value = "(%r, %r)" % (props.value_1, props.value_2) + return description_from_data_path(context, props.data_path, prefix=iface_("Toggle"), value=value) + def execute(self, context): data_path = self.data_path @@ -406,6 +472,10 @@ class WM_OT_context_cycle_int(Operator): reverse: rna_reverse_prop wrap: rna_wrap_prop + @classmethod + def description(cls, context, props): + return description_from_data_path(context, props.data_path, prefix=iface_("Cycle")) + def execute(self, context): data_path = self.data_path value = context_path_validate(context, data_path) @@ -442,6 +512,10 @@ class WM_OT_context_cycle_enum(Operator): reverse: rna_reverse_prop wrap: rna_wrap_prop + @classmethod + def description(cls, context, props): + return description_from_data_path(context, props.data_path, prefix=iface_("Cycle")) + def execute(self, context): data_path = self.data_path value = context_path_validate(context, data_path) @@ -498,6 +572,10 @@ class WM_OT_context_cycle_array(Operator): data_path: rna_path_prop reverse: rna_reverse_prop + @classmethod + def description(cls, context, props): + return description_from_data_path(context, props.data_path, prefix=iface_("Cycle")) + def execute(self, context): data_path = self.data_path value = context_path_validate(context, data_path) @@ -523,6 +601,10 @@ class WM_OT_context_menu_enum(Operator): data_path: rna_path_prop + @classmethod + def description(cls, context, props): + return description_from_data_path(context, props.data_path, prefix=iface_("Menu")) + def execute(self, context): data_path = self.data_path value = context_path_validate(context, data_path) @@ -550,6 +632,10 @@ class WM_OT_context_pie_enum(Operator): data_path: rna_path_prop + @classmethod + def description(cls, context, props): + return description_from_data_path(context, props.data_path, prefix=iface_("Pie Menu")) + def invoke(self, context, event): wm = context.window_manager data_path = self.data_path @@ -587,6 +673,10 @@ class WM_OT_operator_pie_enum(Operator): maxlen=1024, ) + @classmethod + def description(cls, context, props): + return description_from_data_path(context, props.data_path, prefix=iface_("Pie Menu")) + def invoke(self, context, event): wm = context.window_manager -- cgit v1.2.3