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, Luca Bonavita
19 # #**** END GPL LICENSE BLOCK #****
27 Run this script from blenders 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
73 # import rpdb2; rpdb2.start_embedded_debugger('test')
80 from platform import platform
81 PLATFORM = platform().split('-')[0].lower() # 'linux', 'darwin', 'windows'
83 SCRIPT_DIR = os.path.abspath(os.path.dirname(__file__))
88 Parse the args passed to Blender after "--", ignored by Blender
92 # When --help is given, print the usage text
93 parser = argparse.ArgumentParser(
94 formatter_class=argparse.RawTextHelpFormatter,
99 parser.add_argument("-p", "--partial",
103 help="Use a wildcard to only build specific module(s)\n"
104 "Example: --partial bmesh*\n",
107 parser.add_argument("-f", "--fullrebuild",
111 help="Rewrite all rst files in sphinx-in/ "
115 parser.add_argument("-b", "--bpy",
119 help="Write the rst file of the bpy module "
123 parser.add_argument("-o", "--output",
127 help="Path of the API docs (default=<script dir>)",
130 parser.add_argument("-T", "--sphinx-theme",
135 # see SPHINX_THEMES below
136 "Sphinx theme (default='default')\n"
139 "(Blender Foundation) blender-org\n" # naiad
140 "(Sphinx) agogo, basic, epub, haiku, nature, "
141 "scrolls, sphinxdoc, traditional\n",
142 # choices=['naiad', 'blender-org'] + # bf
143 # ['agogo', 'basic', 'epub',
144 # 'haiku', 'nature', 'scrolls',
145 # 'sphinxdoc', 'traditional'], # sphinx
148 parser.add_argument("-N", "--sphinx-named-output",
149 dest="sphinx_named_output",
152 help="Add the theme name to the html dir name.\n"
153 "Example: \"sphinx-out_haiku\" (default=False)",
156 parser.add_argument("-B", "--sphinx-build",
160 help="Build the html docs by running:\n"
161 "sphinx-build SPHINX_IN SPHINX_OUT\n"
162 "(default=False; does not depend on -P)",
165 parser.add_argument("-P", "--sphinx-build-pdf",
166 dest="sphinx_build_pdf",
169 help="Build the pdf by running:\n"
170 "sphinx-build -b latex SPHINX_IN SPHINX_OUT_PDF\n"
171 "(default=False; does not depend on -B)",
174 parser.add_argument("-R", "--pack-reference",
175 dest="pack_reference",
178 help="Pack all necessary files in the deployed dir.\n"
179 "(default=False; use with -B and -P)",
182 parser.add_argument("-l", "--log",
187 "Log the output of the api dump and sphinx|latex "
188 "warnings and errors (default=False).\n"
189 "If given, save logs in:\n"
190 "* OUTPUT_DIR/.bpy.log\n"
191 "* OUTPUT_DIR/.sphinx-build.log\n"
192 "* OUTPUT_DIR/.sphinx-build_pdf.log\n"
193 "* OUTPUT_DIR/.latex_make.log",
197 # parse only the args passed after '--'
200 argv = sys.argv[sys.argv.index("--") + 1:] # get all args after "--"
202 return parser.parse_args(argv)
207 # ----------------------------------BPY-----------------------------------------
209 BPY_LOGGER = logging.getLogger('bpy')
210 BPY_LOGGER.setLevel(logging.DEBUG)
214 rm -rf /b/doc/python_api/sphinx-* && \
215 ./blender.bin -b -noaudio --factory-startup -P doc/python_api/sphinx_doc_gen.py && \
216 sphinx-build doc/python_api/sphinx-in doc/python_api/sphinx-out
220 ./blender.bin -b -noaudio --factory-startup -P doc/python_api/sphinx_doc_gen.py -- -f -B
223 # Switch for quick testing so doc-builds don't take so long
226 FILTER_BPY_OPS = None
227 FILTER_BPY_TYPES = None
228 EXCLUDE_INFO_DOCS = False
232 # can manually edit this too:
233 #FILTER_BPY_OPS = ("import.scene", ) # allow
234 #FILTER_BPY_TYPES = ("bpy_struct", "Operator", "ID") # allow
235 EXCLUDE_INFO_DOCS = True
255 "bpy.ops", # supports filtering
258 "bpy.types", # supports filtering
263 "mathutils.geometry",
270 # TODO, support bpy.ops and bpy.types filtering
273 EXCLUDE_MODULES = [m for m in EXCLUDE_MODULES if not fnmatch.fnmatchcase(m, ARGS.partial)]
275 # special support for bpy.types.XXX
276 FILTER_BPY_OPS = tuple([m[8:] for m in ARGS.partial.split(":") if m.startswith("bpy.ops.")])
278 EXCLUDE_MODULES.remove("bpy.ops")
280 FILTER_BPY_TYPES = tuple([m[10:] for m in ARGS.partial.split(":") if m.startswith("bpy.types.")])
282 EXCLUDE_MODULES.remove("bpy.types")
284 print(FILTER_BPY_TYPES)
286 EXCLUDE_INFO_DOCS = (not fnmatch.fnmatchcase("info", ARGS.partial))
291 BPY_LOGGER.debug("Partial Doc Build, Skipping: %s\n" % "\n ".join(sorted(EXCLUDE_MODULES)))
300 BPY_LOGGER.debug("Warning: Built without 'aud' module, docs incomplete...")
301 EXCLUDE_MODULES = list(EXCLUDE_MODULES) + ["aud"]
304 EXAMPLES_DIR = os.path.abspath(os.path.join(SCRIPT_DIR, "examples"))
306 for f in os.listdir(EXAMPLES_DIR):
307 if f.endswith(".py"):
308 EXAMPLE_SET.add(os.path.splitext(f)[0])
309 EXAMPLE_SET_USED = set()
312 RST_DIR = os.path.abspath(os.path.join(SCRIPT_DIR, "rst"))
314 # extra info, not api reference docs
315 # stored in ./rst/info_*
317 ("info_quickstart.rst", "Blender/Python Quickstart: new to blender/scripting and want to get your feet wet?"),
318 ("info_overview.rst", "Blender/Python API Overview: a more complete explanation of python integration"),
319 ("info_tutorial_addon.rst", "Blender/Python Addon Tutorial: a step by step guide on how to write an addon from scratch"),
320 ("info_api_reference.rst", "Blender/Python API Reference Usage: examples of how to use the API reference docs"),
321 ("info_best_practice.rst", "Best Practice: Conventions to follow for writing good scripts"),
322 ("info_tips_and_tricks.rst", "Tips and Tricks: Hints to help you while writing scripts for blender"),
323 ("info_gotcha.rst", "Gotcha's: some of the problems you may come up against when writing scripts"),
326 # only support for properties atm.
328 # XXX messes up PDF!, really a bug but for now just workaround.
329 "UserPreferencesSystem": {"language", }
334 ("Base Mesh Type", '-'),
336 ("Mesh Elements", '-'),
341 ("Sequence Accessors", '-'),
348 ("Selection History", '-'),
351 ("Custom-Data Layer Access", '-'),
358 ("Custom-Data Layer Types", '-'),
364 # --------------------configure compile time options----------------------------
366 # -------------------------------BLENDER----------------------------------------
368 blender_version_strings = [str(v) for v in bpy.app.version]
370 # converting bytes to strings, due to #30154
371 BLENDER_REVISION = str(bpy.app.build_revision, 'utf_8')
372 BLENDER_DATE = str(bpy.app.build_date, 'utf_8')
374 BLENDER_VERSION_DOTS = ".".join(blender_version_strings) # '2.62.1'
375 if BLENDER_REVISION != "Unknown":
376 BLENDER_VERSION_DOTS += " r" + BLENDER_REVISION # '2.62.1 r44584'
378 BLENDER_VERSION_PATH = "_".join(blender_version_strings) # '2_62_1'
379 if bpy.app.version_cycle == "release":
380 BLENDER_VERSION_PATH = "%s%s_release" % ("_".join(blender_version_strings[:2]),
381 bpy.app.version_char) # '2_62_release'
383 # --------------------------DOWNLOADABLE FILES----------------------------------
385 REFERENCE_NAME = "blender_python_reference_%s" % BLENDER_VERSION_PATH
386 REFERENCE_PATH = os.path.join(ARGS.output_dir, REFERENCE_NAME)
387 BLENDER_PDF_FILENAME = "%s.pdf" % REFERENCE_NAME
388 BLENDER_ZIP_FILENAME = "%s.zip" % REFERENCE_NAME
390 # -------------------------------SPHINX-----------------------------------------
392 SPHINX_THEMES = {'bf': ['blender-org'], # , 'naiad',
403 available_themes = SPHINX_THEMES['bf'] + SPHINX_THEMES['sphinx']
404 if ARGS.sphinx_theme not in available_themes:
405 print("Please choose a theme among: %s" % ', '.join(available_themes))
408 if ARGS.sphinx_theme in SPHINX_THEMES['bf']:
409 SPHINX_THEME_DIR = os.path.join(ARGS.output_dir, ARGS.sphinx_theme)
410 SPHINX_THEME_SVN_DIR = os.path.join(SCRIPT_DIR, ARGS.sphinx_theme)
412 SPHINX_IN = os.path.join(ARGS.output_dir, "sphinx-in")
413 SPHINX_IN_TMP = SPHINX_IN + "-tmp"
414 SPHINX_OUT = os.path.join(ARGS.output_dir, "sphinx-out")
415 if ARGS.sphinx_named_output:
416 SPHINX_OUT += "_%s" % ARGS.sphinx_theme
419 if ARGS.sphinx_build:
420 SPHINX_BUILD = ["sphinx-build", SPHINX_IN, SPHINX_OUT]
423 SPHINX_BUILD_LOG = os.path.join(ARGS.output_dir, ".sphinx-build.log")
424 SPHINX_BUILD = ["sphinx-build",
425 "-w", SPHINX_BUILD_LOG,
426 SPHINX_IN, SPHINX_OUT]
429 if ARGS.sphinx_build_pdf:
430 SPHINX_OUT_PDF = os.path.join(ARGS.output_dir, "sphinx-out_pdf")
431 SPHINX_BUILD_PDF = ["sphinx-build",
433 SPHINX_IN, SPHINX_OUT_PDF]
434 SPHINX_MAKE_PDF = ["make", "-C", SPHINX_OUT_PDF]
435 SPHINX_MAKE_PDF_STDOUT = None
438 SPHINX_BUILD_PDF_LOG = os.path.join(ARGS.output_dir, ".sphinx-build_pdf.log")
439 SPHINX_BUILD_PDF = ["sphinx-build", "-b", "latex",
440 "-w", SPHINX_BUILD_PDF_LOG,
441 SPHINX_IN, SPHINX_OUT_PDF]
443 sphinx_make_pdf_log = os.path.join(ARGS.output_dir, ".latex_make.log")
444 SPHINX_MAKE_PDF_STDOUT = open(sphinx_make_pdf_log, "w", encoding="utf-8")
446 # --------------------------------API DUMP--------------------------------------
448 # lame, python wont give some access
449 ClassMethodDescriptorType = type(dict.__dict__['fromkeys'])
450 MethodDescriptorType = type(dict.get)
451 GetSetDescriptorType = type(int.real)
452 from types import MemberDescriptorType
454 _BPY_STRUCT_FAKE = "bpy_struct"
455 _BPY_PROP_COLLECTION_FAKE = "bpy_prop_collection"
457 if _BPY_PROP_COLLECTION_FAKE:
458 _BPY_PROP_COLLECTION_ID = ":class:`%s`" % _BPY_PROP_COLLECTION_FAKE
460 _BPY_PROP_COLLECTION_ID = "collection"
463 def is_struct_seq(value):
464 return isinstance(value, tuple) and type(tuple) != tuple and hasattr(value, "n_fields")
467 def undocumented_message(module_name, type_name, identifier):
468 if str(type_name).startswith('<module'):
469 preloadtitle = '%s.%s' % (module_name, identifier)
471 preloadtitle = '%s.%s.%s' % (module_name, type_name, identifier)
472 message = ("Undocumented (`contribute "
473 "<http://wiki.blender.org/index.php/"
474 "Dev:2.5/Py/API/Generating_API_Reference/Contribute"
477 "&preload=Dev:2.5/Py/API/Generating_API_Reference/Contribute/Howto-message"
478 "&preloadtitle=%s>`_)\n\n" % preloadtitle)
484 Converts values to strings for the range directive.
485 (unused function it seems)
491 elif type(val) == float:
497 def example_extract_docstring(filepath):
498 file = open(filepath, "r", encoding="utf-8")
499 line = file.readline()
502 if line.startswith('"""'): # assume nothing here
508 for line in file.readlines():
510 if line.startswith('"""'):
513 text.append(line.rstrip())
517 return "\n".join(text), line_no
520 def title_string(text, heading_char, double=False):
521 filler = len(text) * heading_char
524 return "%s\n%s\n%s\n\n" % (filler, text, filler)
526 return "%s\n%s\n\n" % (text, filler)
529 def write_example_ref(ident, fw, example_id, ext="py"):
530 if example_id in EXAMPLE_SET:
532 # extract the comment
533 filepath = os.path.join("..", "examples", "%s.%s" % (example_id, ext))
534 filepath_full = os.path.join(os.path.dirname(fw.__self__.name), filepath)
536 text, line_no = example_extract_docstring(filepath_full)
538 for line in text.split("\n"):
539 fw("%s\n" % (ident + line).rstrip())
542 fw("%s.. literalinclude:: %s\n" % (ident, filepath))
544 fw("%s :lines: %d-\n" % (ident, line_no))
546 EXAMPLE_SET_USED.add(example_id)
549 BPY_LOGGER.debug("\tskipping example: " + example_id)
551 # Support for numbered files bpy.types.Operator -> bpy.types.Operator.1.py
554 example_id_num = "%s.%d" % (example_id, i)
555 if example_id_num in EXAMPLE_SET:
556 write_example_ref(ident, fw, example_id_num, ext)
562 def write_indented_lines(ident, fn, text, strip=True):
564 Apply same indentation to all lines in a multilines text.
569 lines = text.split("\n")
571 # strip empty lines from the start/end
572 while lines and not lines[0].strip():
574 while lines and not lines[-1].strip():
578 # set indentation to <indent>
582 ident_strip = min(ident_strip, len(l) - len(l.lstrip()))
584 fn(ident + l[ident_strip:] + "\n")
586 # add <indent> number of blanks to the current indentation
591 def pymethod2sphinx(ident, fw, identifier, py_func):
593 class method to sphinx
595 arg_str = inspect.formatargspec(*inspect.getargspec(py_func))
596 if arg_str.startswith("(self, "):
597 arg_str = "(" + arg_str[7:]
599 elif arg_str.startswith("(cls, "):
600 arg_str = "(" + arg_str[6:]
601 func_type = "classmethod"
603 func_type = "staticmethod"
605 fw(ident + ".. %s:: %s%s\n\n" % (func_type, identifier, arg_str))
607 write_indented_lines(ident + " ", fw, py_func.__doc__)
611 def pyfunc2sphinx(ident, fw, identifier, py_func, is_class=True):
613 function or class method to sphinx
615 arg_str = inspect.formatargspec(*inspect.getargspec(py_func))
618 func_type = "function"
620 # ther rest are class methods
621 elif arg_str.startswith("(self, "):
622 arg_str = "(" + arg_str[7:]
624 elif arg_str.startswith("(cls, "):
625 arg_str = "(" + arg_str[6:]
626 func_type = "classmethod"
628 func_type = "staticmethod"
630 fw(ident + ".. %s:: %s%s\n\n" % (func_type, identifier, arg_str))
632 write_indented_lines(ident + " ", fw, py_func.__doc__)
636 def py_descr2sphinx(ident, fw, descr, module_name, type_name, identifier):
637 if identifier.startswith("_"):
642 doc = undocumented_message(module_name, type_name, identifier)
644 if type(descr) == GetSetDescriptorType:
645 fw(ident + ".. attribute:: %s\n\n" % identifier)
646 write_indented_lines(ident + " ", fw, doc, False)
648 elif type(descr) == MemberDescriptorType: # same as above but use 'data'
649 fw(ident + ".. data:: %s\n\n" % identifier)
650 write_indented_lines(ident + " ", fw, doc, False)
652 elif type(descr) in (MethodDescriptorType, ClassMethodDescriptorType):
653 write_indented_lines(ident, fw, doc, False)
656 raise TypeError("type was not GetSetDescriptorType, MethodDescriptorType or ClassMethodDescriptorType")
658 write_example_ref(ident + " ", fw, module_name + "." + type_name + "." + identifier)
662 def py_c_func2sphinx(ident, fw, module_name, type_name, identifier, py_func, is_class=True):
664 c defined function to sphinx.
667 # dump the docstring, assume its formatted correctly
669 write_indented_lines(ident, fw, py_func.__doc__, False)
672 fw(ident + ".. function:: %s()\n\n" % identifier)
673 fw(ident + " " + undocumented_message(module_name, type_name, identifier))
676 write_example_ref(ident + " ", fw, module_name + "." + type_name + "." + identifier)
678 write_example_ref(ident + " ", fw, module_name + "." + identifier)
683 def pyprop2sphinx(ident, fw, identifier, py_prop):
685 python property to sphinx
687 # readonly properties use "data" directive, variables use "attribute" directive
688 if py_prop.fset is None:
689 fw(ident + ".. data:: %s\n\n" % identifier)
691 fw(ident + ".. attribute:: %s\n\n" % identifier)
692 write_indented_lines(ident + " ", fw, py_prop.__doc__)
693 if py_prop.fset is None:
694 fw(ident + " (readonly)\n\n")
697 def pymodule2sphinx(basepath, module_name, module, title):
699 attribute_set = set()
700 filepath = os.path.join(basepath, module_name + ".rst")
702 module_all = getattr(module, "__all__", None)
703 module_dir = sorted(dir(module))
706 module_dir = module_all
708 # TODO - currently only used for classes
710 module_grouping = MODULE_GROUPING.get(module_name)
712 def module_grouping_index(name):
713 if module_grouping is not None:
715 return module_grouping.index(name)
720 def module_grouping_heading(name):
721 if module_grouping is not None:
722 i = module_grouping_index(name) - 1
723 if i >= 0 and type(module_grouping[i]) == tuple:
724 return module_grouping[i]
727 def module_grouping_sort_key(name):
728 return module_grouping_index(name)
729 # done grouping support
731 file = open(filepath, "w", encoding="utf-8")
735 fw(title_string("%s (%s)" % (title, module_name), "="))
737 fw(".. module:: %s\n\n" % module_name)
740 # Note, may contain sphinx syntax, dont mangle!
741 fw(module.__doc__.strip())
744 write_example_ref("", fw, module_name)
747 # we could also scan files but this ensures __all__ is used correctly
748 if module_all is not None:
752 for submod_name in module_all:
754 exec_str = "from %s import %s as submod" % (module.__name__, submod_name)
755 exec(exec_str, ns, ns)
756 submod = ns["submod"]
757 if type(submod) == types.ModuleType:
758 submod_ls.append((submod_name, submod))
765 fw(" :maxdepth: 1\n\n")
767 for submod_name, submod in submod_ls:
768 submod_name_full = "%s.%s" % (module_name, submod_name)
769 fw(" %s.rst\n\n" % submod_name_full)
771 pymodule2sphinx(basepath, submod_name_full, submod, "%s submodule" % module_name)
773 # done writing submodules!
775 # write members of the module
776 # only tested with PyStructs which are not exactly modules
777 for key, descr in sorted(type(module).__dict__.items()):
778 if key.startswith("__"):
780 # naughty, we also add getset's into PyStructs, this is not typical py but also not incorrect.
782 # type_name is only used for examples and messages
783 type_name = str(type(module)).strip("<>").split(" ", 1)[-1][1:-1] # "<class 'bpy.app.handlers'>" --> bpy.app.handlers
784 if type(descr) == types.GetSetDescriptorType:
785 py_descr2sphinx("", fw, descr, module_name, type_name, key)
786 attribute_set.add(key)
788 for key, descr in sorted(type(module).__dict__.items()):
789 if key.startswith("__"):
792 if type(descr) == MemberDescriptorType:
794 value = getattr(module, key, None)
796 value_type = type(value)
797 descr_sorted.append((key, descr, value, type(value)))
798 # sort by the valye type
799 descr_sorted.sort(key=lambda descr_data: str(descr_data[3]))
800 for key, descr, value, value_type in descr_sorted:
802 # must be documented as a submodule
803 if is_struct_seq(value):
806 type_name = value_type.__name__
807 py_descr2sphinx("", fw, descr, module_name, type_name, key)
809 attribute_set.add(key)
811 del key, descr, descr_sorted
816 # use this list so we can sort by type
817 module_dir_value_type = []
819 for attribute in module_dir:
820 if attribute.startswith("_"):
823 if attribute in attribute_set:
826 if attribute.startswith("n_"): # annoying exception, needed for bpy.app
829 # workaround for bpy.app documenting .index() and .count()
830 if isinstance(module, tuple) and hasattr(tuple, attribute):
833 value = getattr(module, attribute)
835 module_dir_value_type.append((attribute, value, type(value)))
837 # sort by str of each type
838 # this way lists, functions etc are grouped.
839 module_dir_value_type.sort(key=lambda triple: str(triple[2]))
841 for attribute, value, value_type in module_dir_value_type:
842 if value_type == types.FunctionType:
843 pyfunc2sphinx("", fw, attribute, value, is_class=False)
844 elif value_type in (types.BuiltinMethodType, types.BuiltinFunctionType): # both the same at the moment but to be future proof
845 # note: can't get args from these, so dump the string as is
846 # this means any module used like this must have fully formatted docstrings.
847 py_c_func2sphinx("", fw, module_name, None, attribute, value, is_class=False)
848 elif value_type == type:
849 classes.append((attribute, value))
850 elif issubclass(value_type, types.ModuleType):
851 submodules.append((attribute, value))
852 elif value_type in (bool, int, float, str, tuple):
853 # constant, not much fun we can do here except to list it.
854 # TODO, figure out some way to document these!
855 fw(".. data:: %s\n\n" % attribute)
856 write_indented_lines(" ", fw, "constant value %s" % repr(value), False)
859 BPY_LOGGER.debug("\tnot documenting %s.%s of %r type" % (module_name, attribute, value_type.__name__))
862 attribute_set.add(attribute)
863 # TODO, more types...
864 del module_dir_value_type
866 # TODO, bpy_extras does this already, mathutils not.
875 for attribute, submod in submodules:
876 fw("* :mod:`%s.%s`\n" % (module_name, attribute))
880 if module_grouping is not None:
881 classes.sort(key=lambda pair: module_grouping_sort_key(pair[0]))
883 # write collected classes now
884 for (type_name, value) in classes:
886 if module_grouping is not None:
887 heading, heading_char = module_grouping_heading(type_name)
889 fw(title_string(heading, heading_char))
891 # May need to be its own function
892 fw(".. class:: %s\n\n" % type_name)
894 write_indented_lines(" ", fw, value.__doc__, False)
896 write_example_ref(" ", fw, module_name + "." + type_name)
898 descr_items = [(key, descr) for key, descr in sorted(value.__dict__.items()) if not key.startswith("__")]
900 for key, descr in descr_items:
901 if type(descr) == ClassMethodDescriptorType:
902 py_descr2sphinx(" ", fw, descr, module_name, type_name, key)
904 for key, descr in descr_items:
905 if type(descr) == MethodDescriptorType:
906 py_descr2sphinx(" ", fw, descr, module_name, type_name, key)
908 for key, descr in descr_items:
909 if type(descr) == GetSetDescriptorType:
910 py_descr2sphinx(" ", fw, descr, module_name, type_name, key)
916 # Changes in blender will force errors here
918 "active_base": ("ObjectBase", False),
919 "active_bone": ("EditBone", False),
920 "active_object": ("Object", False),
921 "active_operator": ("Operator", False),
922 "active_pose_bone": ("PoseBone", False),
923 "active_node": ("Node", False),
924 "armature": ("Armature", False),
925 "bone": ("Bone", False),
926 "brush": ("Brush", False),
927 "camera": ("Camera", False),
928 "cloth": ("ClothModifier", False),
929 "collision": ("CollisionModifier", False),
930 "curve": ("Curve", False),
931 "dynamic_paint": ("DynamicPaintModifier", False),
932 "edit_bone": ("EditBone", False),
933 "edit_image": ("Image", False),
934 "edit_mask": ("Mask", False),
935 "edit_movieclip": ("MovieClip", False),
936 "edit_object": ("Object", False),
937 "edit_text": ("Text", False),
938 "editable_bones": ("EditBone", True),
939 "fluid": ("FluidSimulationModifier", False),
940 "image_paint_object": ("Object", False),
941 "lamp": ("Lamp", False),
942 "lattice": ("Lattice", False),
943 "material": ("Material", False),
944 "material_slot": ("MaterialSlot", False),
945 "mesh": ("Mesh", False),
946 "meta_ball": ("MetaBall", False),
947 "object": ("Object", False),
948 "particle_edit_object": ("Object", False),
949 "particle_settings": ("ParticleSettings", False),
950 "particle_system": ("ParticleSystem", False),
951 "particle_system_editable": ("ParticleSystem", False),
952 "pose_bone": ("PoseBone", False),
953 "scene": ("Scene", False),
954 "sculpt_object": ("Object", False),
955 "selectable_bases": ("ObjectBase", True),
956 "selectable_objects": ("Object", True),
957 "selected_bases": ("ObjectBase", True),
958 "selected_bones": ("EditBone", True),
959 "selected_editable_bases": ("ObjectBase", True),
960 "selected_editable_bones": ("EditBone", True),
961 "selected_editable_objects": ("Object", True),
962 "selected_editable_sequences": ("Sequence", True),
963 "selected_nodes": ("Node", True),
964 "selected_objects": ("Object", True),
965 "selected_pose_bones": ("PoseBone", True),
966 "selected_sequences": ("Sequence", True),
967 "sequences": ("Sequence", True),
968 "smoke": ("SmokeModifier", False),
969 "soft_body": ("SoftBodyModifier", False),
970 "speaker": ("Speaker", False),
971 "texture": ("Texture", False),
972 "texture_slot": ("MaterialTextureSlot", False),
973 "texture_user": ("ID", False),
974 "vertex_paint_object": ("Object", False),
975 "visible_bases": ("ObjectBase", True),
976 "visible_bones": ("EditBone", True),
977 "visible_objects": ("Object", True),
978 "visible_pose_bones": ("PoseBone", True),
979 "weight_paint_object": ("Object", False),
980 "world": ("World", False),
983 def pycontext2sphinx(basepath):
984 # Only use once. very irregular
986 filepath = os.path.join(basepath, "bpy.context.rst")
987 file = open(filepath, "w", encoding="utf-8")
989 fw(title_string("Context Access (bpy.context)", "="))
990 fw(".. module:: bpy.context\n")
992 fw("The context members available depend on the area of blender which is currently being accessed.\n")
994 fw("Note that all context values are readonly, but may be modified through the data api or by running operators\n\n")
996 # nasty, get strings directly from blender because there is no other way to get it
1000 "screen_context_dir",
1001 "view3d_context_dir",
1002 "buttons_context_dir",
1003 "image_context_dir",
1007 "sequencer_context_dir",
1012 blend_cdll = ctypes.CDLL("")
1013 for ctx_str in context_strings:
1014 subsection = "%s Context" % ctx_str.split("_")[0].title()
1015 fw("\n%s\n%s\n\n" % (subsection, (len(subsection) * '-')))
1017 attr = ctypes.addressof(getattr(blend_cdll, ctx_str))
1018 c_char_p_p = ctypes.POINTER(ctypes.c_char_p)
1019 char_array = c_char_p_p.from_address(attr)
1021 while char_array[i] is not None:
1022 member = ctypes.string_at(char_array[i]).decode(encoding="ascii")
1023 fw(".. data:: %s\n\n" % member)
1024 member_type, is_seq = context_type_map[member]
1025 fw(" :type: %s :class:`bpy.types.%s`\n\n" % ("sequence of " if is_seq else "", member_type))
1029 # generate typemap...
1030 # for member in sorted(unique):
1031 # print(' "%s": ("", False),' % member)
1032 if len(context_type_map) > len(unique):
1033 raise Exception("Some types are not used: %s" % str([member for member in context_type_map if member not in unique]))
1035 pass # will have raised an error above
1040 def pyrna_enum2sphinx(prop, use_empty_descriptions=False):
1041 """ write a bullet point list of enum + descrptons
1044 if use_empty_descriptions:
1048 for identifier, name, description in prop.enum_items:
1054 return "".join(["* ``%s`` %s.\n" %
1056 ", ".join(val for val in (name, description) if val),
1058 for identifier, name, description in prop.enum_items
1064 def pyrna2sphinx(basepath):
1065 """ bpy.types and bpy.ops
1067 structs, funcs, ops, props = rna_info.BuildRNAInfo()
1068 if FILTER_BPY_TYPES is not None:
1069 structs = {k: v for k, v in structs.items() if k[1] in FILTER_BPY_TYPES}
1071 if FILTER_BPY_OPS is not None:
1072 ops = {k: v for k, v in ops.items() if v.module_name in FILTER_BPY_OPS}
1074 def write_param(ident, fw, prop, is_return=False):
1078 kwargs = {"as_ret": True}
1083 kwargs = {"as_arg": True}
1084 identifier = " %s" % prop.identifier
1086 kwargs["class_fmt"] = ":class:`%s`"
1088 kwargs["collection_id"] = _BPY_PROP_COLLECTION_ID
1090 type_descr = prop.get_type_description(**kwargs)
1092 enum_text = pyrna_enum2sphinx(prop)
1094 if prop.name or prop.description or enum_text:
1095 fw(ident + ":%s%s:\n\n" % (id_name, identifier))
1097 if prop.name or prop.description:
1098 fw(ident + " " + ", ".join(val for val in (prop.name, prop.description) if val) + "\n\n")
1100 # special exception, cant use genric code here for enums
1102 write_indented_lines(ident + " ", fw, enum_text)
1105 # end enum exception
1107 fw(ident + ":%s%s: %s\n" % (id_type, identifier, type_descr))
1109 def write_struct(struct):
1110 #if not struct.identifier.startswith("Sc") and not struct.identifier.startswith("I"):
1113 #if not struct.identifier == "Object":
1116 filepath = os.path.join(basepath, "bpy.types.%s.rst" % struct.identifier)
1117 file = open(filepath, "w", encoding="utf-8")
1120 base_id = getattr(struct.base, "identifier", "")
1121 struct_id = struct.identifier
1123 if _BPY_STRUCT_FAKE:
1125 base_id = _BPY_STRUCT_FAKE
1128 title = "%s(%s)" % (struct_id, base_id)
1132 fw(title_string(title, "="))
1134 fw(".. module:: bpy.types\n\n")
1137 write_example_ref("", fw, "bpy.types.%s" % struct_id)
1139 base_ids = [base.identifier for base in struct.get_bases()]
1141 if _BPY_STRUCT_FAKE:
1142 base_ids.append(_BPY_STRUCT_FAKE)
1147 if len(base_ids) > 1:
1148 fw("base classes --- ")
1150 fw("base class --- ")
1152 fw(", ".join((":class:`%s`" % base_id) for base_id in base_ids))
1155 subclass_ids = [s.identifier for s in structs.values() if s.base is struct if not rna_info.rna_id_ignore(s.identifier)]
1157 fw("subclasses --- \n" + ", ".join((":class:`%s`" % s) for s in subclass_ids) + "\n\n")
1159 base_id = getattr(struct.base, "identifier", "")
1161 if _BPY_STRUCT_FAKE:
1163 base_id = _BPY_STRUCT_FAKE
1166 fw(".. class:: %s(%s)\n\n" % (struct_id, base_id))
1168 fw(".. class:: %s\n\n" % struct_id)
1170 fw(" %s\n\n" % struct.description)
1172 # properties sorted in alphabetical order
1173 sorted_struct_properties = struct.properties[:]
1174 sorted_struct_properties.sort(key=lambda prop: prop.identifier)
1176 # support blacklisting props
1177 struct_blacklist = RNA_BLACKLIST.get(struct_id, ())
1179 for prop in sorted_struct_properties:
1181 # support blacklisting props
1182 if prop.identifier in struct_blacklist:
1185 type_descr = prop.get_type_description(class_fmt=":class:`%s`", collection_id=_BPY_PROP_COLLECTION_ID)
1186 # readonly properties use "data" directive, variables properties use "attribute" directive
1187 if 'readonly' in type_descr:
1188 fw(" .. data:: %s\n\n" % prop.identifier)
1190 fw(" .. attribute:: %s\n\n" % prop.identifier)
1191 if prop.description:
1192 fw(" %s\n\n" % prop.description)
1194 # special exception, cant use genric code here for enums
1195 if prop.type == "enum":
1196 enum_text = pyrna_enum2sphinx(prop)
1198 write_indented_lines(" ", fw, enum_text)
1201 # end enum exception
1203 fw(" :type: %s\n\n" % type_descr)
1206 py_properties = struct.get_py_properties()
1208 for identifier, py_prop in py_properties:
1209 pyprop2sphinx(" ", fw, identifier, py_prop)
1210 del py_properties, py_prop
1212 for func in struct.functions:
1213 args_str = ", ".join(prop.get_arg_default(force=False) for prop in func.args)
1215 fw(" .. %s:: %s(%s)\n\n" % ("classmethod" if func.is_classmethod else "method", func.identifier, args_str))
1216 fw(" %s\n\n" % func.description)
1218 for prop in func.args:
1219 write_param(" ", fw, prop)
1221 if len(func.return_values) == 1:
1222 write_param(" ", fw, func.return_values[0], is_return=True)
1223 elif func.return_values: # multiple return values
1224 fw(" :return (%s):\n" % ", ".join(prop.identifier for prop in func.return_values))
1225 for prop in func.return_values:
1226 # TODO, pyrna_enum2sphinx for multiple return values... actually dont think we even use this but still!!!
1227 type_descr = prop.get_type_description(as_ret=True, class_fmt=":class:`%s`", collection_id=_BPY_PROP_COLLECTION_ID)
1228 descr = prop.description
1231 fw(" `%s`, %s, %s\n\n" % (prop.identifier, descr, type_descr))
1233 write_example_ref(" ", fw, "bpy.types." + struct_id + "." + func.identifier)
1238 py_funcs = struct.get_py_functions()
1241 for identifier, py_func in py_funcs:
1242 pyfunc2sphinx(" ", fw, identifier, py_func, is_class=True)
1243 del py_funcs, py_func
1245 py_funcs = struct.get_py_c_functions()
1248 for identifier, py_func in py_funcs:
1249 py_c_func2sphinx(" ", fw, "bpy.types", struct_id, identifier, py_func, is_class=True)
1253 if struct.base or _BPY_STRUCT_FAKE:
1254 bases = list(reversed(struct.get_bases()))
1259 if _BPY_STRUCT_FAKE:
1260 descr_items = [(key, descr) for key, descr in sorted(bpy.types.Struct.__bases__[0].__dict__.items()) if not key.startswith("__")]
1262 if _BPY_STRUCT_FAKE:
1263 for key, descr in descr_items:
1264 if type(descr) == GetSetDescriptorType:
1265 lines.append(" * :class:`%s.%s`\n" % (_BPY_STRUCT_FAKE, key))
1268 for prop in base.properties:
1269 lines.append(" * :class:`%s.%s`\n" % (base.identifier, prop.identifier))
1271 for identifier, py_prop in base.get_py_properties():
1272 lines.append(" * :class:`%s.%s`\n" % (base.identifier, identifier))
1274 for identifier, py_prop in base.get_py_properties():
1275 lines.append(" * :class:`%s.%s`\n" % (base.identifier, identifier))
1278 fw(".. rubric:: Inherited Properties\n\n")
1281 fw(" :columns: 2\n\n")
1290 if _BPY_STRUCT_FAKE:
1291 for key, descr in descr_items:
1292 if type(descr) == MethodDescriptorType:
1293 lines.append(" * :class:`%s.%s`\n" % (_BPY_STRUCT_FAKE, key))
1296 for func in base.functions:
1297 lines.append(" * :class:`%s.%s`\n" % (base.identifier, func.identifier))
1298 for identifier, py_func in base.get_py_functions():
1299 lines.append(" * :class:`%s.%s`\n" % (base.identifier, identifier))
1302 fw(".. rubric:: Inherited Functions\n\n")
1305 fw(" :columns: 2\n\n")
1313 if struct.references:
1314 # use this otherwise it gets in the index for a normal heading.
1315 fw(".. rubric:: References\n\n")
1318 fw(" :columns: 2\n\n")
1320 # context does its own thing
1321 # "active_base": ("ObjectBase", False),
1322 for ref_attr, (ref_type, ref_is_seq) in sorted(context_type_map.items()):
1323 if ref_type == struct_id:
1324 fw(" * :mod:`bpy.context.%s`\n" % ref_attr)
1325 del ref_attr, ref_type, ref_is_seq
1327 for ref in struct.references:
1328 ref_split = ref.split(".")
1329 if len(ref_split) > 2:
1330 ref = ref_split[-2] + "." + ref_split[-1]
1331 fw(" * :class:`%s`\n" % ref)
1334 # docs last?, disable for now
1335 # write_example_ref("", fw, "bpy.types.%s" % struct_id)
1338 if "bpy.types" not in EXCLUDE_MODULES:
1339 for struct in structs.values():
1340 # TODO, rna_info should filter these out!
1341 if "_OT_" in struct.identifier:
1343 write_struct(struct)
1345 def fake_bpy_type(class_value, class_name, descr_str, use_subclasses=True):
1346 filepath = os.path.join(basepath, "bpy.types.%s.rst" % class_name)
1347 file = open(filepath, "w", encoding="utf-8")
1350 fw(title_string(class_name, "="))
1352 fw(".. module:: bpy.types\n")
1356 subclass_ids = [s.identifier for s in structs.values() if s.base is None if not rna_info.rna_id_ignore(s.identifier)]
1358 fw("subclasses --- \n" + ", ".join((":class:`%s`" % s) for s in sorted(subclass_ids)) + "\n\n")
1360 fw(".. class:: %s\n\n" % class_name)
1361 fw(" %s\n\n" % descr_str)
1362 fw(" .. note::\n\n")
1363 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)
1365 descr_items = [(key, descr) for key, descr in sorted(class_value.__dict__.items()) if not key.startswith("__")]
1367 for key, descr in descr_items:
1368 if type(descr) == MethodDescriptorType: # GetSetDescriptorType, GetSetDescriptorType's are not documented yet
1369 py_descr2sphinx(" ", fw, descr, "bpy.types", class_name, key)
1371 for key, descr in descr_items:
1372 if type(descr) == GetSetDescriptorType:
1373 py_descr2sphinx(" ", fw, descr, "bpy.types", class_name, key)
1376 # write fake classes
1377 if _BPY_STRUCT_FAKE:
1378 class_value = bpy.types.Struct.__bases__[0]
1379 fake_bpy_type(class_value, _BPY_STRUCT_FAKE, "built-in base class for all classes in bpy.types.", use_subclasses=True)
1381 if _BPY_PROP_COLLECTION_FAKE:
1382 class_value = bpy.data.objects.__class__
1383 fake_bpy_type(class_value, _BPY_PROP_COLLECTION_FAKE, "built-in class used for all collections.", use_subclasses=False)
1387 API_BASEURL = "http://svn.blender.org/svnroot/bf-blender/trunk/blender/release/scripts"
1388 API_BASEURL_ADDON = "http://svn.blender.org/svnroot/bf-extensions/trunk/py/scripts"
1389 API_BASEURL_ADDON_CONTRIB = "http://svn.blender.org/svnroot/bf-extensions/contrib/py/scripts"
1392 for op in ops.values():
1393 op_modules.setdefault(op.module_name, []).append(op)
1396 for op_module_name, ops_mod in op_modules.items():
1397 filepath = os.path.join(basepath, "bpy.ops.%s.rst" % op_module_name)
1398 file = open(filepath, "w", encoding="utf-8")
1401 title = "%s Operators" % op_module_name.replace("_", " ").title()
1403 fw(title_string(title, "="))
1405 fw(".. module:: bpy.ops.%s\n\n" % op_module_name)
1407 ops_mod.sort(key=lambda op: op.func_name)
1410 args_str = ", ".join(prop.get_arg_default(force=True) for prop in op.args)
1411 fw(".. function:: %s(%s)\n\n" % (op.func_name, args_str))
1413 # if the description isn't valid, we output the standard warning
1414 # with a link to the wiki so that people can help
1415 if not op.description or op.description == "(undocumented operator)":
1416 operator_description = undocumented_message('bpy.ops', op.module_name, op.func_name)
1418 operator_description = op.description
1420 fw(" %s\n\n" % operator_description)
1421 for prop in op.args:
1422 write_param(" ", fw, prop)
1426 location = op.get_location()
1427 if location != (None, None):
1428 if location[0].startswith("addons_contrib" + os.sep):
1429 url_base = API_BASEURL_ADDON_CONTRIB
1430 elif location[0].startswith("addons" + os.sep):
1431 url_base = API_BASEURL_ADDON
1433 url_base = API_BASEURL
1435 fw(" :file: `%s <%s/%s>`_:%d\n\n" % (location[0],
1442 if "bpy.ops" not in EXCLUDE_MODULES:
1446 def write_sphinx_conf_py(basepath):
1448 Write sphinx's conf.py
1450 filepath = os.path.join(basepath, "conf.py")
1451 file = open(filepath, "w", encoding="utf-8")
1454 fw("project = 'Blender'\n")
1455 # fw("master_doc = 'index'\n")
1456 fw("copyright = u'Blender Foundation'\n")
1457 fw("version = '%s - API'\n" % BLENDER_VERSION_DOTS)
1458 fw("release = '%s - API'\n" % BLENDER_VERSION_DOTS)
1460 if ARGS.sphinx_theme != 'default':
1461 fw("html_theme = '%s'\n" % ARGS.sphinx_theme)
1463 if ARGS.sphinx_theme in SPHINX_THEMES['bf']:
1464 fw("html_theme_path = ['../']\n")
1465 # copied with the theme, exclude else we get an error [#28873]
1466 fw("html_favicon = 'favicon.ico'\n") # in <theme>/static/
1468 # not helpful since the source is generated, adds to upload size.
1469 fw("html_copy_source = False\n")
1472 # needed for latex, pdf gen
1473 fw("latex_documents = [ ('contents', 'contents.tex', 'Blender Index', 'Blender Foundation', 'manual'), ]\n")
1474 fw("latex_paper_size = 'a4paper'\n")
1478 def execfile(filepath):
1479 global_namespace = {"__file__": filepath, "__name__": "__main__"}
1480 exec(compile(open(filepath).read(), filepath, 'exec'), global_namespace)
1483 def write_rst_contents(basepath):
1485 Write the rst file of the main page, needed for sphinx (index.html)
1487 filepath = os.path.join(basepath, "contents.rst")
1488 file = open(filepath, "w", encoding="utf-8")
1491 fw(title_string("Blender Documentation Contents", "%", double=True))
1493 fw("Welcome, this document is an API reference for Blender %s, built %s.\n" % (BLENDER_VERSION_DOTS, BLENDER_DATE))
1496 # fw("`A PDF version of this document is also available <%s>`_\n" % BLENDER_PDF_FILENAME)
1497 fw("`A compressed ZIP file of this site is available <%s>`_\n" % BLENDER_ZIP_FILENAME)
1501 if not EXCLUDE_INFO_DOCS:
1502 fw(title_string("Blender/Python Documentation", "=", double=True))
1504 fw(".. toctree::\n")
1505 fw(" :maxdepth: 1\n\n")
1506 for info, info_desc in INFO_DOCS:
1507 fw(" %s <%s>\n\n" % (info_desc, info))
1510 fw(title_string("Application Modules", "=", double=True))
1511 fw(".. toctree::\n")
1512 fw(" :maxdepth: 1\n\n")
1515 "bpy.context", # note: not actually a module
1516 "bpy.data", # note: not actually a module
1530 for mod in app_modules:
1531 if mod not in EXCLUDE_MODULES:
1534 fw(title_string("Standalone Modules", "=", double=True))
1535 fw(".. toctree::\n")
1536 fw(" :maxdepth: 1\n\n")
1538 standalone_modules = (
1540 "mathutils", "mathutils.geometry", "mathutils.noise",
1542 "bgl", "blf", "gpu", "aud", "bpy_extras",
1543 # bmesh, submodules are in own page
1547 for mod in standalone_modules:
1548 if mod not in EXCLUDE_MODULES:
1551 # special case, this 'bmesh.ops.rst' is extracted from C source
1552 if "bmesh.ops" not in EXCLUDE_MODULES:
1553 execfile(os.path.join(SCRIPT_DIR, "rst_from_bmesh_opdefines.py"))
1556 if "bge" not in EXCLUDE_MODULES:
1557 fw(title_string("Game Engine Modules", "=", double=True))
1558 fw(".. toctree::\n")
1559 fw(" :maxdepth: 1\n\n")
1560 fw(" bge.types.rst\n\n")
1561 fw(" bge.logic.rst\n\n")
1562 fw(" bge.render.rst\n\n")
1563 fw(" bge.texture.rst\n\n")
1564 fw(" bge.events.rst\n\n")
1565 fw(" bge.constraints.rst\n\n")
1567 # rna generated change log
1568 fw(title_string("API Info", "=", double=True))
1569 fw(".. toctree::\n")
1570 fw(" :maxdepth: 1\n\n")
1571 fw(" change_log.rst\n\n")
1575 fw(".. note:: The Blender Python API has areas which are still in development.\n")
1577 fw(" The following areas are subject to change.\n")
1578 fw(" * operator behavior, names and arguments\n")
1579 fw(" * mesh creation and editing functions\n")
1581 fw(" These parts of the API are relatively stable and are unlikely to change significantly\n")
1582 fw(" * data API, access to attributes of blender data such as mesh verts, material color, timeline frames and scene objects\n")
1583 fw(" * user interface functions for defining buttons, creation of menus, headers, panels\n")
1584 fw(" * render engine integration\n")
1585 fw(" * modules: bgl, mathutils & game engine.\n")
1591 def write_rst_bpy(basepath):
1593 Write rst file of bpy module (disabled by default)
1596 filepath = os.path.join(basepath, "bpy.rst")
1597 file = open(filepath, "w", encoding="utf-8")
1602 title = ":mod:`bpy` --- Blender Python Module"
1604 fw(title_string(title, "="))
1606 fw(".. module:: bpy.types\n\n")
1610 def write_rst_types_index(basepath):
1612 Write the rst file of bpy.types module (index)
1614 if "bpy.types" not in EXCLUDE_MODULES:
1615 filepath = os.path.join(basepath, "bpy.types.rst")
1616 file = open(filepath, "w", encoding="utf-8")
1618 fw(title_string("Types (bpy.types)", "="))
1619 fw(".. toctree::\n")
1621 fw(" bpy.types.*\n\n")
1625 def write_rst_ops_index(basepath):
1627 Write the rst file of bpy.ops module (index)
1629 if "bpy.ops" not in EXCLUDE_MODULES:
1630 filepath = os.path.join(basepath, "bpy.ops.rst")
1631 file = open(filepath, "w", encoding="utf-8")
1633 fw(title_string("Operators (bpy.ops)", "="))
1634 write_example_ref("", fw, "bpy.ops")
1635 fw(".. toctree::\n")
1637 fw(" bpy.ops.*\n\n")
1641 def write_rst_data(basepath):
1643 Write the rst file of bpy.data module
1645 if "bpy.data" not in EXCLUDE_MODULES:
1646 # not actually a module, only write this file so we
1647 # can reference in the TOC
1648 filepath = os.path.join(basepath, "bpy.data.rst")
1649 file = open(filepath, "w", encoding="utf-8")
1651 fw(title_string("Data Access (bpy.data)", "="))
1652 fw(".. module:: bpy\n")
1654 fw("This module is used for all blender/python access.\n")
1656 fw(".. data:: data\n")
1658 fw(" Access to blenders internal data\n")
1660 fw(" :type: :class:`bpy.types.BlendData`\n")
1662 fw(".. literalinclude:: ../examples/bpy.data.py\n")
1665 EXAMPLE_SET_USED.add("bpy.data")
1668 def write_rst_importable_modules(basepath):
1670 Write the rst files of importable modules
1672 importable_modules = {
1674 "bpy.path" : "Path Utilities",
1675 "bpy.utils" : "Utilities",
1676 "bpy_extras" : "Extra Utilities",
1679 "aud" : "Audio System",
1680 "blf" : "Font Drawing",
1681 "bmesh" : "BMesh Module",
1682 "bmesh.types" : "BMesh Types",
1683 "bmesh.utils" : "BMesh Utilities",
1684 "bpy.app" : "Application Data",
1685 "bpy.app.handlers" : "Application Handlers",
1686 "bpy.props" : "Property Definitions",
1687 "mathutils" : "Math Types & Utilities",
1688 "mathutils.geometry": "Geometry Utilities",
1689 "mathutils.noise" : "Noise Utilities",
1691 for mod_name, mod_descr in importable_modules.items():
1692 if mod_name not in EXCLUDE_MODULES:
1693 module = __import__(mod_name,
1694 fromlist=[mod_name.rsplit(".", 1)[-1]])
1695 pymodule2sphinx(basepath, mod_name, module, mod_descr)
1698 def copy_handwritten_rsts(basepath):
1701 if not EXCLUDE_INFO_DOCS:
1702 for info, info_desc in INFO_DOCS:
1703 shutil.copy2(os.path.join(RST_DIR, info), basepath)
1705 # TODO put this docs in blender's code and use import as per modules above
1706 handwritten_modules = [
1713 "bgl", # "Blender OpenGl wrapper"
1714 "gpu", # "GPU Shader Module"
1716 "bmesh.ops", # generated by rst_from_bmesh_opdefines.py
1721 for mod_name in handwritten_modules:
1722 if mod_name not in EXCLUDE_MODULES:
1723 # copy2 keeps time/date stamps
1724 shutil.copy2(os.path.join(RST_DIR, "%s.rst" % mod_name), basepath)
1727 shutil.copy2(os.path.join(RST_DIR, "change_log.rst"), basepath)
1729 # copy images, could be smarter but just glob for now.
1730 for f in os.listdir(RST_DIR):
1731 if f.endswith(".png"):
1732 shutil.copy2(os.path.join(RST_DIR, f), basepath)
1735 def rna2sphinx(basepath):
1743 write_sphinx_conf_py(basepath)
1746 write_rst_contents(basepath)
1749 if "bpy.context" not in EXCLUDE_MODULES:
1750 # one of a kind, context doc (uses ctypes to extract info!)
1751 # doesn't work on mac
1752 if PLATFORM != "darwin":
1753 pycontext2sphinx(basepath)
1756 write_rst_bpy(basepath) # bpy, disabled by default
1757 write_rst_types_index(basepath) # bpy.types
1758 write_rst_ops_index(basepath) # bpy.ops
1759 pyrna2sphinx(basepath) # bpy.types.* and bpy.ops.*
1760 write_rst_data(basepath) # bpy.data
1761 write_rst_importable_modules(basepath)
1763 # copy the other rsts
1764 copy_handwritten_rsts(basepath)
1767 def align_sphinx_in_to_sphinx_in_tmp():
1769 Move changed files from SPHINX_IN_TMP to SPHINX_IN
1773 sphinx_in_files = set(os.listdir(SPHINX_IN))
1774 sphinx_in_tmp_files = set(os.listdir(SPHINX_IN_TMP))
1776 # remove deprecated files that have been removed
1777 for f in sorted(sphinx_in_files):
1778 if f not in sphinx_in_tmp_files:
1779 BPY_LOGGER.debug("\tdeprecated: %s" % f)
1780 os.remove(os.path.join(SPHINX_IN, f))
1782 # freshen with new files.
1783 for f in sorted(sphinx_in_tmp_files):
1784 f_from = os.path.join(SPHINX_IN_TMP, f)
1785 f_to = os.path.join(SPHINX_IN, f)
1788 if f in sphinx_in_files:
1789 if filecmp.cmp(f_from, f_to):
1793 BPY_LOGGER.debug("\tupdating: %s" % f)
1794 shutil.copy(f_from, f_to)
1797 def refactor_sphinx_log(sphinx_logfile):
1799 with open(sphinx_logfile, "r", encoding="utf-8") as original_logfile:
1800 lines = set(original_logfile.readlines())
1802 if 'warning' in line.lower() or 'error' in line.lower():
1803 line = line.strip().split(None, 2)
1805 location, kind, msg = line
1806 location = os.path.relpath(location, start=SPHINX_IN)
1807 refactored_log.append((kind, location, msg))
1808 with open(sphinx_logfile, "w", encoding="utf-8") as refactored_logfile:
1809 for log in sorted(refactored_log):
1810 refactored_logfile.write("%-12s %s\n %s\n" % log)
1815 # eventually, create the dirs
1816 for dir_path in [ARGS.output_dir, SPHINX_IN]:
1817 if not os.path.exists(dir_path):
1820 # eventually, log in files
1822 bpy_logfile = os.path.join(ARGS.output_dir, ".bpy.log")
1823 bpy_logfilehandler = logging.FileHandler(bpy_logfile, mode="w")
1824 bpy_logfilehandler.setLevel(logging.DEBUG)
1825 BPY_LOGGER.addHandler(bpy_logfilehandler)
1827 # using a FileHandler seems to disable the stdout, so we add a StreamHandler
1828 bpy_log_stdout_handler = logging.StreamHandler(stream=sys.stdout)
1829 bpy_log_stdout_handler.setLevel(logging.DEBUG)
1830 BPY_LOGGER.addHandler(bpy_log_stdout_handler)
1832 # in case of out-of-source build, copy the needed dirs
1833 if ARGS.output_dir != SCRIPT_DIR:
1835 examples_dir_copy = os.path.join(ARGS.output_dir, "examples")
1836 if os.path.exists(examples_dir_copy):
1837 shutil.rmtree(examples_dir_copy, True)
1838 shutil.copytree(EXAMPLES_DIR,
1840 ignore=shutil.ignore_patterns(*(".svn",)),
1841 copy_function=shutil.copy)
1843 # eventually, copy the theme dir
1844 if ARGS.sphinx_theme in SPHINX_THEMES['bf']:
1845 if os.path.exists(SPHINX_THEME_DIR):
1846 shutil.rmtree(SPHINX_THEME_DIR, True)
1847 shutil.copytree(SPHINX_THEME_SVN_DIR,
1849 ignore=shutil.ignore_patterns(*(".svn",)),
1850 copy_function=shutil.copy)
1852 # dump the api in rst files
1853 if os.path.exists(SPHINX_IN_TMP):
1854 shutil.rmtree(SPHINX_IN_TMP, True)
1856 rna2sphinx(SPHINX_IN_TMP)
1858 if ARGS.full_rebuild:
1859 # only for full updates
1860 shutil.rmtree(SPHINX_IN, True)
1861 shutil.copytree(SPHINX_IN_TMP,
1863 copy_function=shutil.copy)
1864 if ARGS.sphinx_build and os.path.exists(SPHINX_OUT):
1865 shutil.rmtree(SPHINX_OUT, True)
1866 if ARGS.sphinx_build_pdf and os.path.exists(SPHINX_OUT_PDF):
1867 shutil.rmtree(SPHINX_OUT_PDF, True)
1869 # move changed files in SPHINX_IN
1870 align_sphinx_in_to_sphinx_in_tmp()
1872 # report which example files weren't used
1873 EXAMPLE_SET_UNUSED = EXAMPLE_SET - EXAMPLE_SET_USED
1874 if EXAMPLE_SET_UNUSED:
1875 BPY_LOGGER.debug("\nUnused examples found in '%s'..." % EXAMPLES_DIR)
1876 for f in sorted(EXAMPLE_SET_UNUSED):
1877 BPY_LOGGER.debug(" %s.py" % f)
1878 BPY_LOGGER.debug(" %d total\n" % len(EXAMPLE_SET_UNUSED))
1880 # eventually, build the html docs
1881 if ARGS.sphinx_build:
1883 subprocess.call(SPHINX_BUILD)
1885 # sphinx-build log cleanup+sort
1887 if os.stat(SPHINX_BUILD_LOG).st_size:
1888 refactor_sphinx_log(SPHINX_BUILD_LOG)
1890 # eventually, build the pdf docs
1891 if ARGS.sphinx_build_pdf:
1893 subprocess.call(SPHINX_BUILD_PDF)
1894 subprocess.call(SPHINX_MAKE_PDF, stdout=SPHINX_MAKE_PDF_STDOUT)
1896 # sphinx-build log cleanup+sort
1898 if os.stat(SPHINX_BUILD_PDF_LOG).st_size:
1899 refactor_sphinx_log(SPHINX_BUILD_PDF_LOG)
1901 # eventually, prepare the dir to be deployed online (REFERENCE_PATH)
1902 if ARGS.pack_reference:
1904 if ARGS.sphinx_build:
1905 # delete REFERENCE_PATH
1906 if os.path.exists(REFERENCE_PATH):
1907 shutil.rmtree(REFERENCE_PATH, True)
1909 # copy SPHINX_OUT to the REFERENCE_PATH
1910 ignores = ('.doctrees', 'objects.inv', '.buildinfo')
1911 shutil.copytree(SPHINX_OUT,
1913 ignore=shutil.ignore_patterns(*ignores))
1914 shutil.copy(os.path.join(REFERENCE_PATH, "contents.html"),
1915 os.path.join(REFERENCE_PATH, "index.html"))
1917 # zip REFERENCE_PATH
1918 basename = os.path.join(ARGS.output_dir, REFERENCE_NAME)
1919 tmp_path = shutil.make_archive(basename, 'zip',
1920 root_dir=ARGS.output_dir,
1921 base_dir=REFERENCE_NAME)
1922 final_path = os.path.join(REFERENCE_PATH, BLENDER_ZIP_FILENAME)
1923 os.rename(tmp_path, final_path)
1925 if ARGS.sphinx_build_pdf:
1926 # copy the pdf to REFERENCE_PATH
1927 shutil.copy(os.path.join(SPHINX_OUT_PDF, "contents.pdf"),
1928 os.path.join(REFERENCE_PATH, BLENDER_PDF_FILENAME))
1933 if __name__ == '__main__':