1# SPDX-License-Identifier: GPL-2.0-or-later
2
3# for slightly faster access
4from _bpy import ops as _ops_module
5
6# op_add = _ops_module.add
7_op_dir = _ops_module.dir
8_op_poll = _ops_module.poll
9_op_call = _ops_module.call
10_op_as_string = _ops_module.as_string
11_op_get_rna_type = _ops_module.get_rna_type
12_op_get_bl_options = _ops_module.get_bl_options
13
14_ModuleType = type(_ops_module)
15
16
17# -----------------------------------------------------------------------------
18# Callable Operator Wrapper
19
20class _BPyOpsSubModOp:
21 """
22 Utility class to fake submodule operators.
23
24 eg. bpy.ops.object.somefunc
25 """
26
27 __slots__ = ("_module", "_func")
28
29 def _get_doc(self):
30 idname = self.idname()
31 sig = _op_as_string(self.idname())
32 # XXX You never quite know what you get from bpy.types,
33 # with operators... Operator and OperatorProperties
34 # are shadowing each other, and not in the same way for
35 # native ops and py ones! See T39158.
36 # op_class = getattr(bpy.types, idname)
37 op_class = _op_get_rna_type(idname)
38 descr = op_class.description
39 return "%s\n%s" % (sig, descr)
40
41 @staticmethod
42 def _parse_args(args):
43 C_dict = None
44 C_exec = 'EXEC_DEFAULT'
45 C_undo = False
46
47 is_dict = is_exec = is_undo = False
48
49 for arg in args:
50 if is_dict is False and isinstance(arg, dict):
51 if is_exec is True or is_undo is True:
52 raise ValueError("dict arg must come first")
53 C_dict = arg
54 is_dict = True
55 elif is_exec is False and isinstance(arg, str):
56 if is_undo is True:
57 raise ValueError("string arg must come before the boolean")
58 C_exec = arg
59 is_exec = True
60 elif is_undo is False and isinstance(arg, int):
61 C_undo = arg
62 is_undo = True
63 else:
64 raise ValueError("1-3 args execution context is supported")
65
66 return C_dict, C_exec, C_undo
67
68 @staticmethod
69 def _view_layer_update(context):
70 view_layer = context.view_layer
71 if view_layer: # None in background mode
72 view_layer.update()
73 else:
74 import bpy
75 for scene in bpy.data.scenes:
76 for view_layer in scene.view_layers:
77 view_layer.update()
78
79 __doc__ = property(_get_doc)
80
81 def __init__(self, module, func):
82 self._module = module
83 self._func = func
84
85 def poll(self, *args):
86 C_dict, C_exec, _C_undo = _BPyOpsSubModOp._parse_args(args)
87 return _op_poll(self.idname_py(), C_dict, C_exec)
88
89 def idname(self):
90 # submod.foo -> SUBMOD_OT_foo
91 return self._module.upper() + "_OT_" + self._func
92
93 def idname_py(self):
94 return self._module + "." + self._func
95
96 def __call__(self, *args, **kw):
97 import bpy
98 context = bpy.context
99
100 # Get the operator from blender
101 wm = context.window_manager
102
103 # Run to account for any RNA values the user changes.
104 # NOTE: We only update active view-layer, since that's what
105 # operators are supposed to operate on. There might be some
106 # corner cases when operator need a full scene update though.
107 _BPyOpsSubModOp._view_layer_update(context)
108
109 if args:
110 C_dict, C_exec, C_undo = _BPyOpsSubModOp._parse_args(args)
111 ret = _op_call(self.idname_py(), C_dict, kw, C_exec, C_undo)
112 else:
113 ret = _op_call(self.idname_py(), None, kw)
114
115 if 'FINISHED' in ret and context.window_manager == wm:
116 _BPyOpsSubModOp._view_layer_update(context)
117
118 return ret
119
120 def get_rna_type(self):
121 """Internal function for introspection"""
122 return _op_get_rna_type(self.idname())
123
124 @property
125 def bl_options(self):
126 return _op_get_bl_options(self.idname())
127
128 def __repr__(self): # useful display, repr(op)
129 return _op_as_string(self.idname())
130
131 def __str__(self): # used for print(...)
132 return ("<function bpy.ops.%s.%s at 0x%x'>" %
133 (self._module, self._func, id(self)))
134
135
136# -----------------------------------------------------------------------------
137# Sub-Module Access
138
139def _bpy_ops_submodule__getattr__(module, func):
140 # Return a value from `bpy.ops.{module}.{func}`
141 if func.startswith("__"):
142 raise AttributeError(func)
143 return _BPyOpsSubModOp(module, func)
144
145
146def _bpy_ops_submodule__dir__(module):
147 functions = set()
148 module_upper = module.upper()
149
150 for id_name in _op_dir():
151 id_split = id_name.split("_OT_", 1)
152 if len(id_split) == 2 and module_upper == id_split[0]:
153 functions.add(id_split[1])
154
155 return list(functions)
156
157
158def _bpy_ops_submodule(module):
159 result = _ModuleType("bpy.ops." + module)
160 result.__getattr__ = lambda func: _bpy_ops_submodule__getattr__(module, func)
161 result.__dir__ = lambda: _bpy_ops_submodule__dir__(module)
162 return result
163
164
165# -----------------------------------------------------------------------------
166# Module Access
167
168def __getattr__(module):
169 # Return a value from `bpy.ops.{module}`.
170 if module.startswith("__"):
171 raise AttributeError(module)
172 return _bpy_ops_submodule(module)
173
174
175def __dir__():
176 submodules = set()
177 for id_name in _op_dir():
178 id_split = id_name.split("_OT_", 1)
179
180 if len(id_split) == 2:
181 submodules.add(id_split[0].lower())
182 else:
183 submodules.add(id_split[0])
184
185 return list(submodules)