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