3077798cbef89da9b73d95fc051624869e0964dc
[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 """
57
58
59 def main():
60     fsrc = open(FILE_OP_DEFINES_C, 'r', encoding="utf-8")
61
62     blocks = []
63     
64     is_block = False
65     is_comment = False  # /* global comments only */
66     
67     comment_ctx = None
68     block_ctx = None
69
70     for l in fsrc:
71         l = l[:-1]
72         # weak but ok
73         if ("BMOpDefine" in l and l.split()[1] == "BMOpDefine") and not "bmo_opdefines[]" in l:
74             is_block = True
75             block_ctx = []
76             blocks.append((comment_ctx, block_ctx))
77         elif l.strip().startswith("/*"):
78             is_comment = True
79             comment_ctx = []
80         
81         if is_block:
82             if l.strip().startswith("//"):
83                 pass
84             else:
85                 # remove c++ comment if we have one
86                 cpp_comment = l.find("//")
87                 if cpp_comment != -1:
88                     l = l[:cpp_comment]
89
90                 block_ctx.append(l)
91             
92             if l.strip() == "};":
93                 is_block = False
94                 comment_ctx = None
95         
96         if is_comment:
97             c_comment_start = l.find("/*")
98             if c_comment_start != -1:
99                 l = l[c_comment_start + 2:]
100
101             c_comment_end = l.find("*/")
102             if c_comment_end != -1:
103                 l = l[:c_comment_end]
104
105                 is_comment = False
106             comment_ctx.append(l)
107
108     fsrc.close()
109     del fsrc
110
111
112    # namespace hack
113     vars = (
114         "BMO_OP_SLOT_ELEMENT_BUF",
115         "BMO_OP_SLOT_BOOL",
116         "BMO_OP_SLOT_FLT",
117         "BMO_OP_SLOT_INT",
118         "BMO_OP_SLOT_MAT",
119         "BMO_OP_SLOT_VEC",
120         "BMO_OP_SLOT_PTR",
121         "BMO_OP_SLOT_MAPPING",
122         
123         "BMO_OP_SLOT_SUBTYPE_MAP_ELEM",
124         "BMO_OP_SLOT_SUBTYPE_MAP_BOOL",
125         "BMO_OP_SLOT_SUBTYPE_MAP_INT",
126         "BMO_OP_SLOT_SUBTYPE_MAP_FLT",
127         "BMO_OP_SLOT_SUBTYPE_MAP_EMPTY",
128         "BMO_OP_SLOT_SUBTYPE_MAP_INTERNAL",
129
130         "BMO_OP_SLOT_SUBTYPE_PTR_SCENE",
131         "BMO_OP_SLOT_SUBTYPE_PTR_OBJECT",
132         "BMO_OP_SLOT_SUBTYPE_PTR_MESH",
133         "BMO_OP_SLOT_SUBTYPE_PTR_BMESH",
134
135         "BMO_OP_SLOT_SUBTYPE_ELEM_IS_SINGLE",
136
137         "BM_VERT",
138         "BM_EDGE",
139         "BM_FACE",
140
141         "BMO_OP_FLAG_UNTAN_MULTIRES",
142     )
143     vars_dict = {}
144     for i, v in enumerate(vars):
145         vars_dict[v] = (1 << i)
146     globals().update(vars_dict)
147     # reverse lookup
148     vars_dict_reverse = {v: k for k, v in vars_dict.items()}
149     # end namespace hack
150
151     blocks_py = []
152     for comment, b in blocks:
153         # magic, translate into python
154         b[0] = b[0].replace("static BMOpDefine ", "")
155         
156         for i, l in enumerate(b):
157             l = l.strip()
158             l = l.replace("{", "(")
159             l = l.replace("}", ")")
160             
161             if l.startswith("/*"):
162                 l = l.replace("/*", "'''own <")
163             else:
164                 l = l.replace("/*", "'''inline <")
165             l = l.replace("*/", ">''',")
166             
167             # exec func. eg: bmo_rotate_edges_exec,
168             if l.startswith("bmo_") and l.endswith("_exec,"):
169                 l = "None,"
170             b[i] = l
171             
172         #for l in b:
173         #    print(l)
174
175         text = "\n".join(b)
176         global_namespace = {
177             "__file__": "generated",
178             "__name__": "__main__",
179         }
180         
181         global_namespace.update(vars_dict)
182
183         text_a, text_b = text.split("=", 1)
184         text = "result = " + text_b
185         exec(compile(text, "generated", 'exec'), global_namespace)
186         # print(global_namespace["result"])
187         blocks_py.append((comment, global_namespace["result"]))
188
189
190     # ---------------------
191     # Now convert into rst.
192     fout = open(OUT_RST, 'w', encoding="utf-8")
193     fw = fout.write
194     fw(HEADER)
195     for comment, b in blocks_py:
196         args_in = None
197         args_out = None
198         for member in b[1:]:
199             if type(member) == tuple:
200                 if args_in is None:
201                     args_in = member
202                 elif args_out is None:
203                     args_out = member
204                     break
205
206         args_in_index = []
207         args_out_index = []
208
209         if args_in is not None:
210             args_in_index[:] = [i for (i, a) in enumerate(args_in) if type(a) == tuple]
211         if args_out is not None:
212             args_out_index[:] = [i for (i, a) in enumerate(args_out) if type(a) == tuple]
213
214         fw(".. function:: %s(bm, %s)\n\n" % (b[0], ", ".join([args_in[i][0] for i in args_in_index])))
215         
216         # -- wash the comment
217         comment_washed = []
218         for i, l in enumerate(comment):
219             assert((l.strip() == "") or
220                    (l in {"/*", " *"}) or
221                    (l.startswith(("/* ", " * "))))
222
223             l = l[3:]
224             if i == 0 and not l.strip():
225                 continue
226             if l.strip():
227                 l = "   " + l
228             comment_washed.append(l)
229
230         fw("\n".join(comment_washed))
231         fw("\n")
232         # -- done
233
234
235         # get the args
236         def get_args_wash(args, args_index, is_ret):
237             args_wash = []
238             for i in args_index:
239                 arg = args[i]
240                 if len(arg) == 3:
241                     name, tp, tp_sub = arg
242                 elif len(arg) == 2:
243                     name, tp = arg
244                     tp_sub = None
245                 else:
246                     print(arg)
247                     assert(0)
248
249                 tp_str = ""
250
251                 comment_prev = ""
252                 comment_next = ""
253                 if i != 0:
254                     comment_prev = args[i + 1]
255                     if type(comment_prev) == str and comment_prev.startswith("our <"):
256                         comment_prev = comment_next[5:-1]  # strip inline <...>
257                     else:
258                         comment_prev = ""
259
260                 if i + 1 < len(args):
261                     comment_next = args[i + 1]
262                     if type(comment_next) == str and comment_next.startswith("inline <"):
263                         comment_next = comment_next[8:-1]  # strip inline <...>
264                     else:
265                         comment_next = ""
266                         
267                 comment = ""
268                 if comment_prev:
269                     comment += comment_prev.strip()
270                 if comment_next:
271                     comment += ("\n" if comment_prev else "") + comment_next.strip()
272
273                 if tp == BMO_OP_SLOT_FLT:
274                     tp_str = "float"
275                 elif tp == BMO_OP_SLOT_INT:
276                     tp_str = "int"
277                 elif tp == BMO_OP_SLOT_BOOL:
278                     tp_str = "bool"
279                 elif tp == BMO_OP_SLOT_MAT:
280                     tp_str = ":class:`mathutils.Matrix`"
281                 elif tp == BMO_OP_SLOT_VEC:
282                     tp_str = ":class:`mathutils.Vector`"
283                     if not is_ret:
284                         tp_str += " or any sequence of 3 floats"
285                 elif tp == BMO_OP_SLOT_PTR:
286                     tp_str = "dict"
287                     assert(tp_sub is not None)
288                     if tp_sub == BMO_OP_SLOT_SUBTYPE_PTR_BMESH:
289                         tp_str = ":class:`bmesh.types.BMesh`"
290                     elif tp_sub == BMO_OP_SLOT_SUBTYPE_PTR_SCENE:
291                         tp_str = ":class:`bpy.types.Scene`"
292                     elif tp_sub == BMO_OP_SLOT_SUBTYPE_PTR_OBJECT:
293                         tp_str = ":class:`bpy.types.Object`"
294                     elif tp_sub == BMO_OP_SLOT_SUBTYPE_PTR_MESH:
295                         tp_str = ":class:`bpy.types.Mesh`"
296                     else:
297                         print("Cant find", vars_dict_reverse[tp_sub])
298                         assert(0)
299
300                 elif tp == BMO_OP_SLOT_ELEMENT_BUF:
301                     assert(tp_sub is not None)
302                     
303                     ls = []
304                     if tp_sub & BM_VERT: ls.append(":class:`bmesh.types.BMVert`")
305                     if tp_sub & BM_EDGE: ls.append(":class:`bmesh.types.BMEdge`")
306                     if tp_sub & BM_FACE: ls.append(":class:`bmesh.types.BMFace`")
307                     assert(ls)  # must be at least one
308
309                     if tp_sub & BMO_OP_SLOT_SUBTYPE_ELEM_IS_SINGLE:
310                         tp_str = "/".join(ls)
311                     else:
312                         tp_str = ("list of (%s)" % ", ".join(ls))
313                         
314                     del ls
315                 elif tp == BMO_OP_SLOT_MAPPING:
316                     if tp_sub & BMO_OP_SLOT_SUBTYPE_MAP_EMPTY:
317                         tp_str = "set of vert/edge/face type"
318                     else:
319                         tp_str = "dict mapping vert/edge/face types to "
320                         if tp_sub == BMO_OP_SLOT_SUBTYPE_MAP_BOOL:
321                             tp_str += "bool"
322                         elif tp_sub == BMO_OP_SLOT_SUBTYPE_MAP_INT:
323                             tp_str += "int"
324                         elif tp_sub == BMO_OP_SLOT_SUBTYPE_MAP_FLT:
325                             tp_str += "float"
326                         elif tp_sub == BMO_OP_SLOT_SUBTYPE_MAP_ELEM:
327                             tp_str += ":class:`bmesh.types.BMVert`/:class:`bmesh.types.BMEdge`/:class:`bmesh.types.BMFace`"
328                         elif tp_sub == BMO_OP_SLOT_SUBTYPE_MAP_INTERNAL:
329                             tp_str += "unknown internal data, not compatible with python"
330                         else:
331                             print("Cant find", vars_dict_reverse[tp_sub])
332                             assert(0)
333                 else:
334                     print("Cant find", vars_dict_reverse[tp])
335                     assert(0)
336
337                 args_wash.append((name, tp_str, comment))
338             return args_wash
339         # end get_args_wash
340
341         # all ops get this arg
342         fw("   :arg bm: The bmesh to operate on.\n")
343         fw("   :type bm: :class:`bmesh.types.BMesh`\n")
344
345         args_in_wash = get_args_wash(args_in, args_in_index, False)
346         args_out_wash = get_args_wash(args_out, args_out_index, True)
347
348         for (name, tp, comment) in args_in_wash:
349             if comment == "":
350                 comment = "Undocumented."
351
352             fw("   :arg %s: %s\n" % (name, comment))
353             fw("   :type %s: %s\n" % (name, tp))
354         
355         if args_out_wash:
356             fw("   :return:\n\n")
357             
358             for (name, tp, comment) in args_out_wash:
359                 assert(name.endswith(".out"))
360                 name = name[:-4]
361                 fw("      - ``%s``: %s\n\n" % (name, comment))
362                 fw("        **type** %s\n" % tp)
363             
364             fw("\n")
365             fw("   :rtype: dict with string keys\n")
366
367         fw("\n\n")
368         
369     fout.close()
370     del fout
371     print(OUT_RST)
372
373
374 if __name__ == "__main__":
375     main()