generate api reference for 'bmesh.ops', restructured text is extracted from bmesh_opd...
authorCampbell Barton <ideasman42@gmail.com>
Tue, 27 Nov 2012 06:56:51 +0000 (06:56 +0000)
committerCampbell Barton <ideasman42@gmail.com>
Tue, 27 Nov 2012 06:56:51 +0000 (06:56 +0000)
see:
http://www.blender.org/documentation/blender_python_api_2_64_9/bmesh.ops.html

doc/python_api/rst_from_bmesh_opdefines.py [new file with mode: 0644]
doc/python_api/sphinx_doc_gen.py

diff --git a/doc/python_api/rst_from_bmesh_opdefines.py b/doc/python_api/rst_from_bmesh_opdefines.py
new file mode 100644 (file)
index 0000000..5803315
--- /dev/null
@@ -0,0 +1,370 @@
+# ##### BEGIN GPL LICENSE BLOCK #####
+#
+#  This program is free software; you can redistribute it and/or
+#  modify it under the terms of the GNU General Public License
+#  as published by the Free Software Foundation; either version 2
+#  of the License, or (at your option) any later version.
+#
+#  This program is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU General Public License for more details.
+#
+#  You should have received a copy of the GNU General Public License
+#  along with this program; if not, write to the Free Software Foundation,
+#  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# ##### END GPL LICENSE BLOCK #####
+
+# <pep8 compliant>
+
+# This is a quite stupid script which extracts bmesh api docs from
+# 'bmesh_opdefines.c' in order to avoid having to add a lot of introspection
+# data access into the api.
+#
+# The script is stupid becase it makes assumptions about formatting...
+# that each arg has its own line, that comments above or directly after will be __doc__ etc...
+#
+# We may want to replace this script with something else one day but for now its good enough.
+# if it needs large updates it may be better to rewrite using a real parser or
+# add introspection into bmesh.ops.
+# - campbell
+
+import os
+
+CURRENT_DIR = os.path.abspath(os.path.dirname(__file__))
+SOURCE_DIR = os.path.normpath(os.path.abspath(os.path.normpath(os.path.join(CURRENT_DIR, "..", ".."))))
+FILE_OP_DEFINES_C = os.path.join(SOURCE_DIR, "source", "blender", "bmesh", "intern", "bmesh_opdefines.c")
+OUT_RST = os.path.join(CURRENT_DIR, "rst", "bmesh.ops.rst")
+
+HEADER = r"""
+BMesh Operators
+===============
+
+.. module:: bmesh.ops
+
+This module gives access to low level bmesh operations.
+
+Most operators take input and return output, they can be chained together
+to perform useful operations.
+
+.. note::
+
+   This API us new in 2.65 and not yet well tested.
+
+
+"""
+
+
+def main():
+    fsrc = open(FILE_OP_DEFINES_C, 'r', encoding="utf-8")
+
+    blocks = []
+    
+    is_block = False
+    is_comment = False  # /* global comments only */
+    
+    comment_ctx = None
+    block_ctx = None
+
+    for l in fsrc:
+        l = l[:-1]
+        # weak but ok
+        if ("BMOpDefine" in l and l.split()[1] == "BMOpDefine") and not "bmo_opdefines[]" in l:
+            is_block = True
+            block_ctx = []
+            blocks.append((comment_ctx, block_ctx))
+        elif l.strip().startswith("/*"):
+            is_comment = True
+            comment_ctx = []
+        
+        if is_block:
+            if l.strip().startswith("//"):
+                pass
+            else:
+                # remove c++ comment if we have one
+                cpp_comment = l.find("//")
+                if cpp_comment != -1:
+                    l = l[:cpp_comment]
+
+                block_ctx.append(l)
+            
+            if l.strip() == "};":
+                is_block = False
+                comment_ctx = None
+        
+        if is_comment:
+            c_comment_start = l.find("/*")
+            if c_comment_start != -1:
+                l = l[c_comment_start + 2:]
+
+            c_comment_end = l.find("*/")
+            if c_comment_end != -1:
+                l = l[:c_comment_end]
+
+                is_comment = False
+            comment_ctx.append(l)
+
+    fsrc.close()
+    del fsrc
+
+
+   # namespace hack
+    vars = (
+        "BMO_OP_SLOT_ELEMENT_BUF",
+        "BMO_OP_SLOT_BOOL",
+        "BMO_OP_SLOT_FLT",
+        "BMO_OP_SLOT_INT",
+        "BMO_OP_SLOT_MAT",
+        "BMO_OP_SLOT_VEC",
+        "BMO_OP_SLOT_PTR",
+        "BMO_OP_SLOT_MAPPING",
+        
+        "BMO_OP_SLOT_SUBTYPE_MAP_ELEM",
+        "BMO_OP_SLOT_SUBTYPE_MAP_BOOL",
+        "BMO_OP_SLOT_SUBTYPE_MAP_INT",
+        "BMO_OP_SLOT_SUBTYPE_MAP_FLOAT",
+        "BMO_OP_SLOT_SUBTYPE_MAP_EMPTY",
+        "BMO_OP_SLOT_SUBTYPE_MAP_INTERNAL",
+
+        "BMO_OP_SLOT_SUBTYPE_PTR_SCENE",
+        "BMO_OP_SLOT_SUBTYPE_PTR_OBJECT",
+        "BMO_OP_SLOT_SUBTYPE_PTR_MESH",
+        "BMO_OP_SLOT_SUBTYPE_PTR_BMESH",
+
+        "BMO_OP_SLOT_SUBTYPE_ELEM_IS_SINGLE",
+
+        "BM_VERT",
+        "BM_EDGE",
+        "BM_FACE",
+
+        "BMO_OP_FLAG_UNTAN_MULTIRES",
+    )
+    vars_dict = {}
+    for i, v in enumerate(vars):
+        vars_dict[v] = (1 << i)
+    globals().update(vars_dict)
+    # reverse lookup
+    vars_dict_reverse = {v: k for k, v in vars_dict.items()}
+    # end namespace hack
+
+    blocks_py = []
+    for comment, b in blocks:
+        # magic, translate into python
+        b[0] = b[0].replace("static BMOpDefine ", "")
+        
+        for i, l in enumerate(b):
+            l = l.strip()
+            l = l.replace("{", "(")
+            l = l.replace("}", ")")
+            
+            if l.startswith("/*"):
+                l = l.replace("/*", "'''own <")
+            else:
+                l = l.replace("/*", "'''inline <")
+            l = l.replace("*/", ">''',")
+            
+            # exec func. eg: bmo_rotate_edges_exec,
+            if l.startswith("bmo_") and l.endswith("_exec,"):
+                l = "None,"
+            b[i] = l
+            
+        #for l in b:
+        #    print(l)
+
+        text = "\n".join(b)
+        global_namespace = {
+            "__file__": "generated",
+            "__name__": "__main__",
+        }
+        
+        global_namespace.update(vars_dict)
+
+        text_a, text_b = text.split("=", 1)
+        text = "result = " + text_b
+        exec(compile(text, "generated", 'exec'), global_namespace)
+        # print(global_namespace["result"])
+        blocks_py.append((comment, global_namespace["result"]))
+
+
+    # ---------------------
+    # Now convert into rst.
+    fout = open(OUT_RST, 'w', encoding="utf-8")
+    fw = fout.write
+    fw(HEADER)
+    for comment, b in blocks_py:
+        args_in = None
+        args_out = None
+        for member in b[1:]:
+            if type(member) == tuple:
+                if args_in is None:
+                    args_in = member
+                elif args_out is None:
+                    args_out = member
+                    break
+
+        args_in_index = []
+        args_out_index = []
+
+        if args_in is not None:
+            args_in_index[:] = [i for (i, a) in enumerate(args_in) if type(a) == tuple]
+        if args_out is not None:
+            args_out_index[:] = [i for (i, a) in enumerate(args_out) if type(a) == tuple]
+
+        fw(".. function:: %s(%s)\n\n" % (b[0], ", ".join([args_in[i][0] for i in args_in_index])))
+        
+        # -- wash the comment
+        comment_washed = []
+        for i, l in enumerate(comment):
+            assert((l.strip() == "") or
+                   (l in {"/*", " *"}) or
+                   (l.startswith(("/* ", " * "))))
+
+            l = l[3:]
+            if i == 0 and not l.strip():
+                continue
+            if l.strip():
+                l = "   " + l
+            comment_washed.append(l)
+
+        fw("\n".join(comment_washed))
+        fw("\n")
+        # -- done
+
+
+        # get the args
+        def get_args_wash(args, args_index):
+            args_wash = []
+            for i in args_index:
+                arg = args[i]
+                if len(arg) == 3:
+                    name, tp, tp_sub = arg
+                elif len(arg) == 2:
+                    name, tp = arg
+                    tp_sub = None
+                else:
+                    print(arg)
+                    assert(0)
+
+                tp_str = ""
+
+                comment_prev = ""
+                comment_next = ""
+                if i != 0:
+                    comment_prev = args[i + 1]
+                    if type(comment_prev) == str and comment_prev.startswith("our <"):
+                        comment_prev = comment_next[5:-1]  # strip inline <...>
+                    else:
+                        comment_prev = ""
+
+                if i + 1 < len(args):
+                    comment_next = args[i + 1]
+                    if type(comment_next) == str and comment_next.startswith("inline <"):
+                        comment_next = comment_next[8:-1]  # strip inline <...>
+                    else:
+                        comment_next = ""
+                        
+                comment = ""
+                if comment_prev:
+                    comment += comment_prev.strip()
+                if comment_next:
+                    comment += ("\n" if comment_prev else "") + comment_next.strip()
+
+                if tp == BMO_OP_SLOT_FLT:
+                    tp_str = "float"
+                elif tp == BMO_OP_SLOT_INT:
+                    tp_str = "int"
+                elif tp == BMO_OP_SLOT_BOOL:
+                    tp_str = "bool"
+                elif tp == BMO_OP_SLOT_MAT:
+                    tp_str = "matrix"
+                elif tp == BMO_OP_SLOT_VEC:
+                    tp_str = "matrix"
+                elif tp == BMO_OP_SLOT_PTR:
+                    tp_str = "dict"
+                    assert(tp_sub is not None)
+                    if tp_sub == BMO_OP_SLOT_SUBTYPE_PTR_BMESH:
+                        tp_str = "BMesh"
+                    elif tp_sub == BMO_OP_SLOT_SUBTYPE_PTR_SCENE:
+                        tp_str = "Scene"
+                    elif tp_sub == BMO_OP_SLOT_SUBTYPE_PTR_OBJECT:
+                        tp_str = "Object"
+                    elif tp_sub == BMO_OP_SLOT_SUBTYPE_PTR_MESH:
+                        tp_str = "Mesh"
+                    else:
+                        print("Cant find", vars_dict_reverse[tp_sub])
+                        assert(0)
+
+                elif tp == BMO_OP_SLOT_ELEMENT_BUF:
+                    assert(tp_sub is not None)
+                    
+                    ls = []
+                    if tp_sub & BM_VERT: ls.append("vert")
+                    if tp_sub & BM_EDGE: ls.append("edge")
+                    if tp_sub & BM_FACE: ls.append("face")
+                    assert(ls)  # must be at least one
+
+                    if tp_sub & BMO_OP_SLOT_SUBTYPE_ELEM_IS_SINGLE:
+                        tp_str = "/".join(ls)
+                    else:
+                        tp_str = ("list of (%s)" % ", ".join(ls))
+                        
+                    del ls
+                elif tp == BMO_OP_SLOT_MAPPING:
+                    if tp_sub & BMO_OP_SLOT_SUBTYPE_MAP_EMPTY:
+                        tp_str = "set of vert/edge/face type"
+                    else:
+                        tp_str = "dict mapping vert/edge/face types to "
+                        if tp_sub == BMO_OP_SLOT_SUBTYPE_MAP_BOOL:
+                            tp_str += "bool"
+                        elif tp_sub == BMO_OP_SLOT_SUBTYPE_MAP_INT:
+                            tp_str += "int"
+                        elif tp_sub == BMO_OP_SLOT_SUBTYPE_MAP_FLOAT:
+                            tp_str += "float"
+                        elif tp_sub == BMO_OP_SLOT_SUBTYPE_MAP_ELEM:
+                            tp_str += "vert/edge/face elements"
+                        elif tp_sub == BMO_OP_SLOT_SUBTYPE_MAP_INTERNAL:
+                            tp_str += "unknown internal data, not compatible with python"
+                        else:
+                            print("Cant find", vars_dict_reverse[tp_sub])
+                            assert(0)
+                else:
+                    print("Cant find", vars_dict_reverse[tp])
+                    assert(0)
+
+                args_wash.append((name, tp_str, comment))
+            return args_wash
+        # end get_args_wash
+
+
+        args_in_wash = get_args_wash(args_in, args_in_index)
+        args_out_wash = get_args_wash(args_out, args_out_index)
+
+        for (name, tp, comment) in args_in_wash:
+            if comment == "":
+                comment = "Undocumented."
+
+            fw("   :arg %s: %s\n" % (name, comment))
+            fw("   :type %s: %s\n" % (name, tp))
+        
+        if args_out_wash:
+            fw("   :return:\n\n")
+            
+            for (name, tp, comment) in args_out_wash:
+                assert(name.endswith(".out"))
+                name = name[:-4]
+                fw("      - ``%s``: %s\n\n" % (name, comment))
+                fw("        **type** %s\n" % tp)
+            
+            fw("\n")
+            fw("   :rtype: dict with string keys\n")
+
+        fw("\n\n")
+        
+    fout.close()
+    del fout
+    print(OUT_RST)
+
+
+if __name__ == "__main__":
+    main()
index 410612f..1814261 100644 (file)
@@ -35,7 +35,7 @@ API dump in RST files
     ./blender.bin --background --python doc/python_api/sphinx_doc_gen.py -- --output ../python_api
 
   For quick builds:
