# ***** 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. # # Contributor(s): Campbell Barton # # #**** END GPL LICENSE BLOCK #**** script_help_msg = ''' Usage, run this script from blenders root path once you have compiled blender ./blender.bin -b -P /b/source/blender/python/doc/sphinx_doc_gen.py This will generate python files in "./source/blender/python/doc/sphinx-in" Generate html docs by running... sphinx-build source/blender/python/doc/sphinx-in source/blender/python/doc/sphinx-out For PDF generation sphinx-build -b latex source/blender/python/doc/sphinx-in source/blender/python/doc/sphinx-out cd source/blender/python/doc/sphinx-out make ''' import os import inspect import bpy import rna_info reload(rna_info) def range_str(val): if val < -10000000: return '-inf' if val > 10000000: return 'inf' if type(val)==float: return '%g' % val else: return str(val) def write_indented_lines(ident, fn, text, strip=True): if text is None: return for l in text.split("\n"): if strip: fn(ident + l.strip() + "\n") else: fn(ident + l + "\n") def pymethod2sphinx(ident, fw, identifier, py_func): ''' class method to sphinx ''' arg_str = inspect.formatargspec(*inspect.getargspec(py_func)) if arg_str.startswith("(self, "): arg_str = "(" + arg_str[7:] func_type = "method" elif arg_str.startswith("(cls, "): arg_str = "(" + arg_str[6:] func_type = "classmethod" else: func_type = "staticmethod" fw(ident + ".. %s:: %s%s\n\n" % (func_type, identifier, arg_str)) if py_func.__doc__: write_indented_lines(ident + " ", fw, py_func.__doc__) fw("\n") def pyfunc2sphinx(ident, fw, identifier, py_func, is_class=True): ''' function or class method to sphinx ''' arg_str = inspect.formatargspec(*inspect.getargspec(py_func)) if not is_class: func_type = "function" # ther rest are class methods elif arg_str.startswith("(self, "): arg_str = "(" + arg_str[7:] func_type = "method" elif arg_str.startswith("(cls, "): arg_str = "(" + arg_str[6:] func_type = "classmethod" else: func_type = "staticmethod" fw(ident + ".. %s:: %s%s\n\n" % (func_type, identifier, arg_str)) if py_func.__doc__: write_indented_lines(ident + " ", fw, py_func.__doc__.strip()) fw("\n") def py_c_func2sphinx(ident, fw, identifier, py_func, is_class=True): ''' c defined function to sphinx. ''' # dump the docstring, assume its formatted correctly if py_func.__doc__: write_indented_lines(ident, fw, py_func.__doc__, False) fw("\n") else: fw(ident + ".. function:: %s()\n\n" % identifier) fw(ident + " Undocumented function.\n\n" % identifier) def pyprop2sphinx(ident, fw, identifier, py_prop): ''' python property to sphinx ''' fw(ident + ".. attribute:: %s\n\n" % identifier) write_indented_lines(ident + " ", fw, py_prop.__doc__) if py_prop.fset is None: fw(ident + " (readonly)\n\n") def pymodule2sphinx(BASEPATH, module_name, module, title): import types # lame, python wont give some access MethodDescriptorType = type(dict.get) GetSetDescriptorType = type(int.real) filepath = os.path.join(BASEPATH, module_name + ".rst") file = open(filepath, "w") fw = file.write fw(title + "\n") fw(("=" * len(title)) + "\n\n") fw(".. module:: %s\n\n" % module_name) if module.__doc__: # Note, may contain sphinx syntax, dont mangle! fw(module.__doc__.strip()) fw("\n\n") # write members of the module # only tested with PyStructs which are not exactly modules for attribute, descr in sorted(type(module).__dict__.items()): if type(descr) == types.MemberDescriptorType: if descr.__doc__: fw(".. data:: %s\n\n" % attribute) write_indented_lines(" ", fw, descr.__doc__, False) fw("\n") classes = [] for attribute in dir(module): if not attribute.startswith("_"): value = getattr(module, attribute) value_type = type(value) if value_type == types.FunctionType: pyfunc2sphinx("", fw, attribute, value, is_class=False) elif value_type in (types.BuiltinMethodType, types.BuiltinFunctionType): # both the same at the moment but to be future proof # note: can't get args from these, so dump the string as is # this means any module used like this must have fully formatted docstrings. py_c_func2sphinx("", fw, attribute, value, is_class=False) elif value_type == type: classes.append((attribute, value)) # TODO, more types... # write collected classes now for (attribute, value) in classes: # May need to be its own function fw(".. class:: %s\n\n" % attribute) if value.__doc__: write_indented_lines(" ", fw, value.__doc__, False) fw("\n") for key in sorted(value.__dict__.keys()): if key.startswith("__"): continue descr = value.__dict__[key] if type(descr) == GetSetDescriptorType: if descr.__doc__: fw(" .. attribute:: %s\n\n" % key) write_indented_lines(" ", fw, descr.__doc__, False) fw("\n") for key in sorted(value.__dict__.keys()): if key.startswith("__"): continue descr = value.__dict__[key] if type(descr) == MethodDescriptorType: # GetSetDescriptorType, GetSetDescriptorType's are not documented yet if descr.__doc__: write_indented_lines(" ", fw, descr.__doc__, False) fw("\n") fw("\n\n") file.close() def rna2sphinx(BASEPATH): structs, funcs, ops, props = rna_info.BuildRNAInfo() try: os.mkdir(BASEPATH) except: pass # conf.py - empty for now filepath = os.path.join(BASEPATH, "conf.py") file = open(filepath, "w") fw = file.write version_string = bpy.app.version_string.split("(")[0] if bpy.app.build_revision != "Unknown": version_string = version_string + " r" + bpy.app.build_revision fw("project = 'Blender 3D'\n") # fw("master_doc = 'index'\n") fw("copyright = u'Blender Foundation'\n") fw("version = '%s'\n" % version_string) fw("release = '%s'\n" % version_string) fw("\n") # needed for latex, pdf gen fw("latex_documents = [ ('contents', 'contents.tex', 'Blender Index', 'Blender Foundation', 'manual'), ]\n") fw("latex_paper_size = 'a4paper'\n") file.close() filepath = os.path.join(BASEPATH, "contents.rst") file = open(filepath, "w") fw = file.write fw("%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n") fw(" Blender Documentation contents\n") fw("%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n") fw("\n") fw("This document is an API reference for Blender %s. built %s.\n" % (version_string, bpy.app.build_date)) fw("\n") fw("An introduction to blender and python can be found at \n") fw("\n") fw(".. toctree::\n") fw(" :maxdepth: 1\n\n") fw(" bpy.ops.rst\n\n") fw(" bpy.types.rst\n\n") # py modules fw(" bpy.utils.rst\n\n") fw(" bpy.app.rst\n\n") # C modules fw(" bpy.props.rst\n\n") fw(" Mathutils.rst\n\n") file.close() # internal modules filepath = os.path.join(BASEPATH, "bpy.ops.rst") file = open(filepath, "w") fw = file.write fw("Blender Operators (bpy.ops)\n") fw("===========================\n\n") fw(".. toctree::\n") fw(" :glob:\n\n") fw(" bpy.ops.*\n\n") file.close() filepath = os.path.join(BASEPATH, "bpy.types.rst") file = open(filepath, "w") fw = file.write fw("Blender Types (bpy.types)\n") fw("=========================\n\n") fw(".. toctree::\n") fw(" :glob:\n\n") fw(" bpy.types.*\n\n") file.close() # python modules from bpy import utils as module pymodule2sphinx(BASEPATH, "bpy.utils", module, "Utilities (bpy.utils)") # C modules from bpy import app as module pymodule2sphinx(BASEPATH, "bpy.app", module, "Application Data (bpy.app)") from bpy import props as module pymodule2sphinx(BASEPATH, "bpy.props", module, "Property Definitions (bpy.props)") import Mathutils as module pymodule2sphinx(BASEPATH, "Mathutils", module, "Math Types & Utilities (Mathutils)") del module if 0: filepath = os.path.join(BASEPATH, "bpy.rst") file = open(filepath, "w") fw = file.write fw("\n") title = ":mod:`bpy` --- Blender Python Module" fw("%s\n%s\n\n" % (title, "=" * len(title))) fw(".. module:: bpy.types\n\n") file.close() def write_param(ident, fw, prop, is_return=False): if is_return: id_name = "return" id_type = "rtype" kwargs = {"as_ret": True, "class_fmt": ":class:`%s`"} identifier = "" else: id_name = "arg" id_type = "type" kwargs = {"as_arg": True, "class_fmt": ":class:`%s`"} identifier = " %s" % prop.identifier type_descr = prop.get_type_description(**kwargs) if prop.name or prop.description: fw(ident + ":%s%s: %s\n" % (id_name, identifier, ", ".join([val for val in (prop.name, prop.description) if val]))) fw(ident + ":%s%s: %s\n" % (id_type, identifier, type_descr)) def write_struct(struct): #if not struct.identifier.startswith("Sc") and not struct.identifier.startswith("I"): # return #if not struct.identifier == "Object": # return filepath = os.path.join(BASEPATH, "bpy.types.%s.rst" % struct.identifier) file = open(filepath, "w") fw = file.write if struct.base: title = "%s(%s)" % (struct.identifier, struct.base.identifier) else: title = struct.identifier fw("%s\n%s\n\n" % (title, "=" * len(title))) fw(".. module:: bpy.types\n\n") bases = struct.get_bases() if bases: if len(bases) > 1: fw("base classes --- ") else: fw("base class --- ") fw(", ".join([(":class:`%s`" % base.identifier) for base in reversed(bases)])) fw("\n\n") subclasses = [s for s in structs.values() if s.base is struct] if subclasses: fw("subclasses --- \n") fw(", ".join([(":class:`%s`" % s.identifier) for s in subclasses])) fw("\n\n") if struct.base: fw(".. class:: %s(%s)\n\n" % (struct.identifier, struct.base.identifier)) else: fw(".. class:: %s\n\n" % struct.identifier) fw(" %s\n\n" % struct.description) for prop in struct.properties: fw(" .. attribute:: %s\n\n" % prop.identifier) if prop.description: fw(" %s\n\n" % prop.description) type_descr = prop.get_type_description(class_fmt=":class:`%s`") fw(" *type* %s\n\n" % type_descr) # python attributes py_properties = struct.get_py_properties() py_prop = None for identifier, py_prop in py_properties: pyprop2sphinx(" ", fw, identifier, py_prop) del py_properties, py_prop for func in struct.functions: args_str = ", ".join([prop.get_arg_default(force=False) for prop in func.args]) fw(" .. method:: %s(%s)\n\n" % (func.identifier, args_str)) fw(" %s\n\n" % func.description) for prop in func.args: write_param(" ", fw, prop) if len(func.return_values) == 1: write_param(" ", fw, func.return_values[0], is_return=True) elif func.return_values: # multiple return values fw(" :return (%s):\n" % ", ".join([prop.identifier for prop in func.return_values])) for prop in func.return_values: type_descr = prop.get_type_description(as_ret=True, class_fmt=":class:`%s`") descr = prop.description if not descr: descr = prop.name fw(" `%s`, %s, %s\n\n" % (prop.identifier, descr, type_descr)) fw("\n") # python methods py_funcs = struct.get_py_functions() py_func = None for identifier, py_func in py_funcs: pyfunc2sphinx(" ", fw, identifier, py_func, is_class=True) del py_funcs, py_func if struct.references: # use this otherwise it gets in the index for a normal heading. fw(".. rubric:: References\n\n") for ref in struct.references: ref_split = ref.split(".") if len(ref_split) > 2: ref = ref_split[-2] + "." + ref_split[-1] fw("* :class:`%s`\n" % ref) fw("\n") for struct in structs.values(): write_struct(struct) # oeprators def write_ops(): fw = None last_mod = '' for op_key in sorted(ops.keys()): op = ops[op_key] if last_mod != op.module_name: filepath = os.path.join(BASEPATH, "bpy.ops.%s.rst" % op.module_name) file = open(filepath, "w") fw = file.write title = "%s Operators" % (op.module_name[0].upper() + op.module_name[1:]) fw("%s\n%s\n\n" % (title, "=" * len(title))) fw(".. module:: bpy.ops.%s\n\n" % op.module_name) last_mod = op.module_name args_str = ", ".join([prop.get_arg_default(force=True) for prop in op.args]) fw(".. function:: %s(%s)\n\n" % (op.func_name, args_str)) if op.description: fw(" %s\n\n" % op.description) for prop in op.args: write_param(" ", fw, prop) if op.args: fw("\n") location = op.get_location() if location != (None, None): fw(" *python operator source --- `%s:%d`* \n\n" % location) write_ops() file.close() if __name__ == '__main__': if 'bpy' not in dir(): print("\nError, this script must run from inside blender2.5") print(script_help_msg) else: import shutil path_in = 'source/blender/python/doc/sphinx-in' path_out = 'source/blender/python/doc/sphinx-in' shutil.rmtree(path_in, True) shutil.rmtree(path_out, True) rna2sphinx(path_in) # for fast module testing # os.system("rm source/blender/python/doc/sphinx-in/bpy.types.*.rst") # os.system("rm source/blender/python/doc/sphinx-in/bpy.ops.*.rst") import sys sys.exit()