include an example in the api docs for using bmesh operators to make 2 links in a...
[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
117    # namespace hack
118     vars = (
119         "BMO_OP_SLOT_ELEMENT_BUF",
120         "BMO_OP_SLOT_BOOL",
121         "BMO_OP_SLOT_FLT",
122         "BMO_OP_SLOT_INT",
123         "BMO_OP_SLOT_MAT",
124         "BMO_OP_SLOT_VEC",
125         "BMO_OP_SLOT_PTR",
126         "BMO_OP_SLOT_MAPPING",
127         
128         "BMO_OP_SLOT_SUBTYPE_MAP_ELEM",
129         "BMO_OP_SLOT_SUBTYPE_MAP_BOOL",
130         "BMO_OP_SLOT_SUBTYPE_MAP_INT",
131         "BMO_OP_SLOT_SUBTYPE_MAP_FLT",
132         "BMO_OP_SLOT_SUBTYPE_MAP_EMPTY",
133         "BMO_OP_SLOT_SUBTYPE_MAP_INTERNAL",
134
135         "BMO_OP_SLOT_SUBTYPE_PTR_SCENE",
136         "BMO_OP_SLOT_SUBTYPE_PTR_OBJECT",
137         "BMO_OP_SLOT_SUBTYPE_PTR_MESH",
138         "BMO_OP_SLOT_SUBTYPE_PTR_BMESH",
139
140         "BMO_OP_SLOT_SUBTYPE_ELEM_IS_SINGLE",
141
142         "BM_VERT",
143         "BM_EDGE",
144         "BM_FACE",
145
146         "BMO_OP_FLAG_UNTAN_MULTIRES",
147     )
148     vars_dict = {}
149     for i, v in enumerate(vars):
150         vars_dict[v] = (1 << i)
151     globals().update(vars_dict)
152     # reverse lookup
153     vars_dict_reverse = {v: k for k, v in vars_dict.items()}
154     # end namespace hack
155
156     blocks_py = []
157     for comment, b in blocks:
158         # magic, translate into python
159         b[0] = b[0].replace("static BMOpDefine ", "")
160         
161         for i, l in enumerate(b):
162             l = l.strip()
163             l = l.replace("{", "(")
164             l = l.replace("}", ")")
165             
166             if l.startswith("/*"):
167                 l = l.replace("/*", "'''own <")
168             else:
169                 l = l.replace("/*", "'''inline <")
170             l = l.replace("*/", ">''',")
171             
172             # exec func. eg: bmo_rotate_edges_exec,
173             if l.startswith("bmo_") and l.endswith("_exec,"):
174                 l = "None,"
175             b[i] = l
176             
177         #for l in b:
178         #    print(l)
179
180         text = "\n".join(b)
181         global_namespace = {
182             "__file__": "generated",
183             "__name__": "__main__",
184         }
185         
186         global_namespace.update(vars_dict)
187
188         text_a, text_b = text.split("=", 1)
189         text = "result = " + text_b
190         exec(compile(text, "generated", 'exec'), global_namespace)
191         # print(global_namespace["result"])
192         blocks_py.append((comment, global_namespace["result"]))
193
194
195     # ---------------------
196     # Now convert into rst.
197     fout = open(OUT_RST, 'w', encoding="utf-8")
198     fw = fout.write
199     fw(HEADER)
200     for comment, b in blocks_py:
201         args_in = None
202         args_out = None
203         for member in b[1:]:
204             if type(member) == tuple:
205                 if args_in is None:
206                     args_in = member
207                 elif args_out is None:
208                     args_out = member
209                     break
210
211         args_in_index = []
212         args_out_index = []
213
214         if args_in is not None:
215             args_in_index[:] = [i for (i, a) in enumerate(args_in) if type(a) == tuple]
216         if args_out is not None:
217             args_out_index[:] = [i for (i, a) in enumerate(args_out) if type(a) == tuple]
218
219         fw(".. function:: %s(bm, %s)\n\n" % (b[0], ", ".join([args_in[i][0] for i in args_in_index])))
220         
221         # -- wash the comment
222         comment_washed = []
223         for i, l in enumerate(comment):
224             assert((l.strip() == "") or
225                    (l in {"/*", " *"}) or
226                    (l.startswith(("/* ", " * "))))
227
228             l = l[3:]
229             if i == 0 and not l.strip():
230                 continue
231             if l.strip():
232                 l = "   " + l
233             comment_washed.append(l)
234
235         fw("\n".join(comment_washed))
236         fw("\n")
237         # -- done
238
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: ls.append(":class:`bmesh.types.BMVert`")
310                     if tp_sub & BM_EDGE: ls.append(":class:`bmesh.types.BMEdge`")
311                     if tp_sub & BM_FACE: ls.append(":class:`bmesh.types.BMFace`")
312                     assert(ls)  # must be at least one
313
314                     if tp_sub & BMO_OP_SLOT_SUBTYPE_ELEM_IS_SINGLE:
315                         tp_str = "/".join(ls)
316                     else:
317                         tp_str = ("list of (%s)" % ", ".join(ls))
318                         
319                     del ls
320                 elif tp == BMO_OP_SLOT_MAPPING:
321                     if tp_sub & BMO_OP_SLOT_SUBTYPE_MAP_EMPTY:
322                         tp_str = "set of vert/edge/face type"
323                     else:
324                         tp_str = "dict mapping vert/edge/face types to "
325                         if tp_sub == BMO_OP_SLOT_SUBTYPE_MAP_BOOL:
326                             tp_str += "bool"
327                         elif tp_sub == BMO_OP_SLOT_SUBTYPE_MAP_INT:
328                             tp_str += "int"
329                         elif tp_sub == BMO_OP_SLOT_SUBTYPE_MAP_FLT:
330                             tp_str += "float"
331                         elif tp_sub == BMO_OP_SLOT_SUBTYPE_MAP_ELEM:
332                             tp_str += ":class:`bmesh.types.BMVert`/:class:`bmesh.types.BMEdge`/:class:`bmesh.types.BMFace`"
333                         elif tp_sub == BMO_OP_SLOT_SUBTYPE_MAP_INTERNAL:
334                             tp_str += "unknown internal data, not compatible with python"
335                         else:
336                             print("Cant find", vars_dict_reverse[tp_sub])
337                             assert(0)
338                 else:
339                     print("Cant find", vars_dict_reverse[tp])
340                     assert(0)
341
342                 args_wash.append((name, tp_str, comment))
343             return args_wash
344         # end get_args_wash
345
346         # all ops get this arg
347         fw("   :arg bm: The bmesh to operate on.\n")
348         fw("   :type bm: :class:`bmesh.types.BMesh`\n")
349
350         args_in_wash = get_args_wash(args_in, args_in_index, False)
351         args_out_wash = get_args_wash(args_out, args_out_index, True)
352
353         for (name, tp, comment) in args_in_wash:
354             if comment == "":
355                 comment = "Undocumented."
356
357             fw("   :arg %s: %s\n" % (name, comment))
358             fw("   :type %s: %s\n" % (name, tp))
359         
360         if args_out_wash:
361             fw("   :return:\n\n")
362             
363             for (name, tp, comment) in args_out_wash:
364                 assert(name.endswith(".out"))
365                 name = name[:-4]
366                 fw("      - ``%s``: %s\n\n" % (name, comment))
367                 fw("        **type** %s\n" % tp)
368             
369             fw("\n")
370             fw("   :rtype: dict with string keys\n")
371
372         fw("\n\n")
373         
374     fout.close()
375     del fout
376     print(OUT_RST)
377
378
379 if __name__ == "__main__":
380     main()