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