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