1 # ##### BEGIN GPL LICENSE BLOCK #####
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.
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.
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.
17 # Contributor(s): Campbell Barton
19 # ##### END GPL LICENSE BLOCK #####
27 Run this script from Blender's root path once you have compiled Blender
29 ./blender.bin --background -noaudio --python doc/python_api/sphinx_doc_gen.py
31 This will generate python files in doc/python_api/sphinx-in/
32 providing ./blender.bin is or links to the blender executable
34 To choose sphinx-in directory:
35 ./blender.bin --background --python doc/python_api/sphinx_doc_gen.py -- --output ../python_api
38 ./blender.bin --background --python doc/python_api/sphinx_doc_gen.py -- --partial bmesh.*
41 Sphinx: HTML generation
42 -----------------------
43 After you have built doc/python_api/sphinx-in (see above),
44 generate html docs by running:
47 sphinx-build sphinx-in sphinx-out
49 This requires sphinx 1.0.7 to be installed.
52 Sphinx: PDF generation
53 ----------------------
54 After you have built doc/python_api/sphinx-in (see above),
55 generate the pdf doc by running:
57 sphinx-build -b latex doc/python_api/sphinx-in doc/python_api/sphinx-out
58 cd doc/python_api/sphinx-out
64 import bpy # Blender module
66 print("\nERROR: this script must run from inside Blender")
67 print(SCRIPT_HELP_MSG)
71 import rna_info # Blender module
74 def rna_info_BuildRNAInfo_cache():
75 if rna_info_BuildRNAInfo_cache.ret is None:
76 rna_info_BuildRNAInfo_cache.ret = rna_info.BuildRNAInfo()
77 return rna_info_BuildRNAInfo_cache.ret
78 rna_info_BuildRNAInfo_cache.ret = None
79 # --- end rna_info cache
81 # import rpdb2; rpdb2.start_embedded_debugger('test')
88 from platform import platform
89 PLATFORM = platform().split('-')[0].lower() # 'linux', 'darwin', 'windows'
91 SCRIPT_DIR = os.path.abspath(os.path.dirname(__file__))
96 Parse the args passed to Blender after "--", ignored by Blender
100 # When --help is given, print the usage text
101 parser = argparse.ArgumentParser(
102 formatter_class=argparse.RawTextHelpFormatter,
103 usage=SCRIPT_HELP_MSG
107 parser.add_argument("-p", "--partial",
111 help="Use a wildcard to only build specific module(s)\n"
112 "Example: --partial bmesh*\n",
115 parser.add_argument("-f", "--fullrebuild",
119 help="Rewrite all rst files in sphinx-in/ "
123 parser.add_argument("-b", "--bpy",
127 help="Write the rst file of the bpy module "
131 parser.add_argument("-o", "--output",
135 help="Path of the API docs (default=<script dir>)",
138 parser.add_argument("-T", "--sphinx-theme",
142 help="Sphinx theme (default='classic'), "
143 "see: http://sphinx-doc.org/theming.html",
146 parser.add_argument("-N", "--sphinx-named-output",
147 dest="sphinx_named_output",
150 help="Add the theme name to the html dir name.\n"
151 "Example: \"sphinx-out_haiku\" (default=False)",
154 parser.add_argument("-B", "--sphinx-build",
158 help="Build the html docs by running:\n"
159 "sphinx-build SPHINX_IN SPHINX_OUT\n"
160 "(default=False; does not depend on -P)",
163 parser.add_argument("-P", "--sphinx-build-pdf",
164 dest="sphinx_build_pdf",
167 help="Build the pdf by running:\n"
168 "sphinx-build -b latex SPHINX_IN SPHINX_OUT_PDF\n"
169 "(default=False; does not depend on -B)",
172 parser.add_argument("-R", "--pack-reference",
173 dest="pack_reference",
176 help="Pack all necessary files in the deployed dir.\n"
177 "(default=False; use with -B and -P)",
180 parser.add_argument("-l", "--log",
184 help="Log the output of the api dump and sphinx|latex "
185 "warnings and errors (default=False).\n"
186 "If given, save logs in:\n"
187 "* OUTPUT_DIR/.bpy.log\n"
188 "* OUTPUT_DIR/.sphinx-build.log\n"
189 "* OUTPUT_DIR/.sphinx-build_pdf.log\n"
190 "* OUTPUT_DIR/.latex_make.log",
193 # parse only the args passed after '--'
196 argv = sys.argv[sys.argv.index("--") + 1:] # get all args after "--"
198 return parser.parse_args(argv)
203 # ----------------------------------BPY-----------------------------------------
205 BPY_LOGGER = logging.getLogger('bpy')
206 BPY_LOGGER.setLevel(logging.DEBUG)
210 rm -rf /b/doc/python_api/sphinx-* && \
211 ./blender.bin -b -noaudio --factory-startup -P doc/python_api/sphinx_doc_gen.py && \
212 sphinx-build doc/python_api/sphinx-in doc/python_api/sphinx-out
216 ./blender.bin -b -noaudio --factory-startup -P doc/python_api/sphinx_doc_gen.py -- -f -B
219 # Switch for quick testing so doc-builds don't take so long
222 FILTER_BPY_OPS = None
223 FILTER_BPY_TYPES = None
224 EXCLUDE_INFO_DOCS = False
228 # can manually edit this too:
229 # FILTER_BPY_OPS = ("import.scene", ) # allow
230 # FILTER_BPY_TYPES = ("bpy_struct", "Operator", "ID") # allow
231 EXCLUDE_INFO_DOCS = True
251 "bpy.app.translations",
254 "bpy.ops", # supports filtering
257 "bpy.types", # supports filtering
259 "bpy.utils.previews",
266 "mathutils.geometry",
267 "mathutils.interpolate",
271 "freestyle.chainingiterators",
272 "freestyle.functions",
273 "freestyle.predicates",
282 # TODO, support bpy.ops and bpy.types filtering
285 EXCLUDE_MODULES = [m for m in EXCLUDE_MODULES if not fnmatch.fnmatchcase(m, ARGS.partial)]
287 # special support for bpy.types.XXX
288 FILTER_BPY_OPS = tuple([m[8:] for m in ARGS.partial.split(":") if m.startswith("bpy.ops.")])
290 EXCLUDE_MODULES.remove("bpy.ops")
292 FILTER_BPY_TYPES = tuple([m[10:] for m in ARGS.partial.split(":") if m.startswith("bpy.types.")])
294 EXCLUDE_MODULES.remove("bpy.types")
296 print(FILTER_BPY_TYPES)
298 EXCLUDE_INFO_DOCS = (not fnmatch.fnmatchcase("info", ARGS.partial))
304 "Partial Doc Build, Skipping: %s\n" %
305 "\n ".join(sorted(EXCLUDE_MODULES)))
314 BPY_LOGGER.debug("Warning: Built without 'aud' module, docs incomplete...")
315 EXCLUDE_MODULES.append("aud")
318 __import__("freestyle")
320 BPY_LOGGER.debug("Warning: Built without 'freestyle' module, docs incomplete...")
321 EXCLUDE_MODULES.extend([
323 "freestyle.chainingiterators",
324 "freestyle.functions",
325 "freestyle.predicates",
331 # Source files we use, and need to copy to the OUTPUT_DIR
332 # to have working out-of-source builds.
333 # Note that ".." is replaced by "__" in the RST files,
334 # to avoid having to match Blender's source tree.
335 EXTRA_SOURCE_FILES = (
336 "../../../release/scripts/templates_py/bmesh_simple.py",
337 "../../../release/scripts/templates_py/operator_simple.py",
338 "../../../release/scripts/templates_py/ui_panel_simple.py",
339 "../../../release/scripts/templates_py/ui_previews_custom_icon.py",
340 "../examples/bge.constraints.py",
341 "../examples/bge.texture.1.py",
342 "../examples/bge.texture.2.py",
343 "../examples/bge.texture.py",
344 "../examples/bmesh.ops.1.py",
345 "../examples/bpy.app.translations.py",
350 EXAMPLES_DIR = os.path.abspath(os.path.join(SCRIPT_DIR, "examples"))
352 for f in os.listdir(EXAMPLES_DIR):
353 if f.endswith(".py"):
354 EXAMPLE_SET.add(os.path.splitext(f)[0])
355 EXAMPLE_SET_USED = set()
358 RST_DIR = os.path.abspath(os.path.join(SCRIPT_DIR, "rst"))
360 # extra info, not api reference docs
361 # stored in ./rst/info_*
363 ("info_quickstart.rst",
364 "Blender/Python Quickstart: new to Blender/scripting and want to get your feet wet?"),
365 ("info_overview.rst",
366 "Blender/Python API Overview: a more complete explanation of Python integration"),
367 ("info_tutorial_addon.rst",
368 "Blender/Python Addon Tutorial: a step by step guide on how to write an addon from scratch"),
369 ("info_api_reference.rst",
370 "Blender/Python API Reference Usage: examples of how to use the API reference docs"),
371 ("info_best_practice.rst",
372 "Best Practice: Conventions to follow for writing good scripts"),
373 ("info_tips_and_tricks.rst",
374 "Tips and Tricks: Hints to help you while writing scripts for Blender"),
376 "Gotcha's: some of the problems you may come up against when writing scripts"),
379 # only support for properties atm.
381 # XXX messes up PDF!, really a bug but for now just workaround.
382 "UserPreferencesSystem": {"language", }
387 ("Base Mesh Type", '-'),
389 ("Mesh Elements", '-'),
394 ("Sequence Accessors", '-'),
401 ("Selection History", '-'),
404 ("Custom-Data Layer Access", '-'),
411 ("Custom-Data Layer Types", '-'),
417 # --------------------configure compile time options----------------------------
419 # -------------------------------BLENDER----------------------------------------
421 blender_version_strings = [str(v) for v in bpy.app.version]
423 # converting bytes to strings, due to #30154
424 BLENDER_REVISION = str(bpy.app.build_hash, 'utf_8')
425 BLENDER_DATE = str(bpy.app.build_date, 'utf_8')
427 BLENDER_VERSION_DOTS = ".".join(blender_version_strings) # '2.62.1'
428 if BLENDER_REVISION != "Unknown":
429 BLENDER_VERSION_DOTS += " " + BLENDER_REVISION # '2.62.1 SHA1'
431 BLENDER_VERSION_PATH = "_".join(blender_version_strings) # '2_62_1'
432 if bpy.app.version_cycle == "release":
433 BLENDER_VERSION_PATH = "%s%s_release" % ("_".join(blender_version_strings[:2]),
434 bpy.app.version_char) # '2_62_release'
436 # --------------------------DOWNLOADABLE FILES----------------------------------
438 REFERENCE_NAME = "blender_python_reference_%s" % BLENDER_VERSION_PATH
439 REFERENCE_PATH = os.path.join(ARGS.output_dir, REFERENCE_NAME)
440 BLENDER_PDF_FILENAME = "%s.pdf" % REFERENCE_NAME
441 BLENDER_ZIP_FILENAME = "%s.zip" % REFERENCE_NAME
443 # -------------------------------SPHINX-----------------------------------------
445 if ARGS.sphinx_theme == "blender-org":
446 SPHINX_THEME_DIR = os.path.join(ARGS.output_dir, ARGS.sphinx_theme)
447 SPHINX_THEME_SVN_DIR = os.path.join(SCRIPT_DIR, ARGS.sphinx_theme)
449 SPHINX_IN = os.path.join(ARGS.output_dir, "sphinx-in")
450 SPHINX_IN_TMP = SPHINX_IN + "-tmp"
451 SPHINX_OUT = os.path.join(ARGS.output_dir, "sphinx-out")
452 if ARGS.sphinx_named_output:
453 SPHINX_OUT += "_%s" % ARGS.sphinx_theme
456 if ARGS.sphinx_build:
457 SPHINX_BUILD = ["sphinx-build", SPHINX_IN, SPHINX_OUT]
460 SPHINX_BUILD_LOG = os.path.join(ARGS.output_dir, ".sphinx-build.log")
461 SPHINX_BUILD = ["sphinx-build",
462 "-w", SPHINX_BUILD_LOG,
463 SPHINX_IN, SPHINX_OUT]
466 if ARGS.sphinx_build_pdf:
467 SPHINX_OUT_PDF = os.path.join(ARGS.output_dir, "sphinx-out_pdf")
468 SPHINX_BUILD_PDF = ["sphinx-build",
470 SPHINX_IN, SPHINX_OUT_PDF]
471 SPHINX_MAKE_PDF = ["make", "-C", SPHINX_OUT_PDF]
472 SPHINX_MAKE_PDF_STDOUT = None
475 SPHINX_BUILD_PDF_LOG = os.path.join(ARGS.output_dir, ".sphinx-build_pdf.log")
476 SPHINX_BUILD_PDF = ["sphinx-build", "-b", "latex",
477 "-w", SPHINX_BUILD_PDF_LOG,
478 SPHINX_IN, SPHINX_OUT_PDF]
480 sphinx_make_pdf_log = os.path.join(ARGS.output_dir, ".latex_make.log")
481 SPHINX_MAKE_PDF_STDOUT = open(sphinx_make_pdf_log, "w", encoding="utf-8")
483 # --------------------------------API DUMP--------------------------------------
485 # lame, python wont give some access
486 ClassMethodDescriptorType = type(dict.__dict__['fromkeys'])
487 MethodDescriptorType = type(dict.get)
488 GetSetDescriptorType = type(int.real)
489 StaticMethodType = type(staticmethod(lambda: None))
491 MemberDescriptorType,
496 _BPY_STRUCT_FAKE = "bpy_struct"
497 _BPY_PROP_COLLECTION_FAKE = "bpy_prop_collection"
499 if _BPY_PROP_COLLECTION_FAKE:
500 _BPY_PROP_COLLECTION_ID = ":class:`%s`" % _BPY_PROP_COLLECTION_FAKE
502 _BPY_PROP_COLLECTION_ID = "collection"
505 def escape_rst(text):
506 """ Escape plain text which may contain characters used by RST.
508 return text.translate(escape_rst.trans)
509 escape_rst.trans = str.maketrans({
517 def is_struct_seq(value):
518 return isinstance(value, tuple) and type(tuple) != tuple and hasattr(value, "n_fields")
521 def undocumented_message(module_name, type_name, identifier):
522 return "Undocumented"
525 if str(type_name).startswith('<module'):
526 preloadtitle = '%s.%s' % (module_name, identifier)
528 preloadtitle = '%s.%s.%s' % (module_name, type_name, identifier)
529 message = ("Undocumented (`contribute "
530 "<http://wiki.blender.org/index.php/"
531 "Dev:2.5/Py/API/Generating_API_Reference/Contribute"
534 "&preload=Dev:2.5/Py/API/Generating_API_Reference/Contribute/Howto-message"
535 "&preloadtitle=%s>`_)\n\n" % preloadtitle)
542 Converts values to strings for the range directive.
543 (unused function it seems)
549 elif type(val) == float:
555 def example_extract_docstring(filepath):
556 file = open(filepath, "r", encoding="utf-8")
557 line = file.readline()
560 if line.startswith('"""'): # assume nothing here
566 for line in file.readlines():
568 if line.startswith('"""'):
571 text.append(line.rstrip())
575 return "\n".join(text), line_no
578 def title_string(text, heading_char, double=False):
579 filler = len(text) * heading_char
582 return "%s\n%s\n%s\n\n" % (filler, text, filler)
584 return "%s\n%s\n\n" % (text, filler)
587 def write_example_ref(ident, fw, example_id, ext="py"):
588 if example_id in EXAMPLE_SET:
590 # extract the comment
591 filepath = os.path.join("..", "examples", "%s.%s" % (example_id, ext))
592 filepath_full = os.path.join(os.path.dirname(fw.__self__.name), filepath)
594 text, line_no = example_extract_docstring(filepath_full)
596 for line in text.split("\n"):
597 fw("%s\n" % (ident + line).rstrip())
600 fw("%s.. literalinclude:: %s\n" % (ident, filepath))
602 fw("%s :lines: %d-\n" % (ident, line_no))
604 EXAMPLE_SET_USED.add(example_id)
607 BPY_LOGGER.debug("\tskipping example: " + example_id)
609 # Support for numbered files bpy.types.Operator -> bpy.types.Operator.1.py
612 example_id_num = "%s.%d" % (example_id, i)
613 if example_id_num in EXAMPLE_SET:
614 write_example_ref(ident, fw, example_id_num, ext)
620 def write_indented_lines(ident, fn, text, strip=True):
622 Apply same indentation to all lines in a multilines text.
627 lines = text.split("\n")
629 # strip empty lines from the start/end
630 while lines and not lines[0].strip():
632 while lines and not lines[-1].strip():
636 # set indentation to <indent>
640 ident_strip = min(ident_strip, len(l) - len(l.lstrip()))
642 fn(ident + l[ident_strip:] + "\n")
644 # add <indent> number of blanks to the current indentation
649 def pymethod2sphinx(ident, fw, identifier, py_func):
651 class method to sphinx
653 arg_str = inspect.formatargspec(*inspect.getargspec(py_func))
654 if arg_str.startswith("(self, "):
655 arg_str = "(" + arg_str[7:]
657 elif arg_str.startswith("(cls, "):
658 arg_str = "(" + arg_str[6:]
659 func_type = "classmethod"
661 func_type = "staticmethod"
663 fw(ident + ".. %s:: %s%s\n\n" % (func_type, identifier, arg_str))
665 write_indented_lines(ident + " ", fw, py_func.__doc__)
669 def pyfunc2sphinx(ident, fw, module_name, type_name, identifier, py_func, is_class=True):
671 function or class method to sphinx
674 if type(py_func) == MethodType:
677 arg_str = inspect.formatargspec(*inspect.getfullargspec(py_func))
680 func_type = "function"
682 # ther rest are class methods
683 elif arg_str.startswith("(self, ") or arg_str == "(self)":
684 arg_str = "()" if (arg_str == "(self)") else ("(" + arg_str[7:])
686 elif arg_str.startswith("(cls, "):
687 arg_str = "()" if (arg_str == "(cls)") else ("(" + arg_str[6:])
688 func_type = "classmethod"
690 func_type = "staticmethod"
692 doc = py_func.__doc__
693 if (not doc) or (not doc.startswith(".. %s:: " % func_type)):
694 fw(ident + ".. %s:: %s%s\n\n" % (func_type, identifier, arg_str))
695 ident_temp = ident + " "
700 write_indented_lines(ident_temp, fw, doc)
705 write_example_ref(ident + " ", fw, module_name + "." + type_name + "." + identifier)
707 write_example_ref(ident + " ", fw, module_name + "." + identifier)
710 def py_descr2sphinx(ident, fw, descr, module_name, type_name, identifier):
711 if identifier.startswith("_"):
716 doc = undocumented_message(module_name, type_name, identifier)
718 if type(descr) == GetSetDescriptorType:
719 fw(ident + ".. attribute:: %s\n\n" % identifier)
720 write_indented_lines(ident + " ", fw, doc, False)
722 elif type(descr) == MemberDescriptorType: # same as above but use 'data'
723 fw(ident + ".. data:: %s\n\n" % identifier)
724 write_indented_lines(ident + " ", fw, doc, False)
726 elif type(descr) in {MethodDescriptorType, ClassMethodDescriptorType}:
727 write_indented_lines(ident, fw, doc, False)
730 raise TypeError("type was not GetSetDescriptorType, MethodDescriptorType or ClassMethodDescriptorType")
732 write_example_ref(ident + " ", fw, module_name + "." + type_name + "." + identifier)
736 def py_c_func2sphinx(ident, fw, module_name, type_name, identifier, py_func, is_class=True):
738 c defined function to sphinx.
741 # dump the docstring, assume its formatted correctly
743 write_indented_lines(ident, fw, py_func.__doc__, False)
746 fw(ident + ".. function:: %s()\n\n" % identifier)
747 fw(ident + " " + undocumented_message(module_name, type_name, identifier))
750 write_example_ref(ident + " ", fw, module_name + "." + type_name + "." + identifier)
752 write_example_ref(ident + " ", fw, module_name + "." + identifier)
757 def pyprop2sphinx(ident, fw, identifier, py_prop):
759 Python property to sphinx
761 # readonly properties use "data" directive, variables use "attribute" directive
762 if py_prop.fset is None:
763 fw(ident + ".. data:: %s\n\n" % identifier)
765 fw(ident + ".. attribute:: %s\n\n" % identifier)
766 write_indented_lines(ident + " ", fw, py_prop.__doc__)
767 if py_prop.fset is None:
768 fw(ident + " (readonly)\n\n")
773 def pymodule2sphinx(basepath, module_name, module, title):
775 attribute_set = set()
776 filepath = os.path.join(basepath, module_name + ".rst")
778 module_all = getattr(module, "__all__", None)
779 module_dir = sorted(dir(module))
782 module_dir = module_all
784 # TODO - currently only used for classes
786 module_grouping = MODULE_GROUPING.get(module_name)
788 def module_grouping_index(name):
789 if module_grouping is not None:
791 return module_grouping.index(name)
796 def module_grouping_heading(name):
797 if module_grouping is not None:
798 i = module_grouping_index(name) - 1
799 if i >= 0 and type(module_grouping[i]) == tuple:
800 return module_grouping[i]
803 def module_grouping_sort_key(name):
804 return module_grouping_index(name)
805 # done grouping support
807 file = open(filepath, "w", encoding="utf-8")
811 fw(title_string("%s (%s)" % (title, module_name), "="))
813 fw(".. module:: %s\n\n" % module_name)
816 # Note, may contain sphinx syntax, dont mangle!
817 fw(module.__doc__.strip())
820 write_example_ref("", fw, module_name)
823 # we could also scan files but this ensures __all__ is used correctly
824 if module_all is not None:
828 for submod_name in module_all:
830 exec_str = "from %s import %s as submod" % (module.__name__, submod_name)
831 exec(exec_str, ns, ns)
832 submod = ns["submod"]
833 if type(submod) == types.ModuleType:
834 submod_ls.append((submod_name, submod))
841 fw(" :maxdepth: 1\n\n")
843 for submod_name, submod in submod_ls:
844 submod_name_full = "%s.%s" % (module_name, submod_name)
845 fw(" %s.rst\n\n" % submod_name_full)
847 pymodule2sphinx(basepath, submod_name_full, submod, "%s submodule" % module_name)
849 # done writing submodules!
851 # write members of the module
852 # only tested with PyStructs which are not exactly modules
853 for key, descr in sorted(type(module).__dict__.items()):
854 if key.startswith("__"):
856 # naughty, we also add getset's into PyStructs, this is not typical py but also not incorrect.
858 # type_name is only used for examples and messages
859 # "<class 'bpy.app.handlers'>" --> bpy.app.handlers
860 type_name = str(type(module)).strip("<>").split(" ", 1)[-1][1:-1]
861 if type(descr) == types.GetSetDescriptorType:
862 py_descr2sphinx("", fw, descr, module_name, type_name, key)
863 attribute_set.add(key)
865 for key, descr in sorted(type(module).__dict__.items()):
866 if key.startswith("__"):
869 if type(descr) == MemberDescriptorType:
871 value = getattr(module, key, None)
873 value_type = type(value)
874 descr_sorted.append((key, descr, value, type(value)))
875 # sort by the valye type
876 descr_sorted.sort(key=lambda descr_data: str(descr_data[3]))
877 for key, descr, value, value_type in descr_sorted:
879 # must be documented as a submodule
880 if is_struct_seq(value):
883 type_name = value_type.__name__
884 py_descr2sphinx("", fw, descr, module_name, type_name, key)
886 attribute_set.add(key)
888 del key, descr, descr_sorted
893 # use this list so we can sort by type
894 module_dir_value_type = []
896 for attribute in module_dir:
897 if attribute.startswith("_"):
900 if attribute in attribute_set:
903 if attribute.startswith("n_"): # annoying exception, needed for bpy.app
906 # workaround for bpy.app documenting .index() and .count()
907 if isinstance(module, tuple) and hasattr(tuple, attribute):
910 value = getattr(module, attribute)
912 module_dir_value_type.append((attribute, value, type(value)))
914 # sort by str of each type
915 # this way lists, functions etc are grouped.
916 module_dir_value_type.sort(key=lambda triple: str(triple[2]))
918 for attribute, value, value_type in module_dir_value_type:
919 if value_type == FunctionType:
920 pyfunc2sphinx("", fw, module_name, None, attribute, value, is_class=False)
921 # both the same at the moment but to be future proof
922 elif value_type in {types.BuiltinMethodType, types.BuiltinFunctionType}:
923 # note: can't get args from these, so dump the string as is
924 # this means any module used like this must have fully formatted docstrings.
925 py_c_func2sphinx("", fw, module_name, None, attribute, value, is_class=False)
926 elif value_type == type:
927 classes.append((attribute, value))
928 elif issubclass(value_type, types.ModuleType):
929 submodules.append((attribute, value))
930 elif issubclass(value_type, (bool, int, float, str, tuple)):
931 # constant, not much fun we can do here except to list it.
932 # TODO, figure out some way to document these!
933 fw(".. data:: %s\n\n" % attribute)
934 write_indented_lines(" ", fw, "constant value %s" % repr(value), False)
937 BPY_LOGGER.debug("\tnot documenting %s.%s of %r type" % (module_name, attribute, value_type.__name__))
940 attribute_set.add(attribute)
941 # TODO, more types...
942 del module_dir_value_type
944 # TODO, bpy_extras does this already, mathutils not.
953 for attribute, submod in submodules:
954 fw("* :mod:`%s.%s`\n" % (module_name, attribute))
958 if module_grouping is not None:
959 classes.sort(key=lambda pair: module_grouping_sort_key(pair[0]))
961 # write collected classes now
962 for (type_name, value) in classes:
964 if module_grouping is not None:
965 heading, heading_char = module_grouping_heading(type_name)
967 fw(title_string(heading, heading_char))
969 # May need to be its own function
971 if value.__doc__.startswith(".. class::"):
974 fw(".. class:: %s\n\n" % type_name)
975 write_indented_lines(" ", fw, value.__doc__, True)
977 fw(".. class:: %s\n\n" % type_name)
980 write_example_ref(" ", fw, module_name + "." + type_name)
982 descr_items = [(key, descr) for key, descr in sorted(value.__dict__.items()) if not key.startswith("_")]
984 for key, descr in descr_items:
985 if type(descr) == ClassMethodDescriptorType:
986 py_descr2sphinx(" ", fw, descr, module_name, type_name, key)
988 # needed for pure Python classes
989 for key, descr in descr_items:
990 if type(descr) == FunctionType:
991 pyfunc2sphinx(" ", fw, module_name, type_name, key, descr, is_class=True)
993 for key, descr in descr_items:
994 if type(descr) == MethodDescriptorType:
995 py_descr2sphinx(" ", fw, descr, module_name, type_name, key)
997 for key, descr in descr_items:
998 if type(descr) == GetSetDescriptorType:
999 py_descr2sphinx(" ", fw, descr, module_name, type_name, key)
1001 for key, descr in descr_items:
1002 if type(descr) == StaticMethodType:
1003 descr = getattr(value, key)
1004 write_indented_lines(" ", fw, descr.__doc__ or "Undocumented", False)
1011 # Changes in Blender will force errors here
1012 context_type_map = {
1013 "active_base": ("ObjectBase", False),
1014 "active_bone": ("EditBone", False),
1015 "active_gpencil_frame": ("GreasePencilLayer", True),
1016 "active_gpencil_layer": ("GPencilLayer", True),
1017 "active_node": ("Node", False),
1018 "active_object": ("Object", False),
1019 "active_operator": ("Operator", False),
1020 "active_pose_bone": ("PoseBone", False),
1021 "armature": ("Armature", False),
1022 "bone": ("Bone", False),
1023 "brush": ("Brush", False),
1024 "camera": ("Camera", False),
1025 "cloth": ("ClothModifier", False),
1026 "collision": ("CollisionModifier", False),
1027 "curve": ("Curve", False),
1028 "dynamic_paint": ("DynamicPaintModifier", False),
1029 "edit_bone": ("EditBone", False),
1030 "edit_image": ("Image", False),
1031 "edit_mask": ("Mask", False),
1032 "edit_movieclip": ("MovieClip", False),
1033 "edit_object": ("Object", False),
1034 "edit_text": ("Text", False),
1035 "editable_bases": ("ObjectBase", True),
1036 "editable_bones": ("EditBone", True),
1037 "editable_gpencil_layers": ("GPencilLayer", True),
1038 "editable_gpencil_strokes": ("GPencilStroke", True),
1039 "editable_objects": ("Object", True),
1040 "fluid": ("FluidSimulationModifier", False),
1041 "gpencil_data": ("GreasePencel", False),
1042 "gpencil_data_owner": ("ID", False),
1043 "image_paint_object": ("Object", False),
1044 "lamp": ("Lamp", False),
1045 "lattice": ("Lattice", False),
1046 "line_style": ("FreestyleLineStyle", False),
1047 "material": ("Material", False),
1048 "material_slot": ("MaterialSlot", False),
1049 "mesh": ("Mesh", False),
1050 "meta_ball": ("MetaBall", False),
1051 "object": ("Object", False),
1052 "particle_edit_object": ("Object", False),
1053 "particle_settings": ("ParticleSettings", False),
1054 "particle_system": ("ParticleSystem", False),
1055 "particle_system_editable": ("ParticleSystem", False),
1056 "pose_bone": ("PoseBone", False),
1057 "scene": ("Scene", False),
1058 "sculpt_object": ("Object", False),
1059 "selectable_bases": ("ObjectBase", True),
1060 "selectable_objects": ("Object", True),
1061 "selected_bases": ("ObjectBase", True),
1062 "selected_bones": ("EditBone", True),
1063 "selected_editable_bases": ("ObjectBase", True),
1064 "selected_editable_bones": ("EditBone", True),
1065 "selected_editable_objects": ("Object", True),
1066 "selected_editable_sequences": ("Sequence", True),
1067 "selected_nodes": ("Node", True),
1068 "selected_objects": ("Object", True),
1069 "selected_pose_bones": ("PoseBone", True),
1070 "selected_sequences": ("Sequence", True),
1071 "sequences": ("Sequence", True),
1072 "smoke": ("SmokeModifier", False),
1073 "soft_body": ("SoftBodyModifier", False),
1074 "speaker": ("Speaker", False),
1075 "texture": ("Texture", False),
1076 "texture_slot": ("MaterialTextureSlot", False),
1077 "texture_user": ("ID", False),
1078 "texture_user_property": ("Property", False),
1079 "vertex_paint_object": ("Object", False),
1080 "visible_bases": ("ObjectBase", True),
1081 "visible_bones": ("EditBone", True),
1082 "visible_gpencil_layers": ("GPencilLayer", True),
1083 "visible_objects": ("Object", True),
1084 "visible_pose_bones": ("PoseBone", True),
1085 "weight_paint_object": ("Object", False),
1086 "world": ("World", False),
1090 def pycontext2sphinx(basepath):
1091 # Only use once. very irregular
1093 filepath = os.path.join(basepath, "bpy.context.rst")
1094 file = open(filepath, "w", encoding="utf-8")
1096 fw(title_string("Context Access (bpy.context)", "="))
1097 fw(".. module:: bpy.context\n")
1099 fw("The context members available depend on the area of Blender which is currently being accessed.\n")
1101 fw("Note that all context values are readonly,\n")
1102 fw("but may be modified through the data api or by running operators\n\n")
1104 def write_contex_cls():
1106 fw(title_string("Global Context", "-"))
1107 fw("These properties are avilable in any contexts.\n\n")
1109 # very silly. could make these global and only access once.
1110 # structs, funcs, ops, props = rna_info.BuildRNAInfo()
1111 structs, funcs, ops, props = rna_info_BuildRNAInfo_cache()
1112 struct = structs[("", "Context")]
1113 struct_blacklist = RNA_BLACKLIST.get(struct.identifier, ())
1114 del structs, funcs, ops, props
1116 sorted_struct_properties = struct.properties[:]
1117 sorted_struct_properties.sort(key=lambda prop: prop.identifier)
1120 for prop in sorted_struct_properties:
1121 # support blacklisting props
1122 if prop.identifier in struct_blacklist:
1125 type_descr = prop.get_type_description(
1126 class_fmt=":class:`bpy.types.%s`", collection_id=_BPY_PROP_COLLECTION_ID)
1127 fw(".. data:: %s\n\n" % prop.identifier)
1128 if prop.description:
1129 fw(" %s\n\n" % prop.description)
1131 # special exception, cant use genric code here for enums
1132 if prop.type == "enum":
1133 enum_text = pyrna_enum2sphinx(prop)
1135 write_indented_lines(" ", fw, enum_text)
1138 # end enum exception
1140 fw(" :type: %s\n\n" % type_descr)
1143 del write_contex_cls
1146 # nasty, get strings directly from Blender because there is no other way to get it
1150 "screen_context_dir",
1151 "view3d_context_dir",
1152 "buttons_context_dir",
1153 "image_context_dir",
1157 "sequencer_context_dir",
1161 blend_cdll = ctypes.CDLL("")
1162 for ctx_str in context_strings:
1163 subsection = "%s Context" % ctx_str.split("_")[0].title()
1164 fw("\n%s\n%s\n\n" % (subsection, (len(subsection) * '-')))
1166 attr = ctypes.addressof(getattr(blend_cdll, ctx_str))
1167 c_char_p_p = ctypes.POINTER(ctypes.c_char_p)
1168 char_array = c_char_p_p.from_address(attr)
1170 while char_array[i] is not None:
1171 member = ctypes.string_at(char_array[i]).decode(encoding="ascii")
1172 fw(".. data:: %s\n\n" % member)
1173 member_type, is_seq = context_type_map[member]
1174 fw(" :type: %s :class:`bpy.types.%s`\n\n" % ("sequence of " if is_seq else "", member_type))
1178 # generate typemap...
1179 # for member in sorted(unique):
1180 # print(' "%s": ("", False),' % member)
1181 if len(context_type_map) > len(unique):
1183 "Some types are not used: %s" %
1184 str([member for member in context_type_map if member not in unique]))
1186 pass # will have raised an error above
1191 def pyrna_enum2sphinx(prop, use_empty_descriptions=False):
1192 """ write a bullet point list of enum + descriptions
1195 if use_empty_descriptions:
1199 for identifier, name, description in prop.enum_items:
1205 return "".join(["* ``%s`` %s.\n" %
1207 ", ".join(escape_rst(val) for val in (name, description) if val),
1209 for identifier, name, description in prop.enum_items
1215 def pyrna2sphinx(basepath):
1216 """ bpy.types and bpy.ops
1218 # structs, funcs, ops, props = rna_info.BuildRNAInfo()
1219 structs, funcs, ops, props = rna_info_BuildRNAInfo_cache()
1221 if FILTER_BPY_TYPES is not None:
1222 structs = {k: v for k, v in structs.items() if k[1] in FILTER_BPY_TYPES}
1224 if FILTER_BPY_OPS is not None:
1225 ops = {k: v for k, v in ops.items() if v.module_name in FILTER_BPY_OPS}
1227 def write_param(ident, fw, prop, is_return=False):
1231 kwargs = {"as_ret": True}
1236 kwargs = {"as_arg": True}
1237 identifier = " %s" % prop.identifier
1239 kwargs["class_fmt"] = ":class:`%s`"
1241 kwargs["collection_id"] = _BPY_PROP_COLLECTION_ID
1243 type_descr = prop.get_type_description(**kwargs)
1245 enum_text = pyrna_enum2sphinx(prop)
1247 if prop.name or prop.description or enum_text:
1248 fw(ident + ":%s%s:\n\n" % (id_name, identifier))
1250 if prop.name or prop.description:
1251 fw(ident + " " + ", ".join(val for val in (prop.name, prop.description) if val) + "\n\n")
1253 # special exception, cant use genric code here for enums
1255 write_indented_lines(ident + " ", fw, enum_text)
1258 # end enum exception
1260 fw(ident + ":%s%s: %s\n" % (id_type, identifier, type_descr))
1262 def write_struct(struct):
1263 # if not struct.identifier.startswith("Sc") and not struct.identifier.startswith("I"):
1266 # if not struct.identifier == "Object":
1269 filepath = os.path.join(basepath, "bpy.types.%s.rst" % struct.identifier)
1270 file = open(filepath, "w", encoding="utf-8")
1273 base_id = getattr(struct.base, "identifier", "")
1274 struct_id = struct.identifier
1276 if _BPY_STRUCT_FAKE:
1278 base_id = _BPY_STRUCT_FAKE
1281 title = "%s(%s)" % (struct_id, base_id)
1285 fw(title_string(title, "="))
1287 fw(".. module:: bpy.types\n\n")
1290 write_example_ref("", fw, "bpy.types.%s" % struct_id)
1292 base_ids = [base.identifier for base in struct.get_bases()]
1294 if _BPY_STRUCT_FAKE:
1295 base_ids.append(_BPY_STRUCT_FAKE)
1300 if len(base_ids) > 1:
1301 fw("base classes --- ")
1303 fw("base class --- ")
1305 fw(", ".join((":class:`%s`" % base_id) for base_id in base_ids))
1309 s.identifier for s in structs.values()
1311 if not rna_info.rna_id_ignore(s.identifier)
1315 fw("subclasses --- \n" + ", ".join((":class:`%s`" % s) for s in subclass_ids) + "\n\n")
1317 base_id = getattr(struct.base, "identifier", "")
1319 if _BPY_STRUCT_FAKE:
1321 base_id = _BPY_STRUCT_FAKE
1324 fw(".. class:: %s(%s)\n\n" % (struct_id, base_id))
1326 fw(".. class:: %s\n\n" % struct_id)
1328 fw(" %s\n\n" % struct.description)
1330 # properties sorted in alphabetical order
1331 sorted_struct_properties = struct.properties[:]
1332 sorted_struct_properties.sort(key=lambda prop: prop.identifier)
1334 # support blacklisting props
1335 struct_blacklist = RNA_BLACKLIST.get(struct_id, ())
1337 for prop in sorted_struct_properties:
1339 # support blacklisting props
1340 if prop.identifier in struct_blacklist:
1343 type_descr = prop.get_type_description(class_fmt=":class:`%s`", collection_id=_BPY_PROP_COLLECTION_ID)
1344 # readonly properties use "data" directive, variables properties use "attribute" directive
1345 if 'readonly' in type_descr:
1346 fw(" .. data:: %s\n\n" % prop.identifier)
1348 fw(" .. attribute:: %s\n\n" % prop.identifier)
1349 if prop.description:
1350 fw(" %s\n\n" % prop.description)
1352 # special exception, cant use genric code here for enums
1353 if prop.type == "enum":
1354 enum_text = pyrna_enum2sphinx(prop)
1356 write_indented_lines(" ", fw, enum_text)
1359 # end enum exception
1361 fw(" :type: %s\n\n" % type_descr)
1364 py_properties = struct.get_py_properties()
1366 for identifier, py_prop in py_properties:
1367 pyprop2sphinx(" ", fw, identifier, py_prop)
1368 del py_properties, py_prop
1370 for func in struct.functions:
1371 args_str = ", ".join(prop.get_arg_default(force=False) for prop in func.args)
1373 fw(" .. %s:: %s(%s)\n\n" %
1374 ("classmethod" if func.is_classmethod else "method", func.identifier, args_str))
1375 fw(" %s\n\n" % func.description)
1377 for prop in func.args:
1378 write_param(" ", fw, prop)
1380 if len(func.return_values) == 1:
1381 write_param(" ", fw, func.return_values[0], is_return=True)
1382 elif func.return_values: # multiple return values
1383 fw(" :return (%s):\n" % ", ".join(prop.identifier for prop in func.return_values))
1384 for prop in func.return_values:
1385 # TODO, pyrna_enum2sphinx for multiple return values... actually dont
1386 # think we even use this but still!!!
1387 type_descr = prop.get_type_description(
1388 as_ret=True, class_fmt=":class:`%s`", collection_id=_BPY_PROP_COLLECTION_ID)
1389 descr = prop.description
1392 # In rare cases descr may be empty
1393 fw(" `%s`, %s\n\n" %
1395 ", ".join((val for val in (descr, type_descr) if val))))
1397 write_example_ref(" ", fw, "bpy.types." + struct_id + "." + func.identifier)
1402 py_funcs = struct.get_py_functions()
1405 for identifier, py_func in py_funcs:
1406 pyfunc2sphinx(" ", fw, "bpy.types", struct_id, identifier, py_func, is_class=True)
1407 del py_funcs, py_func
1409 py_funcs = struct.get_py_c_functions()
1412 for identifier, py_func in py_funcs:
1413 py_c_func2sphinx(" ", fw, "bpy.types", struct_id, identifier, py_func, is_class=True)
1417 if struct.base or _BPY_STRUCT_FAKE:
1418 bases = list(reversed(struct.get_bases()))
1423 if _BPY_STRUCT_FAKE:
1425 (key, descr) for key, descr in sorted(bpy.types.Struct.__bases__[0].__dict__.items())
1426 if not key.startswith("__")
1429 if _BPY_STRUCT_FAKE:
1430 for key, descr in descr_items:
1431 if type(descr) == GetSetDescriptorType:
1432 lines.append(" * :class:`%s.%s`\n" % (_BPY_STRUCT_FAKE, key))
1435 for prop in base.properties:
1436 lines.append(" * :class:`%s.%s`\n" % (base.identifier, prop.identifier))
1438 for identifier, py_prop in base.get_py_properties():
1439 lines.append(" * :class:`%s.%s`\n" % (base.identifier, identifier))
1441 for identifier, py_prop in base.get_py_properties():
1442 lines.append(" * :class:`%s.%s`\n" % (base.identifier, identifier))
1445 fw(".. rubric:: Inherited Properties\n\n")
1448 fw(" :columns: 2\n\n")
1457 if _BPY_STRUCT_FAKE:
1458 for key, descr in descr_items:
1459 if type(descr) == MethodDescriptorType:
1460 lines.append(" * :class:`%s.%s`\n" % (_BPY_STRUCT_FAKE, key))
1463 for func in base.functions:
1464 lines.append(" * :class:`%s.%s`\n" % (base.identifier, func.identifier))
1465 for identifier, py_func in base.get_py_functions():
1466 lines.append(" * :class:`%s.%s`\n" % (base.identifier, identifier))
1469 fw(".. rubric:: Inherited Functions\n\n")
1472 fw(" :columns: 2\n\n")
1480 if struct.references:
1481 # use this otherwise it gets in the index for a normal heading.
1482 fw(".. rubric:: References\n\n")
1485 fw(" :columns: 2\n\n")
1487 # context does its own thing
1488 # "active_base": ("ObjectBase", False),
1489 for ref_attr, (ref_type, ref_is_seq) in sorted(context_type_map.items()):
1490 if ref_type == struct_id:
1491 fw(" * :mod:`bpy.context.%s`\n" % ref_attr)
1492 del ref_attr, ref_type, ref_is_seq
1494 for ref in struct.references:
1495 ref_split = ref.split(".")
1496 if len(ref_split) > 2:
1497 ref = ref_split[-2] + "." + ref_split[-1]
1498 fw(" * :class:`%s`\n" % ref)
1501 # docs last?, disable for now
1502 # write_example_ref("", fw, "bpy.types.%s" % struct_id)
1505 if "bpy.types" not in EXCLUDE_MODULES:
1506 for struct in structs.values():
1507 # TODO, rna_info should filter these out!
1508 if "_OT_" in struct.identifier:
1510 write_struct(struct)
1512 def fake_bpy_type(class_value, class_name, descr_str, use_subclasses=True):
1513 filepath = os.path.join(basepath, "bpy.types.%s.rst" % class_name)
1514 file = open(filepath, "w", encoding="utf-8")
1517 fw(title_string(class_name, "="))
1519 fw(".. module:: bpy.types\n")
1524 s.identifier for s in structs.values()
1526 if not rna_info.rna_id_ignore(s.identifier)
1529 fw("subclasses --- \n" + ", ".join((":class:`%s`" % s) for s in sorted(subclass_ids)) + "\n\n")
1531 fw(".. class:: %s\n\n" % class_name)
1532 fw(" %s\n\n" % descr_str)
1533 fw(" .. note::\n\n")
1534 fw(" Note that bpy.types.%s is not actually available from within Blender,\n"
1535 " it only exists for the purpose of documentation.\n\n" % class_name)
1538 (key, descr) for key, descr in sorted(class_value.__dict__.items())
1539 if not key.startswith("__")
1542 for key, descr in descr_items:
1543 # GetSetDescriptorType, GetSetDescriptorType's are not documented yet
1544 if type(descr) == MethodDescriptorType:
1545 py_descr2sphinx(" ", fw, descr, "bpy.types", class_name, key)
1547 for key, descr in descr_items:
1548 if type(descr) == GetSetDescriptorType:
1549 py_descr2sphinx(" ", fw, descr, "bpy.types", class_name, key)
1552 # write fake classes
1553 if _BPY_STRUCT_FAKE:
1554 class_value = bpy.types.Struct.__bases__[0]
1556 class_value, _BPY_STRUCT_FAKE,
1557 "built-in base class for all classes in bpy.types.", use_subclasses=True)
1559 if _BPY_PROP_COLLECTION_FAKE:
1560 class_value = bpy.data.objects.__class__
1562 class_value, _BPY_PROP_COLLECTION_FAKE,
1563 "built-in class used for all collections.", use_subclasses=False)
1567 API_BASEURL = "http://svn.blender.org/svnroot/bf-blender/trunk/blender/release/scripts"
1568 API_BASEURL_ADDON = "http://svn.blender.org/svnroot/bf-extensions/trunk/py/scripts"
1569 API_BASEURL_ADDON_CONTRIB = "http://svn.blender.org/svnroot/bf-extensions/contrib/py/scripts"
1572 for op in ops.values():
1573 op_modules.setdefault(op.module_name, []).append(op)
1576 for op_module_name, ops_mod in op_modules.items():
1577 filepath = os.path.join(basepath, "bpy.ops.%s.rst" % op_module_name)
1578 file = open(filepath, "w", encoding="utf-8")
1581 title = "%s Operators" % op_module_name.replace("_", " ").title()
1583 fw(title_string(title, "="))
1585 fw(".. module:: bpy.ops.%s\n\n" % op_module_name)
1587 ops_mod.sort(key=lambda op: op.func_name)
1590 args_str = ", ".join(prop.get_arg_default(force=True) for prop in op.args)
1591 fw(".. function:: %s(%s)\n\n" % (op.func_name, args_str))
1593 # if the description isn't valid, we output the standard warning
1594 # with a link to the wiki so that people can help
1595 if not op.description or op.description == "(undocumented operator)":
1596 operator_description = undocumented_message('bpy.ops', op.module_name, op.func_name)
1598 operator_description = op.description
1600 fw(" %s\n\n" % operator_description)
1601 for prop in op.args:
1602 write_param(" ", fw, prop)
1606 location = op.get_location()
1607 if location != (None, None):
1608 if location[0].startswith("addons_contrib" + os.sep):
1609 url_base = API_BASEURL_ADDON_CONTRIB
1610 elif location[0].startswith("addons" + os.sep):
1611 url_base = API_BASEURL_ADDON
1613 url_base = API_BASEURL
1615 fw(" :file: `%s <%s/%s>`_:%d\n\n" % (location[0],
1622 if "bpy.ops" not in EXCLUDE_MODULES:
1626 def write_sphinx_conf_py(basepath):
1628 Write sphinx's conf.py
1630 filepath = os.path.join(basepath, "conf.py")
1631 file = open(filepath, "w", encoding="utf-8")
1634 fw("project = 'Blender'\n")
1635 # fw("master_doc = 'index'\n")
1636 fw("copyright = u'Blender Foundation'\n")
1637 fw("version = '%s - API'\n" % BLENDER_VERSION_DOTS)
1638 fw("release = '%s - API'\n" % BLENDER_VERSION_DOTS)
1640 if ARGS.sphinx_theme != 'default':
1641 fw("html_theme = '%s'\n" % ARGS.sphinx_theme)
1643 if ARGS.sphinx_theme == "blender-org":
1644 fw("html_theme_path = ['../']\n")
1645 # copied with the theme, exclude else we get an error [#28873]
1646 fw("html_favicon = 'favicon.ico'\n") # in <theme>/static/
1648 # not helpful since the source is generated, adds to upload size.
1649 fw("html_copy_source = False\n")
1652 # needed for latex, pdf gen
1653 fw("latex_documents = [ ('contents', 'contents.tex', 'Blender Index', 'Blender Foundation', 'manual'), ]\n")
1654 fw("latex_paper_size = 'a4paper'\n")
1658 def execfile(filepath):
1659 global_namespace = {"__file__": filepath, "__name__": "__main__"}
1660 file_handle = open(filepath)
1661 exec(compile(file_handle.read(), filepath, 'exec'), global_namespace)
1665 def write_rst_contents(basepath):
1667 Write the rst file of the main page, needed for sphinx (index.html)
1669 filepath = os.path.join(basepath, "contents.rst")
1670 file = open(filepath, "w", encoding="utf-8")
1673 fw(title_string("Blender Documentation Contents", "%", double=True))
1675 fw("Welcome, this document is an API reference for Blender %s, built %s.\n" %
1676 (BLENDER_VERSION_DOTS, BLENDER_DATE))
1679 # fw("`A PDF version of this document is also available <%s>`_\n" % BLENDER_PDF_FILENAME)
1680 fw("This site can be downloaded for offline use `Download the full Documentation (zipped HTML files) <%s>`_\n" %
1681 BLENDER_ZIP_FILENAME)
1685 if not EXCLUDE_INFO_DOCS:
1686 fw(title_string("Blender/Python Documentation", "=", double=True))
1688 fw(".. toctree::\n")
1689 fw(" :maxdepth: 1\n\n")
1690 for info, info_desc in INFO_DOCS:
1691 fw(" %s <%s>\n\n" % (info_desc, info))
1694 fw(title_string("Application Modules", "=", double=True))
1695 fw(".. toctree::\n")
1696 fw(" :maxdepth: 1\n\n")
1699 "bpy.context", # note: not actually a module
1700 "bpy.data", # note: not actually a module
1706 "bpy.utils.previews",
1710 "bpy.app.translations",
1716 for mod in app_modules:
1717 if mod not in EXCLUDE_MODULES:
1720 fw(title_string("Standalone Modules", "=", double=True))
1721 fw(".. toctree::\n")
1722 fw(" :maxdepth: 1\n\n")
1724 standalone_modules = (
1727 "mathutils.geometry",
1728 "mathutils.bvhtree", "mathutils.kdtree",
1729 "mathutils.interpolate",
1732 "freestyle", "bgl", "blf",
1733 "gpu", "gpu.offscreen",
1734 "aud", "bpy_extras",
1736 # bmesh, submodules are in own page
1740 for mod in standalone_modules:
1741 if mod not in EXCLUDE_MODULES:
1744 # special case, this 'bmesh.ops.rst' is extracted from C source
1745 if "bmesh.ops" not in EXCLUDE_MODULES:
1746 execfile(os.path.join(SCRIPT_DIR, "rst_from_bmesh_opdefines.py"))
1749 if "bge" not in EXCLUDE_MODULES:
1750 fw(title_string("Game Engine Modules", "=", double=True))
1751 fw(".. toctree::\n")
1752 fw(" :maxdepth: 1\n\n")
1753 fw(" bge.types.rst\n\n")
1754 fw(" bge.logic.rst\n\n")
1755 fw(" bge.render.rst\n\n")
1756 fw(" bge.texture.rst\n\n")
1757 fw(" bge.events.rst\n\n")
1758 fw(" bge.constraints.rst\n\n")
1759 fw(" bge.app.rst\n\n")
1761 # rna generated change log
1762 fw(title_string("API Info", "=", double=True))
1763 fw(".. toctree::\n")
1764 fw(" :maxdepth: 1\n\n")
1765 fw(" change_log.rst\n\n")
1769 fw(".. note:: The Blender Python API has areas which are still in development.\n")
1771 fw(" The following areas are subject to change.\n")
1772 fw(" * operator behavior, names and arguments\n")
1773 fw(" * mesh creation and editing functions\n")
1775 fw(" These parts of the API are relatively stable and are unlikely to change significantly\n")
1776 fw(" * data API, access to attributes of Blender data such as mesh verts, material color,\n")
1777 fw(" timeline frames and scene objects\n")
1778 fw(" * user interface functions for defining buttons, creation of menus, headers, panels\n")
1779 fw(" * render engine integration\n")
1780 fw(" * modules: bgl, mathutils & game engine.\n")
1786 def write_rst_bpy(basepath):
1788 Write rst file of bpy module (disabled by default)
1791 filepath = os.path.join(basepath, "bpy.rst")
1792 file = open(filepath, "w", encoding="utf-8")
1797 title = ":mod:`bpy` --- Blender Python Module"
1799 fw(title_string(title, "="))
1801 fw(".. module:: bpy.types\n\n")
1805 def write_rst_types_index(basepath):
1807 Write the rst file of bpy.types module (index)
1809 if "bpy.types" not in EXCLUDE_MODULES:
1810 filepath = os.path.join(basepath, "bpy.types.rst")
1811 file = open(filepath, "w", encoding="utf-8")
1813 fw(title_string("Types (bpy.types)", "="))
1814 fw(".. toctree::\n")
1816 fw(" bpy.types.*\n\n")
1820 def write_rst_ops_index(basepath):
1822 Write the rst file of bpy.ops module (index)
1824 if "bpy.ops" not in EXCLUDE_MODULES:
1825 filepath = os.path.join(basepath, "bpy.ops.rst")
1826 file = open(filepath, "w", encoding="utf-8")
1828 fw(title_string("Operators (bpy.ops)", "="))
1829 write_example_ref("", fw, "bpy.ops")
1830 fw(".. toctree::\n")
1832 fw(" bpy.ops.*\n\n")
1836 def write_rst_data(basepath):
1838 Write the rst file of bpy.data module
1840 if "bpy.data" not in EXCLUDE_MODULES:
1841 # not actually a module, only write this file so we
1842 # can reference in the TOC
1843 filepath = os.path.join(basepath, "bpy.data.rst")
1844 file = open(filepath, "w", encoding="utf-8")
1846 fw(title_string("Data Access (bpy.data)", "="))
1847 fw(".. module:: bpy\n")
1849 fw("This module is used for all Blender/Python access.\n")
1851 fw(".. data:: data\n")
1853 fw(" Access to Blender's internal data\n")
1855 fw(" :type: :class:`bpy.types.BlendData`\n")
1857 fw(".. literalinclude:: ../examples/bpy.data.py\n")
1860 EXAMPLE_SET_USED.add("bpy.data")
1863 def write_rst_importable_modules(basepath):
1865 Write the rst files of importable modules
1867 importable_modules = {
1869 "bpy.path": "Path Utilities",
1870 "bpy.utils": "Utilities",
1871 "bpy_extras": "Extra Utilities",
1874 "aud": "Audio System",
1875 "blf": "Font Drawing",
1876 "gpu.offscreen": "GPU Off-Screen Buffer",
1877 "bmesh": "BMesh Module",
1878 "bmesh.types": "BMesh Types",
1879 "bmesh.utils": "BMesh Utilities",
1880 "bmesh.geometry": "BMesh Geometry Utilities",
1881 "bpy.app": "Application Data",
1882 "bpy.app.handlers": "Application Handlers",
1883 "bpy.app.translations": "Application Translations",
1884 "bpy.props": "Property Definitions",
1885 "idprop.types": "ID Property Access",
1886 "mathutils": "Math Types & Utilities",
1887 "mathutils.geometry": "Geometry Utilities",
1888 "mathutils.bvhtree": "BVHTree Utilities",
1889 "mathutils.kdtree": "KDTree Utilities",
1890 "mathutils.interpolate": "Interpolation Utilities",
1891 "mathutils.noise": "Noise Utilities",
1892 "freestyle": "Freestyle Module",
1893 "freestyle.types": "Freestyle Types",
1894 "freestyle.predicates": "Freestyle Predicates",
1895 "freestyle.functions": "Freestyle Functions",
1896 "freestyle.chainingiterators": "Freestyle Chaining Iterators",
1897 "freestyle.shaders": "Freestyle Shaders",
1898 "freestyle.utils": "Freestyle Utilities",
1900 for mod_name, mod_descr in importable_modules.items():
1901 if mod_name not in EXCLUDE_MODULES:
1902 module = __import__(mod_name,
1903 fromlist=[mod_name.rsplit(".", 1)[-1]])
1904 pymodule2sphinx(basepath, mod_name, module, mod_descr)
1907 def copy_handwritten_rsts(basepath):
1910 if not EXCLUDE_INFO_DOCS:
1911 for info, info_desc in INFO_DOCS:
1912 shutil.copy2(os.path.join(RST_DIR, info), basepath)
1914 # TODO put this docs in Blender's code and use import as per modules above
1915 handwritten_modules = [
1922 "bgl", # "Blender OpenGl wrapper"
1923 "gpu", # "GPU Shader Module"
1925 "bmesh.ops", # generated by rst_from_bmesh_opdefines.py
1930 for mod_name in handwritten_modules:
1931 if mod_name not in EXCLUDE_MODULES:
1932 # copy2 keeps time/date stamps
1933 shutil.copy2(os.path.join(RST_DIR, "%s.rst" % mod_name), basepath)
1935 if "bge.types" not in EXCLUDE_MODULES:
1936 shutil.copy2(os.path.join(RST_DIR, "bge.types.rst"), basepath)
1938 bge_types_dir = os.path.join(RST_DIR, "bge_types")
1940 for i in os.listdir(bge_types_dir):
1941 if i.startswith("."):
1942 # Avoid things like .svn dir...
1944 shutil.copy2(os.path.join(bge_types_dir, i), basepath)
1947 shutil.copy2(os.path.join(RST_DIR, "change_log.rst"), basepath)
1949 # copy images, could be smarter but just glob for now.
1950 for f in os.listdir(RST_DIR):
1951 if f.endswith(".png"):
1952 shutil.copy2(os.path.join(RST_DIR, f), basepath)
1955 def copy_handwritten_extra(basepath):
1956 for f_src in EXTRA_SOURCE_FILES:
1958 f_src = os.sep.join(f_src.split("/"))
1960 f_dst = f_src.replace("..", "__")
1962 f_src = os.path.join(RST_DIR, f_src)
1963 f_dst = os.path.join(basepath, f_dst)
1965 os.makedirs(os.path.dirname(f_dst), exist_ok=True)
1967 shutil.copy2(f_src, f_dst)
1970 def rna2sphinx(basepath):
1978 write_sphinx_conf_py(basepath)
1981 write_rst_contents(basepath)
1984 if "bpy.context" not in EXCLUDE_MODULES:
1985 # one of a kind, context doc (uses ctypes to extract info!)
1986 # doesn't work on mac and windows
1987 if PLATFORM not in {"darwin", "windows"}:
1988 pycontext2sphinx(basepath)
1991 write_rst_bpy(basepath) # bpy, disabled by default
1992 write_rst_types_index(basepath) # bpy.types
1993 write_rst_ops_index(basepath) # bpy.ops
1994 pyrna2sphinx(basepath) # bpy.types.* and bpy.ops.*
1995 write_rst_data(basepath) # bpy.data
1996 write_rst_importable_modules(basepath)
1998 # copy the other rsts
1999 copy_handwritten_rsts(basepath)
2001 # copy source files referenced
2002 copy_handwritten_extra(basepath)
2005 def align_sphinx_in_to_sphinx_in_tmp(dir_src, dir_dst):
2007 Move changed files from SPHINX_IN_TMP to SPHINX_IN
2011 # possible the dir doesn't exist when running recursively
2012 os.makedirs(dir_dst, exist_ok=True)
2014 sphinx_dst_files = set(os.listdir(dir_dst))
2015 sphinx_src_files = set(os.listdir(dir_src))
2017 # remove deprecated files that have been removed
2018 for f in sorted(sphinx_dst_files):
2019 if f not in sphinx_src_files:
2020 BPY_LOGGER.debug("\tdeprecated: %s" % f)
2021 f_dst = os.path.join(dir_dst, f)
2022 if os.path.isdir(f_dst):
2023 shutil.rmtree(f_dst, True)
2027 # freshen with new files.
2028 for f in sorted(sphinx_src_files):
2029 f_src = os.path.join(dir_src, f)
2030 f_dst = os.path.join(dir_dst, f)
2032 if os.path.isdir(f_src):
2033 align_sphinx_in_to_sphinx_in_tmp(f_src, f_dst)
2036 if f in sphinx_dst_files:
2037 if filecmp.cmp(f_src, f_dst):
2041 BPY_LOGGER.debug("\tupdating: %s" % f)
2042 shutil.copy(f_src, f_dst)
2045 def refactor_sphinx_log(sphinx_logfile):
2047 with open(sphinx_logfile, "r", encoding="utf-8") as original_logfile:
2048 lines = set(original_logfile.readlines())
2050 if 'warning' in line.lower() or 'error' in line.lower():
2051 line = line.strip().split(None, 2)
2053 location, kind, msg = line
2054 location = os.path.relpath(location, start=SPHINX_IN)
2055 refactored_log.append((kind, location, msg))
2056 with open(sphinx_logfile, "w", encoding="utf-8") as refactored_logfile:
2057 for log in sorted(refactored_log):
2058 refactored_logfile.write("%-12s %s\n %s\n" % log)
2062 filepath = os.path.join(SCRIPT_DIR, "sphinx_doc_gen_monkeypatch.py")
2063 global_namespace = {"__file__": filepath, "__name__": "__main__"}
2064 file = open(filepath, 'rb')
2065 exec(compile(file.read(), filepath, 'exec'), global_namespace)
2071 # first monkey patch to load in fake members
2074 # eventually, create the dirs
2075 for dir_path in [ARGS.output_dir, SPHINX_IN]:
2076 if not os.path.exists(dir_path):
2079 # eventually, log in files
2081 bpy_logfile = os.path.join(ARGS.output_dir, ".bpy.log")
2082 bpy_logfilehandler = logging.FileHandler(bpy_logfile, mode="w")
2083 bpy_logfilehandler.setLevel(logging.DEBUG)
2084 BPY_LOGGER.addHandler(bpy_logfilehandler)
2086 # using a FileHandler seems to disable the stdout, so we add a StreamHandler
2087 bpy_log_stdout_handler = logging.StreamHandler(stream=sys.stdout)
2088 bpy_log_stdout_handler.setLevel(logging.DEBUG)
2089 BPY_LOGGER.addHandler(bpy_log_stdout_handler)
2091 # in case of out-of-source build, copy the needed dirs
2092 if ARGS.output_dir != SCRIPT_DIR:
2094 examples_dir_copy = os.path.join(ARGS.output_dir, "examples")
2095 if os.path.exists(examples_dir_copy):
2096 shutil.rmtree(examples_dir_copy, True)
2097 shutil.copytree(EXAMPLES_DIR,
2099 ignore=shutil.ignore_patterns(*(".svn",)),
2100 copy_function=shutil.copy)
2102 # eventually, copy the theme dir
2103 if ARGS.sphinx_theme == "blender-org":
2104 if os.path.exists(SPHINX_THEME_DIR):
2105 shutil.rmtree(SPHINX_THEME_DIR, True)
2106 shutil.copytree(SPHINX_THEME_SVN_DIR,
2108 ignore=shutil.ignore_patterns(*(".svn",)),
2109 copy_function=shutil.copy)
2111 # dump the api in rst files
2112 if os.path.exists(SPHINX_IN_TMP):
2113 shutil.rmtree(SPHINX_IN_TMP, True)
2115 rna2sphinx(SPHINX_IN_TMP)
2117 if ARGS.full_rebuild:
2118 # only for full updates
2119 shutil.rmtree(SPHINX_IN, True)
2120 shutil.copytree(SPHINX_IN_TMP,
2122 copy_function=shutil.copy)
2123 if ARGS.sphinx_build and os.path.exists(SPHINX_OUT):
2124 shutil.rmtree(SPHINX_OUT, True)
2125 if ARGS.sphinx_build_pdf and os.path.exists(SPHINX_OUT_PDF):
2126 shutil.rmtree(SPHINX_OUT_PDF, True)
2128 # move changed files in SPHINX_IN
2129 align_sphinx_in_to_sphinx_in_tmp(SPHINX_IN_TMP, SPHINX_IN)
2131 # report which example files weren't used
2132 EXAMPLE_SET_UNUSED = EXAMPLE_SET - EXAMPLE_SET_USED
2133 if EXAMPLE_SET_UNUSED:
2134 BPY_LOGGER.debug("\nUnused examples found in '%s'..." % EXAMPLES_DIR)
2135 for f in sorted(EXAMPLE_SET_UNUSED):
2136 BPY_LOGGER.debug(" %s.py" % f)
2137 BPY_LOGGER.debug(" %d total\n" % len(EXAMPLE_SET_UNUSED))
2139 # eventually, build the html docs
2140 if ARGS.sphinx_build:
2142 subprocess.call(SPHINX_BUILD)
2144 # sphinx-build log cleanup+sort
2146 if os.stat(SPHINX_BUILD_LOG).st_size:
2147 refactor_sphinx_log(SPHINX_BUILD_LOG)
2149 # eventually, build the pdf docs
2150 if ARGS.sphinx_build_pdf:
2152 subprocess.call(SPHINX_BUILD_PDF)
2153 subprocess.call(SPHINX_MAKE_PDF, stdout=SPHINX_MAKE_PDF_STDOUT)
2155 # sphinx-build log cleanup+sort
2157 if os.stat(SPHINX_BUILD_PDF_LOG).st_size:
2158 refactor_sphinx_log(SPHINX_BUILD_PDF_LOG)
2160 # eventually, prepare the dir to be deployed online (REFERENCE_PATH)
2161 if ARGS.pack_reference:
2163 if ARGS.sphinx_build:
2164 # delete REFERENCE_PATH
2165 if os.path.exists(REFERENCE_PATH):
2166 shutil.rmtree(REFERENCE_PATH, True)
2168 # copy SPHINX_OUT to the REFERENCE_PATH
2169 ignores = ('.doctrees', 'objects.inv', '.buildinfo')
2170 shutil.copytree(SPHINX_OUT,
2172 ignore=shutil.ignore_patterns(*ignores))
2173 shutil.copy(os.path.join(REFERENCE_PATH, "contents.html"),
2174 os.path.join(REFERENCE_PATH, "index.html"))
2176 # zip REFERENCE_PATH
2177 basename = os.path.join(ARGS.output_dir, REFERENCE_NAME)
2178 tmp_path = shutil.make_archive(basename, 'zip',
2179 root_dir=ARGS.output_dir,
2180 base_dir=REFERENCE_NAME)
2181 final_path = os.path.join(REFERENCE_PATH, BLENDER_ZIP_FILENAME)
2182 os.rename(tmp_path, final_path)
2184 if ARGS.sphinx_build_pdf:
2185 # copy the pdf to REFERENCE_PATH
2186 shutil.copy(os.path.join(SPHINX_OUT_PDF, "contents.pdf"),
2187 os.path.join(REFERENCE_PATH, BLENDER_PDF_FILENAME))
2192 if __name__ == '__main__':