ebcedbf71f15da206d114cfd8f4b786f0927a897
[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 "bmo_opdefines[]" not 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_OPTYPE_FLAG_NORMALS_CALC",
146         "BMO_OPTYPE_FLAG_UNTAN_MULTIRES",
147         "BMO_OPTYPE_FLAG_SELECT_FLUSH",
148         "BMO_OPTYPE_FLAG_SELECT_VALIDATE",
149         "BMO_OPTYPE_FLAG_NOP",
150     )
151     vars_dict = {}
152     for i, v in enumerate(vars):
153         vars_dict[v] = (1 << i)
154     globals().update(vars_dict)
155     # reverse lookup
156     vars_dict_reverse = {v: k for k, v in vars_dict.items()}
157     # end namespace hack
158
159     blocks_py = []
160     for comment, b in blocks:
161         # magic, translate into python
162         b[0] = b[0].replace("static BMOpDefine ", "")
163
164         for i, l in enumerate(b):
165             l = l.strip()
166             # casts
167             l = l.replace("(int)", "")
168
169             l = l.replace("{", "(")
170             l = l.replace("}", ")")
171
172             if l.startswith("/*"):
173                 l = l.replace("/*", "'''own <")
174             else:
175                 l = l.replace("/*", "'''inline <")
176             l = l.replace("*/", ">''',")
177
178             # exec func. eg: bmo_rotate_edges_exec,
179             if l.startswith("bmo_") and l.endswith("_exec,"):
180                 l = "None,"
181             b[i] = l
182
183         # for l in b:
184         #     print(l)
185
186         text = "\n".join(b)
187         global_namespace = {
188             "__file__": "generated",
189             "__name__": "__main__",
190         }
191
192         global_namespace.update(vars_dict)
193
194         text_a, text_b = text.split("=", 1)
195         text = "result = " + text_b
196         exec(compile(text, "generated", 'exec'), global_namespace)
197         # print(global_namespace["result"])
198         blocks_py.append((comment, global_namespace["result"]))
199
200     # ---------------------
201     # Now convert into rst.
202     fout = open(OUT_RST, 'w', encoding="utf-8")
203     fw = fout.write
204     fw(HEADER)
205     for comment, b in blocks_py:
206         args_in = None
207         args_out = None
208         for member in b[1:]:
209             if type(member) == tuple:
210                 if args_in is None:
211                     args_in = member
212                 elif args_out is None:
213                     args_out = member
214                     break
215
216         args_in_index = []
217         args_out_index = []
218
219         if args_in is not None:
220             args_in_index[:] = [i for (i, a) in enumerate(args_in) if type(a) == tuple]
221         if args_out is not None:
222             args_out_index[:] = [i for (i, a) in enumerate(args_out) if type(a) == tuple]
223
224         fw(".. function:: %s(bm, %s)\n\n" % (b[0], ", ".join([args_in[i][0] for i in args_in_index])))
225
226         # -- wash the comment
227         comment_washed = []
228         for i, l in enumerate(comment):
229             assert((l.strip() == "") or
230                    (l in {"/*", " *"}) or
231                    (l.startswith(("/* ", " * "))))
232
233             l = l[3:]
234             if i == 0 and not l.strip():
235                 continue
236             if l.strip():
237                 l = "   " + l
238             comment_washed.append(l)
239
240         fw("\n".join(comment_washed))
241         fw("\n")
242         # -- done
243
244         # get the args
245         def get_args_wash(args, args_index, is_ret):
246             args_wash = []
247             for i in args_index:
248                 arg = args[i]
249                 if len(arg) == 3:
250                     name, tp, tp_sub = arg
251                 elif len(arg) == 2:
252                     name, tp = arg
253                     tp_sub = None
254                 else:
255                     print(arg)
256                     assert(0)
257
258                 tp_str = ""
259
260                 comment_prev = ""
261                 comment_next = ""
262                 if i != 0:
263                     comment_prev = args[i + 1]
264                     if type(comment_prev) == str and comment_prev.startswith("our <"):
265                         comment_prev = comment_next[5:-1]  # strip inline <...>
266                     else:
267                         comment_prev = ""
268
269                 if i + 1 < len(args):
270                     comment_next = args[i + 1]
271                     if type(comment_next) == str and comment_next.startswith("inline <"):
272                         comment_next = comment_next[8:-1]  # strip inline <...>
273                     else:
274                         comment_next = ""
275
276                 comment = ""
277                 if comment_prev:
278                     comment += comment_prev.strip()
279                 if comment_next:
280                     comment += ("\n" if comment_prev else "") + comment_next.strip()
281
282                 if tp == BMO_OP_SLOT_FLT:
283                     tp_str = "float"
284                 elif tp == BMO_OP_SLOT_INT:
285                     tp_str = "int"
286                 elif tp == BMO_OP_SLOT_BOOL:
287                     tp_str = "bool"
288                 elif tp == BMO_OP_SLOT_MAT:
289                     tp_str = ":class:`mathutils.Matrix`"
290                 elif tp == BMO_OP_SLOT_VEC:
291                     tp_str = ":class:`mathutils.Vector`"
292                     if not is_ret:
293                         tp_str += " or any sequence of 3 floats"
294                 elif tp == BMO_OP_SLOT_PTR:
295                     tp_str = "dict"
296                     assert(tp_sub is not None)
297                     if tp_sub == BMO_OP_SLOT_SUBTYPE_PTR_BMESH:
298                         tp_str = ":class:`bmesh.types.BMesh`"
299                     elif tp_sub == BMO_OP_SLOT_SUBTYPE_PTR_SCENE:
300                         tp_str = ":class:`bpy.types.Scene`"
301                     elif tp_sub == BMO_OP_SLOT_SUBTYPE_PTR_OBJECT:
302                         tp_str = ":class:`bpy.types.Object`"
303                     elif tp_sub == BMO_OP_SLOT_SUBTYPE_PTR_MESH:
304                         tp_str = ":class:`bpy.types.Mesh`"
305                     else:
306                         print("Cant find", vars_dict_reverse[tp_sub])
307                         assert(0)
308
309                 elif tp == BMO_OP_SLOT_ELEMENT_BUF:
310                     assert(tp_sub is not None)
311
312                     ls = []
313                     if tp_sub & BM_VERT:
314                         ls.append(":class:`bmesh.types.BMVert`")
315                     if tp_sub & BM_EDGE:
316                         ls.append(":class:`bmesh.types.BMEdge`")
317                     if tp_sub & BM_FACE:
318                         ls.append(":class:`bmesh.types.BMFace`")
319                     assert(ls)  # must be at least one
320
321                     if tp_sub & BMO_OP_SLOT_SUBTYPE_ELEM_IS_SINGLE:
322                         tp_str = "/".join(ls)
323                     else:
324                         tp_str = ("list of (%s)" % ", ".join(ls))
325
326                     del ls
327                 elif tp == BMO_OP_SLOT_MAPPING:
328                     if tp_sub & BMO_OP_SLOT_SUBTYPE_MAP_EMPTY:
329                         tp_str = "set of vert/edge/face type"
330                     else:
331                         tp_str = "dict mapping vert/edge/face types to "
332                         if tp_sub == BMO_OP_SLOT_SUBTYPE_MAP_BOOL:
333                             tp_str += "bool"
334                         elif tp_sub == BMO_OP_SLOT_SUBTYPE_MAP_INT:
335                             tp_str += "int"
336                         elif tp_sub == BMO_OP_SLOT_SUBTYPE_MAP_FLT:
337                             tp_str += "float"
338                         elif tp_sub == BMO_OP_SLOT_SUBTYPE_MAP_ELEM:
339                             tp_str += ":class:`bmesh.types.BMVert`/:class:`bmesh.types.BMEdge`/:class:`bmesh.types.BMFace`"
340                         elif tp_sub == BMO_OP_SLOT_SUBTYPE_MAP_INTERNAL:
341                             tp_str += "unknown internal data, not compatible with python"
342                         else:
343                             print("Cant find", vars_dict_reverse[tp_sub])
344                             assert(0)
345                 else:
346                     print("Cant find", vars_dict_reverse[tp])
347                     assert(0)
348
349                 args_wash.append((name, tp_str, comment))
350             return args_wash
351         # end get_args_wash
352
353         # all ops get this arg
354         fw("   :arg bm: The bmesh to operate on.\n")
355         fw("   :type bm: :class:`bmesh.types.BMesh`\n")
356
357         args_in_wash = get_args_wash(args_in, args_in_index, False)
358         args_out_wash = get_args_wash(args_out, args_out_index, True)
359
360         for (name, tp, comment) in args_in_wash:
361             if comment == "":
362                 comment = "Undocumented."
363
364             fw("   :arg %s: %s\n" % (name, comment))
365             fw("   :type %s: %s\n" % (name, tp))
366
367         if args_out_wash:
368             fw("   :return:\n\n")
369
370             for (name, tp, comment) in args_out_wash:
371                 assert(name.endswith(".out"))
372                 name = name[:-4]
373                 fw("      - ``%s``: %s\n\n" % (name, comment))
374                 fw("        **type** %s\n" % tp)
375
376             fw("\n")
377             fw("   :rtype: dict with string keys\n")
378
379         fw("\n\n")
380
381     fout.close()
382     del fout
383     print(OUT_RST)
384
385
386 if __name__ == "__main__":
387     main()