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