664dc79b28b72239010d47633a6f58ad5c1c0964
[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 = ops_module.get_rna
30 op_get_rna_type = ops_module.get_rna_type
31 op_get_instance = ops_module.get_instance
32
33
34 class BPyOps:
35     """
36     Fake module like class.
37
38      bpy.ops
39     """
40     __slots__ = ()
41
42     def __getattr__(self, module):
43         """
44         gets a bpy.ops submodule
45         """
46         if module.startswith('__'):
47             raise AttributeError(module)
48         return BPyOpsSubMod(module)
49
50     def __dir__(self):
51
52         submodules = set()
53
54         # add this classes functions
55         for id_name in dir(self.__class__):
56             if not id_name.startswith('__'):
57                 submodules.add(id_name)
58
59         for id_name in op_dir():
60             id_split = id_name.split('_OT_', 1)
61
62             if len(id_split) == 2:
63                 submodules.add(id_split[0].lower())
64             else:
65                 submodules.add(id_split[0])
66
67         return list(submodules)
68
69     def __repr__(self):
70         return "<module like class 'bpy.ops'>"
71
72
73 class BPyOpsSubMod:
74     """
75     Utility class to fake submodules.
76
77     eg. bpy.ops.object
78     """
79     __slots__ = ("_module",)
80
81     def __init__(self, module):
82         self._module = module
83
84     def __getattr__(self, func):
85         """
86         gets a bpy.ops.submodule function
87         """
88         if func.startswith('__'):
89             raise AttributeError(func)
90         return BPyOpsSubModOp(self._module, func)
91
92     def __dir__(self):
93
94         functions = set()
95
96         module_upper = self._module.upper()
97
98         for id_name in op_dir():
99             id_split = id_name.split('_OT_', 1)
100             if len(id_split) == 2 and module_upper == id_split[0]:
101                 functions.add(id_split[1])
102
103         return list(functions)
104
105     def __repr__(self):
106         return "<module like class 'bpy.ops.%s'>" % self._module
107
108
109 class BPyOpsSubModOp:
110     """
111     Utility class to fake submodule operators.
112
113     eg. bpy.ops.object.somefunc
114     """
115
116     __slots__ = ("_module", "_func")
117
118     def _get_doc(self):
119         idname = self.idname()
120         sig = op_as_string(self.idname())
121         # XXX You never quite know what you get from bpy.types,
122         # with operators... Operator and OperatorProperties
123         # are shadowing each other, and not in the same way for
124         # native ops and py ones! See T39158.
125         # op_class = getattr(bpy.types, idname)
126         op_class = op_get_rna_type(idname)
127         descr = op_class.description
128         return f"{sig}\n{descr}"
129
130     @staticmethod
131     def _parse_args(args):
132         C_dict = None
133         C_exec = 'EXEC_DEFAULT'
134         C_undo = False
135
136         is_dict = is_exec = is_undo = False
137
138         for i, arg in enumerate(args):
139             if is_dict is False and isinstance(arg, dict):
140                 if is_exec is True or is_undo is True:
141                     raise ValueError("dict arg must come first")
142                 C_dict = arg
143                 is_dict = True
144             elif is_exec is False and isinstance(arg, str):
145                 if is_undo is True:
146                     raise ValueError("string arg must come before the boolean")
147                 C_exec = arg
148                 is_exec = True
149             elif is_undo is False and isinstance(arg, int):
150                 C_undo = arg
151                 is_undo = True
152             else:
153                 raise ValueError("1-3 args execution context is supported")
154
155         return C_dict, C_exec, C_undo
156
157     @staticmethod
158     def _scene_update(context):
159         scene = context.scene
160         if scene:  # None in background mode
161             scene.update()
162         else:
163             import bpy
164             for scene in bpy.data.scenes:
165                 scene.update()
166
167     __doc__ = property(_get_doc)
168
169     def __init__(self, module, func):
170         self._module = module
171         self._func = func
172
173     def poll(self, *args):
174         C_dict, C_exec, C_undo = BPyOpsSubModOp._parse_args(args)
175         return op_poll(self.idname_py(), C_dict, C_exec)
176
177     def idname(self):
178         # submod.foo -> SUBMOD_OT_foo
179         return self._module.upper() + "_OT_" + self._func
180
181     def idname_py(self):
182         # submod.foo -> SUBMOD_OT_foo
183         return self._module + "." + self._func
184
185     def __call__(self, *args, **kw):
186         import bpy
187         context = bpy.context
188
189         # Get the operator from blender
190         wm = context.window_manager
191
192         # run to account for any rna values the user changes.
193         BPyOpsSubModOp._scene_update(context)
194
195         if args:
196             C_dict, C_exec, C_undo = BPyOpsSubModOp._parse_args(args)
197             ret = op_call(self.idname_py(), C_dict, kw, C_exec, C_undo)
198         else:
199             ret = op_call(self.idname_py(), None, kw)
200
201         if 'FINISHED' in ret and context.window_manager == wm:
202             BPyOpsSubModOp._scene_update(context)
203
204         return ret
205
206     def get_rna_type(self):
207         """Internal function for introspection"""
208         return op_get_rna_type(self.idname())
209
210     def get_rna(self):
211         """Internal function for introspection"""
212         return op_get_rna(self.idname())
213
214     def get_instance(self):
215         """Internal function for introspection"""
216         return op_get_instance(self.idname())
217
218     def __repr__(self):  # useful display, repr(op)
219         # import bpy
220         return op_as_string(self.idname())
221
222     def __str__(self):  # used for print(...)
223         return ("<function bpy.ops.%s.%s at 0x%x'>" %
224                 (self._module, self._func, id(self)))
225
226
227 ops_fake_module = BPyOps()