From 7f1a8a947d9629d956157464814541dbb764defb Mon Sep 17 00:00:00 2001 From: Campbell Barton Date: Fri, 22 Jan 2010 02:04:25 +0000 Subject: [PATCH] initial sphinx doc generation support for python and C modules. python modules bpy.app, bpy.utils are now included in docs. C defined python module bpy.props has its docstrings extracted and written directly into sphinx docs since the C methods cant be inspected. added docstrings to bpy.props and improved some in bpy.utils. will update online docs tomorrow. --- release/scripts/modules/bpy/app.py | 21 ++++ release/scripts/modules/bpy/utils.py | 54 +++++---- source/blender/python/intern/bpy_props.c | 60 +++++++-- source/blender/python/sphinx_doc_gen.py | 147 ++++++++++++++++++++--- 4 files changed, 234 insertions(+), 48 deletions(-) diff --git a/release/scripts/modules/bpy/app.py b/release/scripts/modules/bpy/app.py index da45ab8eb30..8fc92175d0e 100644 --- a/release/scripts/modules/bpy/app.py +++ b/release/scripts/modules/bpy/app.py @@ -18,6 +18,27 @@ # +""" +This module contains application values that remain unchanged during runtime. + +.. data:: version + + The Blender version as a tuple of 3 numbers. eg. (2, 50, 11) + + +.. data:: version_string + + The Blender version formatted as a string. + +.. data:: home + + The blender home directory, normally matching $HOME + +.. data:: binary_path + + The location of blenders executable, useful for utilities that spawn new instances. + +""" # constants import _bpy version = _bpy._VERSION diff --git a/release/scripts/modules/bpy/utils.py b/release/scripts/modules/bpy/utils.py index 1948a28a726..90ba9ba4c71 100644 --- a/release/scripts/modules/bpy/utils.py +++ b/release/scripts/modules/bpy/utils.py @@ -18,13 +18,18 @@ # -import bpy -import os +""" +This module contains utility functions spesific to blender but +not assosiated with blenders internal data. +""" + +import bpy as _bpy +import os as _os def expandpath(path): if path.startswith("//"): - return os.path.join(os.path.dirname(bpy.data.filename), path[2:]) + return _os.path.join(_os.path.dirname(_bpy.data.filename), path[2:]) return path @@ -47,21 +52,23 @@ _unclean_chars = ''.join([chr(i) for i in _unclean_chars]) def clean_name(name, replace="_"): - ''' + """ + Returns a name with characters replaced that may cause problems under various circumstances, such as writing to a file. All characters besides A-Z/a-z, 0-9 are replaced with "_" or the replace argumet if defined. - ''' + """ for ch in _unclean_chars: name = name.replace(ch, replace) return name def display_name(name): - ''' - Only capitalize all lowercase names, mixed case use them as is. - should work with filenames and module names. - ''' - name_base = os.path.splitext(name)[0] + """ + Creates a display string from name to be used menus and the user interface. + Capitalize the first letter in all lowercase names, mixed case names are kept as is. + Intended for use with filenames and module names. + """ + name_base = _os.path.splitext(name)[0] # string replacements name_base = name_base.replace("_colon_", ":") @@ -75,39 +82,44 @@ def display_name(name): # base scripts -_scripts = os.path.join(os.path.dirname(__file__), os.path.pardir, os.path.pardir) -_scripts = (os.path.normpath(_scripts), ) +_scripts = _os.path.join(_os.path.dirname(__file__), _os.path.pardir, _os.path.pardir) +_scripts = (_os.path.normpath(_scripts), ) def script_paths(*args): + """ + Returns a list of valid script paths from the home directory and user preferences. + + Accepts any number of string arguments which are joined to make a path. + """ scripts = list(_scripts) # add user scripts dir - user_script_path = bpy.context.user_preferences.filepaths.python_scripts_directory + user_script_path = _bpy.context.user_preferences.filepaths.python_scripts_directory if not user_script_path: # XXX - WIN32 needs checking, perhaps better call a blender internal function. - user_script_path = os.path.join(os.path.expanduser("~"), ".blender", "scripts") + user_script_path = _os.path.join(_os.path.expanduser("~"), ".blender", "scripts") - user_script_path = os.path.normpath(user_script_path) + user_script_path = _os.path.normpath(user_script_path) - if user_script_path not in scripts and os.path.isdir(user_script_path): + if user_script_path not in scripts and _os.path.isdir(user_script_path): scripts.append(user_script_path) if not args: return scripts - subdir = os.path.join(*args) + subdir = _os.path.join(*args) script_paths = [] for path in scripts: - path_subdir = os.path.join(path, subdir) - if os.path.isdir(path_subdir): + path_subdir = _os.path.join(path, subdir) + if _os.path.isdir(path_subdir): script_paths.append(path_subdir) return script_paths -_presets = os.path.join(_scripts[0], "presets") # FIXME - multiple paths +_presets = _os.path.join(_scripts[0], "presets") # FIXME - multiple paths def preset_paths(subdir): @@ -115,4 +127,4 @@ def preset_paths(subdir): Returns a list of paths for a spesific preset. ''' - return (os.path.join(_presets, subdir), ) + return (_os.path.join(_presets, subdir), ) diff --git a/source/blender/python/intern/bpy_props.c b/source/blender/python/intern/bpy_props.c index 938aaca4b9a..946e94c7064 100644 --- a/source/blender/python/intern/bpy_props.c +++ b/source/blender/python/intern/bpy_props.c @@ -46,6 +46,10 @@ static PyObject *bpy_prop_deferred_return(void *func, PyObject *kw) /* Function that sets RNA, NOTE - self is NULL when called from python, but being abused from C so we can pass the srna allong * This isnt incorrect since its a python object - but be careful */ +static char BPy_BoolProperty_doc[] = +".. function:: BoolProperty(name=\"\", description=\"\", default=False, hidden=False)\n" +"\n" +" Returns a new boolean property definition.."; PyObject *BPy_BoolProperty(PyObject *self, PyObject *args, PyObject *kw) { @@ -79,6 +83,10 @@ PyObject *BPy_BoolProperty(PyObject *self, PyObject *args, PyObject *kw) } } +static char BPy_IntProperty_doc[] = +".. function:: IntProperty(name=\"\", description=\"\", default=0, min=-sys.maxint, max=sys.maxint, soft_min=-sys.maxint, soft_max=sys.maxint, step=1, hidden=False)\n" +"\n" +" Returns a new int property definition."; PyObject *BPy_IntProperty(PyObject *self, PyObject *args, PyObject *kw) { StructRNA *srna; @@ -113,6 +121,10 @@ PyObject *BPy_IntProperty(PyObject *self, PyObject *args, PyObject *kw) } } +static char BPy_FloatProperty_doc[] = +".. function:: FloatProperty(name=\"\", description=\"\", default=0.0, min=sys.float_info.min, max=sys.float_info.max, soft_min=sys.float_info.min, soft_max=sys.float_info.max, step=3, precision=2, hidden=False)\n" +"\n" +" Returns a new float property definition."; PyObject *BPy_FloatProperty(PyObject *self, PyObject *args, PyObject *kw) { StructRNA *srna; @@ -147,6 +159,10 @@ PyObject *BPy_FloatProperty(PyObject *self, PyObject *args, PyObject *kw) } } +static char BPy_FloatVectorProperty_doc[] = +".. function:: FloatVectorProperty(name=\"\", description=\"\", default=(0.0, 0.0, 0.0), min=sys.float_info.min, max=sys.float_info.max, soft_min=sys.float_info.min, soft_max=sys.float_info.max, step=3, precision=2, hidden=False, size=3)\n" +"\n" +" Returns a new vector float property definition."; PyObject *BPy_FloatVectorProperty(PyObject *self, PyObject *args, PyObject *kw) { StructRNA *srna; @@ -213,6 +229,10 @@ PyObject *BPy_FloatVectorProperty(PyObject *self, PyObject *args, PyObject *kw) } } +static char BPy_StringProperty_doc[] = +".. function:: StringProperty(name=\"\", description=\"\", default=\"\", maxlen=0, hidden=False)\n" +"\n" +" Returns a new string property definition."; PyObject *BPy_StringProperty(PyObject *self, PyObject *args, PyObject *kw) { StructRNA *srna; @@ -291,6 +311,13 @@ static EnumPropertyItem *enum_items_from_py(PyObject *value, const char *def, in return items; } +static char BPy_EnumProperty_doc[] = +".. function:: EnumProperty(items, name=\"\", description=\"\", default=\"\", hidden=False)\n" +"\n" +" Returns a new enumerator property definition.\n" +"\n" +" :arg items: The items that make up this enumerator.\n" +" :type items: sequence of string triplets"; PyObject *BPy_EnumProperty(PyObject *self, PyObject *args, PyObject *kw) { StructRNA *srna; @@ -349,6 +376,13 @@ static StructRNA *pointer_type_from_py(PyObject *value) return srna; } +static char BPy_PointerProperty_doc[] = +".. function:: PointerProperty(items, type=\"\", description=\"\", default=\"\", hidden=False)\n" +"\n" +" Returns a new pointer property definition.\n" +"\n" +" :arg type: Dynamic type from :mod:`bpy.types`.\n" +" :type type: class"; PyObject *BPy_PointerProperty(PyObject *self, PyObject *args, PyObject *kw) { StructRNA *srna; @@ -388,6 +422,13 @@ PyObject *BPy_PointerProperty(PyObject *self, PyObject *args, PyObject *kw) return NULL; } +static char BPy_CollectionProperty_doc[] = +".. function:: CollectionProperty(items, type=\"\", description=\"\", default=\"\", hidden=False)\n" +"\n" +" Returns a new collection property definition.\n" +"\n" +" :arg type: Dynamic type from :mod:`bpy.types`.\n" +" :type type: class"; PyObject *BPy_CollectionProperty(PyObject *self, PyObject *args, PyObject *kw) { StructRNA *srna; @@ -428,21 +469,22 @@ PyObject *BPy_CollectionProperty(PyObject *self, PyObject *args, PyObject *kw) } static struct PyMethodDef props_methods[] = { - {"BoolProperty", (PyCFunction)BPy_BoolProperty, METH_VARARGS|METH_KEYWORDS, ""}, - {"IntProperty", (PyCFunction)BPy_IntProperty, METH_VARARGS|METH_KEYWORDS, ""}, - {"FloatProperty", (PyCFunction)BPy_FloatProperty, METH_VARARGS|METH_KEYWORDS, ""}, - {"FloatVectorProperty", (PyCFunction)BPy_FloatVectorProperty, METH_VARARGS|METH_KEYWORDS, ""}, - {"StringProperty", (PyCFunction)BPy_StringProperty, METH_VARARGS|METH_KEYWORDS, ""}, - {"EnumProperty", (PyCFunction)BPy_EnumProperty, METH_VARARGS|METH_KEYWORDS, ""}, - {"PointerProperty", (PyCFunction)BPy_PointerProperty, METH_VARARGS|METH_KEYWORDS, ""}, - {"CollectionProperty", (PyCFunction)BPy_CollectionProperty, METH_VARARGS|METH_KEYWORDS, ""}, + {"BoolProperty", (PyCFunction)BPy_BoolProperty, METH_VARARGS|METH_KEYWORDS, BPy_BoolProperty_doc}, + {"IntProperty", (PyCFunction)BPy_IntProperty, METH_VARARGS|METH_KEYWORDS, BPy_IntProperty_doc}, + {"FloatProperty", (PyCFunction)BPy_FloatProperty, METH_VARARGS|METH_KEYWORDS, BPy_FloatProperty_doc}, + {"FloatVectorProperty", (PyCFunction)BPy_FloatVectorProperty, METH_VARARGS|METH_KEYWORDS, BPy_FloatVectorProperty_doc}, + {"StringProperty", (PyCFunction)BPy_StringProperty, METH_VARARGS|METH_KEYWORDS, BPy_StringProperty_doc}, + {"EnumProperty", (PyCFunction)BPy_EnumProperty, METH_VARARGS|METH_KEYWORDS, BPy_EnumProperty_doc}, + {"PointerProperty", (PyCFunction)BPy_PointerProperty, METH_VARARGS|METH_KEYWORDS, BPy_PointerProperty_doc}, + {"CollectionProperty", (PyCFunction)BPy_CollectionProperty, METH_VARARGS|METH_KEYWORDS, BPy_CollectionProperty_doc}, {NULL, NULL, 0, NULL} }; static struct PyModuleDef props_module = { PyModuleDef_HEAD_INIT, "bpy.props", - "", + "This module defines properties to extend blenders internal data, the result of these functions" + " is used to assign properties to classes registered with blender and can't be used directly.", -1,/* multiple "initialization" just copies the module dict. */ props_methods, NULL, NULL, NULL, NULL diff --git a/source/blender/python/sphinx_doc_gen.py b/source/blender/python/sphinx_doc_gen.py index ec17139b843..a882e145aec 100644 --- a/source/blender/python/sphinx_doc_gen.py +++ b/source/blender/python/sphinx_doc_gen.py @@ -49,6 +49,114 @@ def write_indented_lines(ident, fn, text): for l in text.split("\n"): fn(ident + l.strip() + "\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__: + for l in py_func.__doc__.split("\n"): + fw(ident + l + "\n") + 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 + + filepath = os.path.join(BASEPATH, module_name + ".rst") + + file = open(filepath, "w") + print(filepath) + print(filepath) + 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") + + for attribute in dir(module): + if not attribute.startswith("_"): + value = getattr(module, attribute) + + value_type = type(value) + print(attribute, value_type) + 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) + + # TODO, more types... + + file.close() + + def rna2sphinx(BASEPATH): structs, funcs, ops, props = rna_info.BuildRNAInfo() @@ -80,8 +188,27 @@ def rna2sphinx(BASEPATH): fw(" :glob:\n\n") fw(" bpy.ops.*\n\n") fw(" bpy.types.*\n\n") + + # py modules + fw(" bpy.utils\n\n") + fw(" bpy.app\n\n") + + # C modules + fw(" bpy.props\n\n") + file.close() + # python modules + from bpy import utils as module + pymodule2sphinx(BASEPATH, "bpy.utils", module, "Blender Python Utilities") + from bpy import app as module + pymodule2sphinx(BASEPATH, "bpy.app", module, "Blender Python Application Constants") + + from bpy import props as module + pymodule2sphinx(BASEPATH, "bpy.props", module, "Blender Python Property Definitions") + del module + + if 0: filepath = os.path.join(BASEPATH, "bpy.rst") file = open(filepath, "w") @@ -167,10 +294,7 @@ def rna2sphinx(BASEPATH): py_properties = struct.get_py_properties() py_prop = None for identifier, py_prop in py_properties: - fw(" .. attribute:: %s\n\n" % identifier) - write_indented_lines(" ", fw, py_prop.__doc__) - if py_prop.fset is None: - fw(" (readonly)\n\n") + pyprop2sphinx(" ", fw, identifier, py_prop) del py_properties, py_prop for func in struct.functions: @@ -201,20 +325,7 @@ def rna2sphinx(BASEPATH): py_func = None for identifier, py_func in py_funcs: - 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(" .. %s:: %s%s\n\n" % (func_type, identifier, arg_str)) - if py_func.__doc__: - write_indented_lines(" ", fw, py_func.__doc__) - fw("\n") + pyfunc2sphinx(" ", fw, identifier, py_func, is_class=True) del py_funcs, py_func if struct.references: