diff options
Diffstat (limited to 'doc/python_api/sphinx_doc_gen.py')
-rw-r--r-- | doc/python_api/sphinx_doc_gen.py | 1317 |
1 files changed, 1317 insertions, 0 deletions
diff --git a/doc/python_api/sphinx_doc_gen.py b/doc/python_api/sphinx_doc_gen.py new file mode 100644 index 00000000000..661d41af4ef --- /dev/null +++ b/doc/python_api/sphinx_doc_gen.py @@ -0,0 +1,1317 @@ + # ***** 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 #**** + +# <pep8 compliant> + +script_help_msg = ''' +Usage: + +For HTML generation +------------------- +- Run this script from blenders root path once you have compiled blender + + ./blender.bin --background --python doc/python_api/sphinx_doc_gen.py + + This will generate python files in doc/python_api/sphinx-in/ + providing ./blender.bin is or links to the blender executable + +- Generate html docs by running... + + cd doc/python_api + sphinx-build sphinx-in sphinx-out + + This requires sphinx 1.0.7 to be installed. + +For PDF generation +------------------ +- After you have built doc/python_api/sphinx-in (see above), run: + + sphinx-build -b latex doc/python_api/sphinx-in doc/python_api/sphinx-out + cd doc/python_api/sphinx-out + make +''' + +# Check we're running in blender +if __import__("sys").modules.get("bpy") is None: + print("\nError, this script must run from inside blender2.5") + print(script_help_msg) + + import sys + sys.exit() + + +# Switch for quick testing +if 1: + # full build + EXCLUDE_INFO_DOCS = False + EXCLUDE_MODULES = () + FILTER_BPY_TYPES = None + FILTER_BPY_OPS = None + +else: + EXCLUDE_INFO_DOCS = False + # for testing so doc-builds dont take so long. + EXCLUDE_MODULES = ( + "bpy.context", + "bpy.app", + "bpy.path", + "bpy.data", + "bpy.props", + "bpy.utils", + "bpy.context", + "bpy.types", # supports filtering + "bpy.ops", # supports filtering + "bpy_extras", + "bge", + "aud", + "bgl", + "blf", + "mathutils", + "mathutils.geometry", + ) + + FILTER_BPY_TYPES = ("bpy_struct", "Panel", "ID") # allow + FILTER_BPY_OPS = ("import.scene", ) # allow + + # for quick rebuilds + """ +rm -rf /b/doc/python_api/sphinx-* && \ +./blender.bin --background --factory-startup --python doc/python_api/sphinx_doc_gen.py && \ +sphinx-build doc/python_api/sphinx-in doc/python_api/sphinx-out + + """ + +# extra info, not api reference docs +# stored in ./rst/info/ +INFO_DOCS = ( + ("info_quickstart.rst", "Blender/Python Quickstart: new to blender/scripting and want to get you're feet wet?"), + ("info_overview.rst", "Blender/Python API Overview: a more complete explanation of python integration"), + ("info_gotcha.rst", "Gotcha's: some of the problems you may come up against when writing scripts"), + ) + +# import rpdb2; rpdb2.start_embedded_debugger('test') + +import os +import inspect +import bpy +import rna_info + +# lame, python wont give some access +ClassMethodDescriptorType = type(dict.__dict__['fromkeys']) +MethodDescriptorType = type(dict.get) +GetSetDescriptorType = type(int.real) + +EXAMPLE_SET = set() +EXAMPLE_SET_USED = set() + +_BPY_STRUCT_FAKE = "bpy_struct" +_BPY_PROP_COLLECTION_FAKE = "bpy_prop_collection" +_BPY_FULL_REBUILD = False + +if _BPY_PROP_COLLECTION_FAKE: + _BPY_PROP_COLLECTION_ID = ":class:`%s`" % _BPY_PROP_COLLECTION_FAKE +else: + _BPY_PROP_COLLECTION_ID = "collection" + + +def undocumented_message(module_name, type_name, identifier): + if str(type_name).startswith('<module'): + preloadtitle = '%s.%s' % (module_name, identifier) + else: + preloadtitle = '%s.%s.%s' % (module_name, type_name, identifier) + message = "Undocumented (`contribute "\ + "<http://wiki.blender.org/index.php/Dev:2.5/Py/API/Documentation/Contribute"\ + "?action=edit§ion=new&preload=Dev:2.5/Py/API/Documentation/Contribute/Howto-message"\ + "&preloadtitle=%s>`_)\n\n" % preloadtitle + return message + + +def range_str(val): + ''' + Converts values to strings for the range directive. + (unused function it seems) + ''' + if val < -10000000: + return '-inf' + elif val > 10000000: + return 'inf' + elif type(val) == float: + return '%g' % val + else: + return str(val) + + +def example_extract_docstring(filepath): + file = open(filepath, 'r') + line = file.readline() + line_no = 0 + text = [] + if line.startswith('"""'): # assume nothing here + line_no += 1 + else: + file.close() + return "", 0 + + for line in file.readlines(): + line_no += 1 + if line.startswith('"""'): + break + else: + text.append(line.rstrip()) + + line_no += 1 + file.close() + return "\n".join(text), line_no + + +def write_title(fw, text, heading_char): + fw("%s\n%s\n\n" % (text, len(text) * heading_char)) + + +def write_example_ref(ident, fw, example_id, ext="py"): + if example_id in EXAMPLE_SET: + + # extract the comment + filepath = "../examples/%s.%s" % (example_id, ext) + filepath_full = os.path.join(os.path.dirname(fw.__self__.name), filepath) + + text, line_no = example_extract_docstring(filepath_full) + + for line in text.split("\n"): + fw("%s\n" % (ident + line).rstrip()) + fw("\n") + + fw("%s.. literalinclude:: %s\n" % (ident, filepath)) + if line_no > 0: + fw("%s :lines: %d-\n" % (ident, line_no)) + fw("\n") + EXAMPLE_SET_USED.add(example_id) + else: + if bpy.app.debug: + print("\tskipping example:", example_id) + + # Support for numbered files bpy.types.Operator -> bpy.types.Operator.1.py + i = 1 + while True: + example_id_num = "%s.%d" % (example_id, i) + if example_id_num in EXAMPLE_SET: + write_example_ref(ident, fw, example_id_num, ext) + i += 1 + else: + break + + +def write_indented_lines(ident, fn, text, strip=True): + ''' + Apply same indentation to all lines in a multilines text. + ''' + if text is None: + return + + lines = text.split("\n") + + # strip empty lines from the start/end + while lines and not lines[0].strip(): + del lines[0] + while lines and not lines[-1].strip(): + del lines[-1] + + if strip: + ident_strip = 1000 + for l in lines: + if l.strip(): + ident_strip = min(ident_strip, len(l) - len(l.lstrip())) + for l in lines: + fn(ident + l[ident_strip:] + "\n") + else: + for l in lines: + 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__) + fw("\n") + + +def py_descr2sphinx(ident, fw, descr, module_name, type_name, identifier): + if identifier.startswith("_"): + return + + doc = descr.__doc__ + if not doc: + doc = undocumented_message(module_name, type_name, identifier) + + if type(descr) == GetSetDescriptorType: + fw(ident + ".. attribute:: %s\n\n" % identifier) + write_indented_lines(ident + " ", fw, doc, False) + fw("\n") + elif type(descr) in (MethodDescriptorType, ClassMethodDescriptorType): + write_indented_lines(ident, fw, doc, False) + fw("\n") + else: + raise TypeError("type was not GetSetDescriptorType, MethodDescriptorType or ClassMethodDescriptorType") + + write_example_ref(ident + " ", fw, module_name + "." + type_name + "." + identifier) + fw("\n") + + +def py_c_func2sphinx(ident, fw, module_name, type_name, 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_message(module_name, type_name, identifier)) + + if is_class: + write_example_ref(ident + " ", fw, module_name + "." + type_name + "." + identifier) + else: + write_example_ref(ident + " ", fw, module_name + "." + identifier) + + fw("\n") + + +def pyprop2sphinx(ident, fw, identifier, py_prop): + ''' + python property to sphinx + ''' + # readonly properties use "data" directive, variables use "attribute" directive + if py_prop.fset is None: + fw(ident + ".. data:: %s\n\n" % identifier) + else: + 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 + attribute_set = set() + filepath = os.path.join(BASEPATH, module_name + ".rst") + + module_all = getattr(module, "__all__", None) + module_dir = sorted(dir(module)) + + if module_all: + module_dir = module_all + + file = open(filepath, "w") + + fw = file.write + + write_title(fw, "%s (%s)" % (title, module_name), "=") + + 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_example_ref("", fw, module_name) + + # write submodules + # we could also scan files but this ensures __all__ is used correctly + if module_all is not None: + submod_name = None + submod = None + submod_ls = [] + for submod_name in module_all: + ns = {} + exec_str = "from %s import %s as submod" % (module.__name__, submod_name) + exec(exec_str, ns, ns) + submod = ns["submod"] + if type(submod) == types.ModuleType: + submod_ls.append((submod_name, submod)) + + del submod_name + del submod + + if submod_ls: + fw(".. toctree::\n") + fw(" :maxdepth: 1\n\n") + + for submod_name, submod in submod_ls: + submod_name_full = "%s.%s" % (module_name, submod_name) + fw(" %s.rst\n\n" % submod_name_full) + + pymodule2sphinx(BASEPATH, submod_name_full, submod, "%s submodule" % module_name) + del submod_ls + # done writing submodules! + + # write members of the module + # only tested with PyStructs which are not exactly modules + for key, descr in sorted(type(module).__dict__.items()): + if key.startswith("__"): + continue + # naughty, we also add getset's into PyStructs, this is not typical py but also not incorrect. + if type(descr) == types.GetSetDescriptorType: # 'bpy_app_type' name is only used for examples and messages + py_descr2sphinx("", fw, descr, module_name, "bpy_app_type", key) + attribute_set.add(key) + for key, descr in sorted(type(module).__dict__.items()): + if key.startswith("__"): + continue + + if type(descr) == types.MemberDescriptorType: + if descr.__doc__: + fw(".. data:: %s\n\n" % key) + write_indented_lines(" ", fw, descr.__doc__, False) + fw("\n") + attribute_set.add(key) + + del key, descr + + classes = [] + submodules = [] + + for attribute in module_dir: + if not attribute.startswith("_"): + if attribute in attribute_set: + continue + + if attribute.startswith("n_"): # annoying exception, needed for bpy.app + continue + + 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, module_name, None, attribute, value, is_class=False) + elif value_type == type: + classes.append((attribute, value)) + elif issubclass(value_type, types.ModuleType): + submodules.append((attribute, value)) + elif value_type in (bool, int, float, str, tuple): + # constant, not much fun we can do here except to list it. + # TODO, figure out some way to document these! + fw(".. data:: %s\n\n" % attribute) + write_indented_lines(" ", fw, "constant value %s" % repr(value), False) + fw("\n") + else: + print("\tnot documenting %s.%s of %r type" % (module_name, attribute, value_type.__name__)) + continue + + attribute_set.add(attribute) + # TODO, more types... + + # TODO, bpy_extras does this already, mathutils not. + """ + if submodules: + fw("\n" + "**********\n" + "Submodules\n" + "**********\n" + "\n" + ) + for attribute, submod in submodules: + fw("* :mod:`%s.%s`\n" % (module_name, attribute)) + fw("\n") + """ + + # write collected classes now + for (type_name, value) in classes: + # May need to be its own function + fw(".. class:: %s\n\n" % type_name) + if value.__doc__: + write_indented_lines(" ", fw, value.__doc__, False) + fw("\n") + write_example_ref(" ", fw, module_name + "." + type_name) + + descr_items = [(key, descr) for key, descr in sorted(value.__dict__.items()) if not key.startswith("__")] + + for key, descr in descr_items: + if type(descr) == ClassMethodDescriptorType: + py_descr2sphinx(" ", fw, descr, module_name, type_name, key) + + for key, descr in descr_items: + if type(descr) == MethodDescriptorType: + py_descr2sphinx(" ", fw, descr, module_name, type_name, key) + + for key, descr in descr_items: + if type(descr) == GetSetDescriptorType: + py_descr2sphinx(" ", fw, descr, module_name, type_name, key) + + fw("\n\n") + + file.close() + + +def pycontext2sphinx(BASEPATH): + # Only use once. very irregular + + filepath = os.path.join(BASEPATH, "bpy.context.rst") + file = open(filepath, "w") + fw = file.write + fw("Context Access (bpy.context)\n") + fw("============================\n\n") + fw(".. module:: bpy.context\n") + fw("\n") + fw("The context members available depend on the area of blender which is currently being accessed.\n") + fw("\n") + fw("Note that all context values are readonly, but may be modified through the data api or by running operators\n\n") + + # nasty, get strings directly from blender because there is no other way to get it + import ctypes + + context_strings = ( + "screen_context_dir", + "view3d_context_dir", + "buttons_context_dir", + "image_context_dir", + "node_context_dir", + "text_context_dir", + ) + + # Changes in blender will force errors here + type_map = { + "active_base": ("ObjectBase", False), + "active_bone": ("Bone", False), + "active_object": ("Object", False), + "active_pose_bone": ("PoseBone", False), + "armature": ("Armature", False), + "bone": ("Bone", False), + "brush": ("Brush", False), + "camera": ("Camera", False), + "cloth": ("ClothModifier", False), + "collision": ("CollisionModifier", False), + "curve": ("Curve", False), + "edit_bone": ("EditBone", False), + "edit_image": ("Image", False), + "edit_object": ("Object", False), + "edit_text": ("Text", False), + "editable_bones": ("EditBone", True), + "fluid": ("FluidSimulationModifier", False), + "image_paint_object": ("Object", False), + "lamp": ("Lamp", False), + "lattice": ("Lattice", False), + "material": ("Material", False), + "material_slot": ("MaterialSlot", False), + "mesh": ("Mesh", False), + "meta_ball": ("MetaBall", False), + "object": ("Object", False), + "particle_edit_object": ("Object", False), + "particle_system": ("ParticleSystem", False), + "particle_system_editable": ("ParticleSystem", False), + "pose_bone": ("PoseBone", False), + "scene": ("Scene", False), + "sculpt_object": ("Object", False), + "selectable_bases": ("ObjectBase", True), + "selectable_objects": ("Object", True), + "selected_bases": ("ObjectBase", True), + "selected_bones": ("Bone", True), + "selected_editable_bases": ("ObjectBase", True), + "selected_editable_bones": ("Bone", True), + "selected_editable_objects": ("Object", True), + "selected_editable_sequences": ("Sequence", True), + "selected_nodes": ("Node", True), + "selected_objects": ("Object", True), + "selected_pose_bones": ("PoseBone", True), + "selected_sequences": ("Sequence", True), + "sequences": ("Sequence", True), + "smoke": ("SmokeModifier", False), + "soft_body": ("SoftBodyModifier", False), + "speaker": ("Speaker", False), + "texture": ("Texture", False), + "texture_slot": ("MaterialTextureSlot", False), + "vertex_paint_object": ("Object", False), + "visible_bases": ("ObjectBase", True), + "visible_bones": ("Object", True), + "visible_objects": ("Object", True), + "visible_pose_bones": ("PoseBone", True), + "weight_paint_object": ("Object", False), + "world": ("World", False), + } + + unique = set() + blend_cdll = ctypes.CDLL("") + for ctx_str in context_strings: + subsection = "%s Context" % ctx_str.split("_")[0].title() + fw("\n%s\n%s\n\n" % (subsection, (len(subsection) * '-'))) + + attr = ctypes.addressof(getattr(blend_cdll, ctx_str)) + c_char_p_p = ctypes.POINTER(ctypes.c_char_p) + char_array = c_char_p_p.from_address(attr) + i = 0 + while char_array[i] is not None: + member = ctypes.string_at(char_array[i]).decode() + fw(".. data:: %s\n\n" % member) + member_type, is_seq = type_map[member] + fw(" :type: %s :class:`bpy.types.%s`\n\n" % ("sequence of " if is_seq else "", member_type)) + unique.add(member) + i += 1 + + # generate typemap... + # for member in sorted(unique): + # print(' "%s": ("", False),' % member) + if len(type_map) > len(unique): + raise Exception("Some types are not used: %s" % str([member for member in type_map if member not in unique])) + else: + pass # will have raised an error above + + file.close() + + +def pyrna2sphinx(BASEPATH): + """ bpy.types and bpy.ops + """ + structs, funcs, ops, props = rna_info.BuildRNAInfo() + if FILTER_BPY_TYPES is not None: + structs = {k: v for k, v in structs.items() if k[1] in FILTER_BPY_TYPES} + + if FILTER_BPY_OPS is not None: + ops = {k: v for k, v in ops.items() if v.module_name in FILTER_BPY_OPS} + + def write_param(ident, fw, prop, is_return=False): + if is_return: + id_name = "return" + id_type = "rtype" + kwargs = {"as_ret": True} + identifier = "" + else: + id_name = "arg" + id_type = "type" + kwargs = {"as_arg": True} + identifier = " %s" % prop.identifier + + kwargs["class_fmt"] = ":class:`%s`" + + kwargs["collection_id"] = _BPY_PROP_COLLECTION_ID + + 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 + + base_id = getattr(struct.base, "identifier", "") + + if _BPY_STRUCT_FAKE: + if not base_id: + base_id = _BPY_STRUCT_FAKE + + if base_id: + title = "%s(%s)" % (struct.identifier, base_id) + else: + title = struct.identifier + + write_title(fw, title, "=") + + fw(".. module:: bpy.types\n\n") + + # docs first?, ok + write_example_ref("", fw, "bpy.types.%s" % struct.identifier) + + base_ids = [base.identifier for base in struct.get_bases()] + + if _BPY_STRUCT_FAKE: + base_ids.append(_BPY_STRUCT_FAKE) + + base_ids.reverse() + + if base_ids: + if len(base_ids) > 1: + fw("base classes --- ") + else: + fw("base class --- ") + + fw(", ".join((":class:`%s`" % base_id) for base_id in base_ids)) + fw("\n\n") + + subclass_ids = [s.identifier for s in structs.values() if s.base is struct if not rna_info.rna_id_ignore(s.identifier)] + if subclass_ids: + fw("subclasses --- \n" + ", ".join((":class:`%s`" % s) for s in subclass_ids) + "\n\n") + + base_id = getattr(struct.base, "identifier", "") + + if _BPY_STRUCT_FAKE: + if not base_id: + base_id = _BPY_STRUCT_FAKE + + if base_id: + fw(".. class:: %s(%s)\n\n" % (struct.identifier, base_id)) + else: + fw(".. class:: %s\n\n" % struct.identifier) + + fw(" %s\n\n" % struct.description) + + # properties sorted in alphabetical order + sorted_struct_properties = struct.properties[:] + sorted_struct_properties.sort(key=lambda prop: prop.identifier) + + for prop in sorted_struct_properties: + type_descr = prop.get_type_description(class_fmt=":class:`%s`", collection_id=_BPY_PROP_COLLECTION_ID) + # readonly properties use "data" directive, variables properties use "attribute" directive + if 'readonly' in type_descr: + fw(" .. data:: %s\n\n" % prop.identifier) + else: + fw(" .. attribute:: %s\n\n" % prop.identifier) + if prop.description: + fw(" %s\n\n" % prop.description) + 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(" .. %s:: %s(%s)\n\n" % ("classmethod" if func.is_classmethod else "method", 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`", collection_id=_BPY_PROP_COLLECTION_ID) + descr = prop.description + if not descr: + descr = prop.name + fw(" `%s`, %s, %s\n\n" % (prop.identifier, descr, type_descr)) + + write_example_ref(" ", fw, "bpy.types." + struct.identifier + "." + func.identifier) + + 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 + + py_funcs = struct.get_py_c_functions() + py_func = None + + for identifier, py_func in py_funcs: + py_c_func2sphinx(" ", fw, "bpy.types", struct.identifier, identifier, py_func, is_class=True) + + lines = [] + + if struct.base or _BPY_STRUCT_FAKE: + bases = list(reversed(struct.get_bases())) + + # props + lines[:] = [] + + if _BPY_STRUCT_FAKE: + descr_items = [(key, descr) for key, descr in sorted(bpy.types.Struct.__bases__[0].__dict__.items()) if not key.startswith("__")] + + if _BPY_STRUCT_FAKE: + for key, descr in descr_items: + if type(descr) == GetSetDescriptorType: + lines.append(" * :class:`%s.%s`\n" % (_BPY_STRUCT_FAKE, key)) + + for base in bases: + for prop in base.properties: + lines.append(" * :class:`%s.%s`\n" % (base.identifier, prop.identifier)) + + for identifier, py_prop in base.get_py_properties(): + lines.append(" * :class:`%s.%s`\n" % (base.identifier, identifier)) + + for identifier, py_prop in base.get_py_properties(): + lines.append(" * :class:`%s.%s`\n" % (base.identifier, identifier)) + + if lines: + fw(".. rubric:: Inherited Properties\n\n") + + fw(".. hlist::\n") + fw(" :columns: 2\n\n") + + for line in lines: + fw(line) + fw("\n") + + # funcs + lines[:] = [] + + if _BPY_STRUCT_FAKE: + for key, descr in descr_items: + if type(descr) == MethodDescriptorType: + lines.append(" * :class:`%s.%s`\n" % (_BPY_STRUCT_FAKE, key)) + + for base in bases: + for func in base.functions: + lines.append(" * :class:`%s.%s`\n" % (base.identifier, func.identifier)) + for identifier, py_func in base.get_py_functions(): + lines.append(" * :class:`%s.%s`\n" % (base.identifier, identifier)) + + if lines: + fw(".. rubric:: Inherited Functions\n\n") + + fw(".. hlist::\n") + fw(" :columns: 2\n\n") + + for line in lines: + fw(line) + fw("\n") + + lines[:] = [] + + if struct.references: + # use this otherwise it gets in the index for a normal heading. + fw(".. rubric:: References\n\n") + + fw(".. hlist::\n") + fw(" :columns: 2\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") + + # docs last?, disable for now + # write_example_ref("", fw, "bpy.types.%s" % struct.identifier) + file.close() + + if "bpy.types" not in EXCLUDE_MODULES: + for struct in structs.values(): + # TODO, rna_info should filter these out! + if "_OT_" in struct.identifier: + continue + write_struct(struct) + + def fake_bpy_type(class_value, class_name, descr_str, use_subclasses=True): + filepath = os.path.join(BASEPATH, "bpy.types.%s.rst" % class_name) + file = open(filepath, "w") + fw = file.write + + write_title(fw, class_name, "=") + + fw(".. module:: bpy.types\n") + fw("\n") + + if use_subclasses: + subclass_ids = [s.identifier for s in structs.values() if s.base is None if not rna_info.rna_id_ignore(s.identifier)] + if subclass_ids: + fw("subclasses --- \n" + ", ".join((":class:`%s`" % s) for s in sorted(subclass_ids)) + "\n\n") + + fw(".. class:: %s\n\n" % class_name) + fw(" %s\n\n" % descr_str) + fw(" .. note::\n\n") + fw(" Note that bpy.types.%s is not actually available from within blender, it only exists for the purpose of documentation.\n\n" % class_name) + + descr_items = [(key, descr) for key, descr in sorted(class_value.__dict__.items()) if not key.startswith("__")] + + for key, descr in descr_items: + if type(descr) == MethodDescriptorType: # GetSetDescriptorType, GetSetDescriptorType's are not documented yet + py_descr2sphinx(" ", fw, descr, "bpy.types", class_name, key) + + for key, descr in descr_items: + if type(descr) == GetSetDescriptorType: + py_descr2sphinx(" ", fw, descr, "bpy.types", class_name, key) + file.close() + + # write fake classes + if _BPY_STRUCT_FAKE: + class_value = bpy.types.Struct.__bases__[0] + fake_bpy_type(class_value, _BPY_STRUCT_FAKE, "built-in base class for all classes in bpy.types.", use_subclasses=True) + + if _BPY_PROP_COLLECTION_FAKE: + class_value = bpy.data.objects.__class__ + fake_bpy_type(class_value, _BPY_PROP_COLLECTION_FAKE, "built-in class used for all collections.", use_subclasses=False) + + # operators + def write_ops(): + API_BASEURL = "http://svn.blender.org/svnroot/bf-blender/trunk/blender/release/scripts" + API_BASEURL_ADDON = "http://svn.blender.org/svnroot/bf-extensions/trunk/py/scripts" + API_BASEURL_ADDON_CONTRIB = "http://svn.blender.org/svnroot/bf-extensions/contrib/py/scripts" + + op_modules = {} + for op in ops.values(): + op_modules.setdefault(op.module_name, []).append(op) + del op + + for op_module_name, ops_mod in op_modules.items(): + 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.replace("_", " ").title() + + write_title(fw, title, "=") + + fw(".. module:: bpy.ops.%s\n\n" % op_module_name) + + ops_mod.sort(key=lambda op: op.func_name) + + for op in ops_mod: + 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 the description isn't valid, we output the standard warning + # with a link to the wiki so that people can help + if not op.description or op.description == "(undocumented operator)": + operator_description = undocumented_message('bpy.ops', op.module_name, op.func_name) + else: + operator_description = op.description + + fw(" %s\n\n" % operator_description) + for prop in op.args: + write_param(" ", fw, prop) + if op.args: + fw("\n") + + location = op.get_location() + if location != (None, None): + if location[0].startswith("addons_contrib" + os.sep): + url_base = API_BASEURL_ADDON_CONTRIB + elif location[0].startswith("addons" + os.sep): + url_base = API_BASEURL_ADDON + else: + url_base = API_BASEURL + + fw(" :file: `%s <%s/%s>`_:%d\n\n" % (location[0], url_base, location[0], location[1])) + + file.close() + + if "bpy.ops" not in EXCLUDE_MODULES: + write_ops() + + +def rna2sphinx(BASEPATH): + + 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 = ".".join(str(v) for v in bpy.app.version) + if bpy.app.build_revision != "Unknown": + version_string = version_string + " r" + bpy.app.build_revision + + version_string_fp = "_".join(str(v) for v in bpy.app.version) + + if bpy.app.version_cycle == "release": + version_string_pdf = "%s%s_release" % ("_".join(str(v) for v in bpy.app.version[:2]), bpy.app.version_char) + else: + version_string_pdf = version_string_fp + + fw("project = 'Blender'\n") + # fw("master_doc = 'index'\n") + fw("copyright = u'Blender Foundation'\n") + fw("version = '%s - API'\n" % version_string) + fw("release = '%s - API'\n" % version_string) + fw("html_theme = 'blender-org'\n") + fw("html_theme_path = ['../']\n") + fw("html_favicon = 'favicon.ico'\n") + # not helpful since the source us generated, adds to upload size. + fw("html_copy_source = False\n") + 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() + + # main page needed for sphinx (index.html) + 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("Welcome, this document is an API reference for Blender %s. built %s.\n" % (version_string, bpy.app.build_date)) + fw("\n") + fw("`A PDF version of this document is also available <blender_python_reference_%s.pdf>`_\n" % version_string_pdf) + + fw("\n") + + if not EXCLUDE_INFO_DOCS: + fw("============================\n") + fw("Blender/Python Documentation\n") + fw("============================\n") + fw("\n") + fw("\n") + fw(".. toctree::\n") + fw(" :maxdepth: 1\n\n") + for info, info_desc in INFO_DOCS: + fw(" %s <%s>\n\n" % (info_desc, info)) + fw("\n") + + fw("===================\n") + fw("Application Modules\n") + fw("===================\n") + fw("\n") + fw(".. toctree::\n") + fw(" :maxdepth: 1\n\n") + if "bpy.context" not in EXCLUDE_MODULES: + fw(" bpy.context.rst\n\n") # note: not actually a module + if "bpy.data" not in EXCLUDE_MODULES: + fw(" bpy.data.rst\n\n") # note: not actually a module + if "bpy.ops" not in EXCLUDE_MODULES: + fw(" bpy.ops.rst\n\n") + if "bpy.types" not in EXCLUDE_MODULES: + fw(" bpy.types.rst\n\n") + + # py modules + if "bpy.utils" not in EXCLUDE_MODULES: + fw(" bpy.utils.rst\n\n") + if "bpy.path" not in EXCLUDE_MODULES: + fw(" bpy.path.rst\n\n") + if "bpy.app" not in EXCLUDE_MODULES: + fw(" bpy.app.rst\n\n") + + # C modules + if "bpy.props" not in EXCLUDE_MODULES: + fw(" bpy.props.rst\n\n") + + fw("==================\n") + fw("Standalone Modules\n") + fw("==================\n") + fw("\n") + fw(".. toctree::\n") + fw(" :maxdepth: 1\n\n") + + if "mathutils" not in EXCLUDE_MODULES: + fw(" mathutils.rst\n\n") + if "mathutils.geometry" not in EXCLUDE_MODULES: + fw(" mathutils.geometry.rst\n\n") + if "bgl" not in EXCLUDE_MODULES: + fw(" bgl.rst\n\n") + if "blf" not in EXCLUDE_MODULES: + fw(" blf.rst\n\n") + if "aud" not in EXCLUDE_MODULES: + fw(" aud.rst\n\n") + if "bpy_extras" not in EXCLUDE_MODULES: + fw(" bpy_extras.rst\n\n") + + # game engine + if "bge" not in EXCLUDE_MODULES: + fw("===================\n") + fw("Game Engine Modules\n") + fw("===================\n") + fw("\n") + fw(".. toctree::\n") + fw(" :maxdepth: 1\n\n") + fw(" bge.types.rst\n\n") + fw(" bge.logic.rst\n\n") + fw(" bge.render.rst\n\n") + fw(" bge.texture.rst\n\n") + fw(" bge.events.rst\n\n") + fw(" bge.constraints.rst\n\n") + + # rna generated change log + fw("========\n") + fw("API Info\n") + fw("========\n") + fw("\n") + fw(".. toctree::\n") + fw(" :maxdepth: 1\n\n") + fw(" change_log.rst\n\n") + + fw("\n") + fw("\n") + fw(".. note:: The Blender Python API has areas which are still in development.\n") + fw(" \n") + fw(" The following areas are subject to change.\n") + fw(" * operator behavior, names and arguments\n") + fw(" * mesh creation and editing functions\n") + fw(" \n") + fw(" These parts of the API are relatively stable and are unlikely to change significantly\n") + fw(" * data API, access to attributes of blender data such as mesh verts, material color, timeline frames and scene objects\n") + fw(" * user interface functions for defining buttons, creation of menus, headers, panels\n") + fw(" * render engine integration\n") + fw(" * modules: bgl, mathutils & game engine.\n") + fw("\n") + + file.close() + + # internal modules + if "bpy.ops" not in EXCLUDE_MODULES: + filepath = os.path.join(BASEPATH, "bpy.ops.rst") + file = open(filepath, "w") + fw = file.write + fw("Operators (bpy.ops)\n") + fw("===================\n\n") + write_example_ref("", fw, "bpy.ops") + fw(".. toctree::\n") + fw(" :glob:\n\n") + fw(" bpy.ops.*\n\n") + file.close() + + if "bpy.types" not in EXCLUDE_MODULES: + filepath = os.path.join(BASEPATH, "bpy.types.rst") + file = open(filepath, "w") + fw = file.write + fw("Types (bpy.types)\n") + fw("=================\n\n") + fw(".. toctree::\n") + fw(" :glob:\n\n") + fw(" bpy.types.*\n\n") + file.close() + + if "bpy.data" not in EXCLUDE_MODULES: + # not actually a module, only write this file so we + # can reference in the TOC + filepath = os.path.join(BASEPATH, "bpy.data.rst") + file = open(filepath, "w") + fw = file.write + fw("Data Access (bpy.data)\n") + fw("======================\n\n") + fw(".. module:: bpy\n") + fw("\n") + fw("This module is used for all blender/python access.\n") + fw("\n") + fw(".. data:: data\n") + fw("\n") + fw(" Access to blenders internal data\n") + fw("\n") + fw(" :type: :class:`bpy.types.BlendData`\n") + fw("\n") + fw(".. literalinclude:: ../examples/bpy.data.py\n") + file.close() + + EXAMPLE_SET_USED.add("bpy.data") + + module = None + + if "bpy.context" not in EXCLUDE_MODULES: + # one of a kind, context doc (uses ctypes to extract info!) + pycontext2sphinx(BASEPATH) + + # python modules + if "bpy.utils" not in EXCLUDE_MODULES: + from bpy import utils as module + pymodule2sphinx(BASEPATH, "bpy.utils", module, "Utilities") + + if "bpy.path" not in EXCLUDE_MODULES: + from bpy import path as module + pymodule2sphinx(BASEPATH, "bpy.path", module, "Path Utilities") + + if "bpy_extras" not in EXCLUDE_MODULES: + import bpy_extras as module + pymodule2sphinx(BASEPATH, "bpy_extras", module, "Extra Utilities") + + # C modules + if "bpy.app" not in EXCLUDE_MODULES: + from bpy import app as module + pymodule2sphinx(BASEPATH, "bpy.app", module, "Application Data") + + if "bpy.props" not in EXCLUDE_MODULES: + from bpy import props as module + pymodule2sphinx(BASEPATH, "bpy.props", module, "Property Definitions") + + if "mathutils" not in EXCLUDE_MODULES: + import mathutils as module + pymodule2sphinx(BASEPATH, "mathutils", module, "Math Types & Utilities") + + if "mathutils.geometry" not in EXCLUDE_MODULES: + import mathutils.geometry as module + pymodule2sphinx(BASEPATH, "mathutils.geometry", module, "Geometry Utilities") + + if "blf" not in EXCLUDE_MODULES: + import blf as module + pymodule2sphinx(BASEPATH, "blf", module, "Font Drawing") + + if "bgl" not in EXCLUDE_MODULES: + #import bgl as module + #pymodule2sphinx(BASEPATH, "bgl", module, "Blender OpenGl wrapper") + #del module + import shutil + shutil.copy2(os.path.join(BASEPATH, "..", "rst", "bgl.rst"), BASEPATH) + + if "aud" not in EXCLUDE_MODULES: + import aud as module + pymodule2sphinx(BASEPATH, "aud", module, "Audio System") + del module + + ## game engine + import shutil + # copy2 keeps time/date stamps + if "bge" not in EXCLUDE_MODULES: + shutil.copy2(os.path.join(BASEPATH, "..", "rst", "bge.types.rst"), BASEPATH) + shutil.copy2(os.path.join(BASEPATH, "..", "rst", "bge.logic.rst"), BASEPATH) + shutil.copy2(os.path.join(BASEPATH, "..", "rst", "bge.render.rst"), BASEPATH) + shutil.copy2(os.path.join(BASEPATH, "..", "rst", "bge.texture.rst"), BASEPATH) + shutil.copy2(os.path.join(BASEPATH, "..", "rst", "bge.events.rst"), BASEPATH) + shutil.copy2(os.path.join(BASEPATH, "..", "rst", "bge.constraints.rst"), BASEPATH) + + shutil.copy2(os.path.join(BASEPATH, "..", "rst", "change_log.rst"), BASEPATH) + + if not EXCLUDE_INFO_DOCS: + for info, info_desc in INFO_DOCS: + shutil.copy2(os.path.join(BASEPATH, "..", "rst", info), BASEPATH) + + if 0: + filepath = os.path.join(BASEPATH, "bpy.rst") + file = open(filepath, "w") + fw = file.write + + fw("\n") + + title = ":mod:`bpy` --- Blender Python Module" + + write_title(fw, title, "=") + + fw(".. module:: bpy.types\n\n") + file.close() + + # bpy.types and bpy.ops + pyrna2sphinx(BASEPATH) + + file.close() + + +def main(): + import shutil + + script_dir = os.path.dirname(__file__) + path_in = os.path.join(script_dir, "sphinx-in") + path_out = os.path.join(script_dir, "sphinx-out") + path_examples = os.path.join(script_dir, "examples") + # only for partial updates + path_in_tmp = path_in + "-tmp" + + if not os.path.exists(path_in): + os.mkdir(path_in) + + for f in os.listdir(path_examples): + if f.endswith(".py"): + EXAMPLE_SET.add(os.path.splitext(f)[0]) + + # only for full updates + if _BPY_FULL_REBUILD: + shutil.rmtree(path_in, True) + shutil.rmtree(path_out, True) + else: + # write here, then move + shutil.rmtree(path_in_tmp, True) + + rna2sphinx(path_in_tmp) + + if not _BPY_FULL_REBUILD: + import filecmp + + # now move changed files from 'path_in_tmp' --> 'path_in' + file_list_path_in = set(os.listdir(path_in)) + file_list_path_in_tmp = set(os.listdir(path_in_tmp)) + + # remove deprecated files that have been removed. + for f in sorted(file_list_path_in): + if f not in file_list_path_in_tmp: + print("\tdeprecated: %s" % f) + os.remove(os.path.join(path_in, f)) + + # freshen with new files. + for f in sorted(file_list_path_in_tmp): + f_from = os.path.join(path_in_tmp, f) + f_to = os.path.join(path_in, f) + + do_copy = True + if f in file_list_path_in: + if filecmp.cmp(f_from, f_to): + do_copy = False + + if do_copy: + print("\tupdating: %s" % f) + shutil.copy(f_from, f_to) + '''else: + print("\tkeeping: %s" % f) # eh, not that useful''' + + EXAMPLE_SET_UNUSED = EXAMPLE_SET - EXAMPLE_SET_USED + if EXAMPLE_SET_UNUSED: + print("\nUnused examples found in '%s'..." % path_examples) + for f in EXAMPLE_SET_UNUSED: + print(" %s.py" % f) + print(" %d total\n" % len(EXAMPLE_SET_UNUSED)) + + import sys + sys.exit() + +if __name__ == '__main__': + main() |