BGE Py API docs: correct references to bge.keys
[blender.git] / doc / python_api / rst_from_bmesh_opdefines.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 compliant>
20
21 # This is a quite stupid script which extracts bmesh api docs from
22 # 'bmesh_opdefines.c' in order to avoid having to add a lot of introspection
23 # data access into the api.
24 #
25 # The script is stupid becase it makes assumptions about formatting...
26 # that each arg has its own line, that comments above or directly after will be __doc__ etc...
27 #
28 # We may want to replace this script with something else one day but for now its good enough.
29 # if it needs large updates it may be better to rewrite using a real parser or
30 # add introspection into bmesh.ops.
31 # - campbell
32
33 import os
34
35 CURRENT_DIR = os.path.abspath(os.path.dirname(__file__))
36 SOURCE_DIR = os.path.normpath(os.path.abspath(os.path.normpath(os.path.join(CURRENT_DIR, "..", ".."))))
37 FILE_OP_DEFINES_C = os.path.join(SOURCE_DIR, "source", "blender", "bmesh", "intern", "bmesh_opdefines.c")
38 OUT_RST = os.path.join(CURRENT_DIR, "rst", "bmesh.ops.rst")
39
40 HEADER = r"""
41 BMesh Operators (bmesh.ops)
42 ===========================
43
44 .. module:: bmesh.ops
45
46 This module gives access to low level bmesh operations.
47
48 Most operators take input and return output, they can be chained together
49 to perform useful operations.
50
51 .. note::
52
53    This API us new in 2.65 and not yet well tested.
54
55
56 Operator Example
57 ++++++++++++++++
58 This script shows how operators can be used to model a link of a chain.
59
60 .. literalinclude:: ../examples/bmesh.ops.1.py
61 """
62
63
64 def main():
65     fsrc = open(FILE_OP_DEFINES_C, 'r', encoding="utf-8")
66
67     blocks = []
68
69     is_block = False
70     is_comment = False  # /* global comments only */
71
72     comment_ctx = None
73     block_ctx = None
74
75     for l in fsrc:
76         l = l[:-1]
77         # weak but ok
78         if ("BMOpDefine" in l and l.split()[1] == "BMOpDefine") and not "bmo_opdefines[]" in l:
79             is_block = True
80             block_ctx = []
81             blocks.append((comment_ctx, block_ctx))
82         elif l.strip().startswith("/*"):
83             is_comment = True
84             comment_ctx = []
85
86         if is_block:
87             if l.strip().startswith("//"):
88                 pass
89             else:
90                 # remove c++ comment if we have one
91                 cpp_comment = l.find("//")
92                 if cpp_comment != -1:
93                     l = l[:cpp_comment]
94
95                 block_ctx.append(l)
96
97             if l.strip() == "};":
98                 is_block = False
99                 comment_ctx = None
100
101         if is_comment:
102             c_comment_start = l.find("/*")
103             if c_comment_start != -1:
104                 l = l[c_comment_start + 2:]
105
106             c_comment_end = l.find("*/")
107             if c_comment_end != -1:
108                 l = l[:c_comment_end]
109
110                 is_comment = False
111             comment_ctx.append(l)
112
113     fsrc.close()
114     del fsrc
115
116    # namespace hack
117     vars = (
118         "BMO_OP_SLOT_ELEMENT_BUF",
119         "BMO_OP_SLOT_BOOL",
120         "BMO_OP_SLOT_FLT",
121         "BMO_OP_SLOT_INT",
122         "BMO_OP_SLOT_MAT",
123         "BMO_OP_SLOT_VEC",
124         "BMO_OP_SLOT_PTR",
125         "BMO_OP_SLOT_MAPPING",
126
127         "BMO_OP_SLOT_SUBTYPE_MAP_ELEM",
128         "BMO_OP_SLOT_SUBTYPE_MAP_BOOL",
129         "BMO_OP_SLOT_SUBTYPE_MAP_INT",
130         "BMO_OP_SLOT_SUBTYPE_MAP_FLT",
131         "BMO_OP_SLOT_SUBTYPE_MAP_EMPTY",
132         "BMO_OP_SLOT_SUBTYPE_MAP_INTERNAL",
133
134         "BMO_OP_SLOT_SUBTYPE_PTR_SCENE",
135         "BMO_OP_SLOT_SUBTYPE_PTR_OBJECT",
136         "BMO_OP_SLOT_SUBTYPE_PTR_MESH",
137         "BMO_OP_SLOT_SUBTYPE_PTR_BMESH",
138
139         "BMO_OP_SLOT_SUBTYPE_ELEM_IS_SINGLE",
140
141         "BM_VERT",
142         "BM_EDGE",
143         "BM_FACE",
144
145         "BMO_OP_FLAG_UNTAN_MULTIRES",
146     )
147     vars_dict = {}
148     for i, v in enumerate(vars):
149         vars_dict[v] = (1 << i)
150     globals().update(vars_dict)
151     # reverse lookup
152     vars_dict_reverse = {v: k for k, v in vars_dict.items()}
153     # end namespace hack
154
155     blocks_py = []
156     for comment, b in blocks:
157         # magic, translate into python
158         b[0] = b[0].replace("static BMOpDefine ", "")
159
160         for i, l in enumerate(b):
161             l = l.strip()
162             l = l.replace("{", "(")
163             l = l.replace("}", ")")
164
165             if l.startswith("/*"):
166                 l = l.replace("/*", "'''own <")
167             else:
168                 l = l.replace("/*", "'''inline <")
169             l = l.replace("*/", ">''',")
170
171             # exec func. eg: bmo_rotate_edges_exec,
172             if l.startswith("bmo_") and l.endswith("_exec,"):
173                 l = "None,"
174             b[i] = l
175
176         #for l in b:
177         #    print(l)
178
179         text = "\n".join(b)
180         global_namespace = {
181             "__file__": "generated",
182             "__name__": "__main__",
183         }
184
185         global_namespace.update(vars_dict)
186
187         text_a, text_b = text.split("=", 1)
188         text = "result = " + text_b
189         exec(compile(text, "generated", 'exec'), global_namespace)
190         # print(global_namespace["result"])
191         blocks_py.append((comment, global_namespace["result"]))
192
193     # ---------------------
194     # Now convert into rst.
195     fout = open(OUT_RST, 'w', encoding="utf-8")
196     fw = fout.write
197     fw(HEADER)
198     for comment, b in blocks_py:
199         args_in = None
200         args_out = None
201         for member in b[1:]:
202             if type(member) == tuple:
203                 if args_in is None:
204                     args_in = member
205                 elif args_out is None:
206                     args_out = member
207                     break
208
209         args_in_index = []
210         args_out_index = []
211
212         if args_in is not None:
213             args_in_index[:] = [i for (i, a) in enumerate(args_in) if type(a) == tuple]
214         if args_out is not None:
215             args_out_index[:] = [i for (i, a) in enumerate(args_out) if type(a) == tuple]
216
217         fw(".. function:: %s(bm, %s)\n\n" % (b[0], ", ".join([args_in[i][0] for i in args_in_index])))
218
219         # -- wash the comment
220         comment_washed = []
221         for i, l in enumerate(comment):
222             assert((l.strip() == "") or
223                    (l in {"/*", " *"}) or
224                    (l.startswith(("/* ", " * "))))
225
226             l = l[3:]
227             if i == 0 and not l.strip():
228                 continue
229             if l.strip():
230                 l = "   " + l
231             comment_washed.append(l)
232
233         fw("\n".join(comment_washed))
234         fw("\n")
235         # -- done
236
237         # get the args
238         def get_args_wash(args, args_index, is_ret):
239             args_wash = []
240             for i in args_index:
241                 arg = args[i]
242                 if len(arg) == 3:
243                     name, tp, tp_sub = arg
244                 elif len(arg) == 2:
245                     name, tp = arg
246                     tp_sub = None
247                 else:
248                     print(arg)
249                     assert(0)
250
251                 tp_str = ""
252
253                 comment_prev = ""
254                 comment_next = ""
255                 if i != 0:
256                     comment_prev = args[i + 1]
257                     if type(comment_prev) == str and comment_prev.startswith("our <"):
258                         comment_prev = comment_next[5:-1]  # strip inline <...>
259                     else:
260                         comment_prev = ""
261
262                 if i + 1 < len(args):
263                     comment_next = args[i + 1]
264                     if type(comment_next) == str and comment_next.startswith("inline <"):
265                         comment_next = comment_next[8:-1]  # strip inline <...>
266                     else:
267                         comment_next = ""
268
269                 comment = ""
270                 if comment_prev:
271                     comment += comment_prev.strip()
272                 if comment_next:
273                     comment += ("\n" if comment_prev else "") + comment_next.strip()
274
275                 if tp == BMO_OP_SLOT_FLT:
276                     tp_str = "float"
277                 elif tp == BMO_OP_SLOT_INT:
278                     tp_str = "int"
279                 elif tp == BMO_OP_SLOT_BOOL:
280                     tp_str = "bool"
281                 elif tp == BMO_OP_SLOT_MAT:
282                     tp_str = ":class:`mathutils.Matrix`"
283                 elif tp == BMO_OP_SLOT_VEC:
284                     tp_str = ":class:`mathutils.Vector`"
285                     if not is_ret:
286                         tp_str += " or any sequence of 3 floats"
287                 elif tp == BMO_OP_SLOT_PTR:
288                     tp_str = "dict"
289                     assert(tp_sub is not None)
290                     if tp_sub == BMO_OP_SLOT_SUBTYPE_PTR_BMESH:
291                         tp_str = ":class:`bmesh.types.BMesh`"
292                     elif tp_sub == BMO_OP_SLOT_SUBTYPE_PTR_SCENE:
293                         tp_str = ":class:`bpy.types.Scene`"
294                     elif tp_sub == BMO_OP_SLOT_SUBTYPE_PTR_OBJECT:
295                         tp_str = ":class:`bpy.types.Object`"
296                     elif tp_sub == BMO_OP_SLOT_SUBTYPE_PTR_MESH:
297                         tp_str = ":class:`bpy.types.Mesh`"
298                     else:
299                         print("Cant find", vars_dict_reverse[tp_sub])
300                         assert(0)
301
302                 elif tp == BMO_OP_SLOT_ELEMENT_BUF:
303                     assert(tp_sub is not None)
304
305                     ls = []
306                     if tp_sub & BM_VERT:
307                         ls.append(":class:`bmesh.types.BMVert`")
308                     if tp_sub & BM_EDGE:
309                         ls.append(":class:`bmesh.types.BMEdge`")
310                     if tp_sub & BM_FACE:
311                         ls.append(":class:`bmesh.types.BMFace`")
312                     assert(ls)  # must be at least one
313
314                     if tp_sub & BMO_OP_SLOT_SUBTYPE_ELEM_IS_SINGLE:
315                         tp_str = "/".join(ls)
316                     else:
317                         tp_str = ("list of (%s)" % ", ".join(ls))
318
319                     del ls
320                 elif tp == BMO_OP_SLOT_MAPPING:
321                     if tp_sub & BMO_OP_SLOT_SUBTYPE_MAP_EMPTY:
322                         tp_str = "set of vert/edge/face type"
323                     else:
324                         tp_str = "dict mapping vert/edge/face types to "
325                         if tp_sub == BMO_OP_SLOT_SUBTYPE_MAP_BOOL:
326                             tp_str += "bool"
327                         elif tp_sub == BMO_OP_SLOT_SUBTYPE_MAP_INT:
328                             tp_str += "int"
329                         elif tp_sub == BMO_OP_SLOT_SUBTYPE_MAP_FLT:
330                             tp_str += "float"
331                         elif tp_sub == BMO_OP_SLOT_SUBTYPE_MAP_ELEM:
332                             tp_str += ":class:`bmesh.types.BMVert`/:class:`bmesh.types.BMEdge`/:class:`bmesh.types.BMFace`"
333                         elif tp_sub == BMO_OP_SLOT_SUBTYPE_MAP_INTERNAL:
334                             tp_str += "unknown internal data, not compatible with python"
335                         else:
336                             print("Cant find", vars_dict_reverse[tp_sub])
337                             assert(0)
338                 else:
339                     print("Cant find", vars_dict_reverse[tp])
340                     assert(0)
341
342                 args_wash.append((name, tp_str, comment))
343             return args_wash
344         # end get_args_wash
345
346         # all ops get this arg
347         fw("   :arg bm: The bmesh to operate on.\n")
348         fw("   :type bm: :class:`bmesh.types.BMesh`\n")
349
350         args_in_wash = get_args_wash(args_in, args_in_index, False)
351         args_out_wash = get_args_wash(args_out, args_out_index, True)
352
353         for (name, tp, comment) in args_in_wash:
354             if comment == "":
355                 comment = "Undocumented."
356
357             fw("   :arg %s: %s\n" % (name, comment))
358             fw("   :type %s: %s\n" % (name, tp))
359
360         if args_out_wash:
361             fw("   :return:\n\n")
362
363             for (name, tp, comment) in args_out_wash:
364                 assert(name.endswith(".out"))
365                 name = name[:-4]
366                 fw("      - ``%s``: %s\n\n" % (name, comment))
367                 fw("        **type** %s\n" % tp)
368
369             fw("\n")
370             fw("   :rtype: dict with string keys\n")
371
372         fw("\n\n")
373
374     fout.close()
375     del fout
376     print(OUT_RST)
377
378
379 if __name__ == "__main__":
380     main()