-    ./blender.bin --background --python doc/python_api/sphinx_doc_gen.py -- --partial
+    ./blender.bin --background --python doc/python_api/sphinx_doc_gen.py -- --partial bmesh.*
 
 
 Sphinx: HTML generation
@@ -245,6 +245,7 @@ else:
         "bgl",
         "blf",
         "bmesh",
+        "bmesh.ops",
         "bmesh.types",
         "bmesh.utils",
         "bpy.app",
@@ -297,7 +298,7 @@ try:
     __import__("aud")
 except ImportError:
     BPY_LOGGER.debug("Warning: Built without 'aud' module, docs incomplete...")
-    EXCLUDE_MODULES = EXCLUDE_MODULES + ("aud", )
+    EXCLUDE_MODULES = list(EXCLUDE_MODULES) + ["aud"]
 
 # examples
 EXAMPLES_DIR = os.path.abspath(os.path.join(SCRIPT_DIR, "examples"))
@@ -1472,6 +1473,11 @@ def write_sphinx_conf_py(basepath):
     file.close()
 
 
+def execfile(filepath):
+    global_namespace = {"__file__": filepath, "__name__": "__main__"}
+    exec(compile(open(filepath).read(), filepath, 'exec'), global_namespace)
+
+
 def write_rst_contents(basepath):
     '''
     Write the rst file of the main page, needed for sphinx (index.html)
@@ -1533,13 +1539,17 @@ def write_rst_contents(basepath):
         # misc
         "bgl", "blf", "gpu", "aud", "bpy_extras",
         # bmesh
-        "bmesh", "bmesh.types", "bmesh.utils",
+        "bmesh", "bmesh.types", "bmesh.utils", "bmesh.ops",
         )
 
     for mod in standalone_modules:
         if mod not in EXCLUDE_MODULES:
             fw("   %s\n\n" % mod)
 
+    # special case, this 'bmesh.ops.rst' is extracted from C source
+    if "bmesh.ops" not in EXCLUDE_MODULES:
+        execfile(os.path.join(SCRIPT_DIR, "rst_from_bmesh_opdefines.py"))
+
     # game engine
     if "bge" not in EXCLUDE_MODULES:
         fw(title_string("Game Engine Modules", "=", double=True))
@@ -1701,6 +1711,8 @@ def copy_handwritten_rsts(basepath):
         "bgl",  # "Blender OpenGl wrapper"
         "gpu",  # "GPU Shader Module"
 
+        "bmesh.ops",  # generated by rst_from_bmesh_opdefines.py
+
         # includes...
         "include__bmesh",
     ]