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
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
254 "bpy.ops", # supports filtering
257 "bpy.types", # supports filtering
262 "mathutils.geometry",
269 # TODO, support bpy.ops and bpy.types filtering
272 EXCLUDE_MODULES = [m for m in EXCLUDE_MODULES if not fnmatch.fnmatchcase(m, ARGS.partial)]
274 # special support for bpy.types.XXX
275 FILTER_BPY_OPS = tuple([m[8:] for m in ARGS.partial.split(":") if m.startswith("bpy.ops.")])
277 EXCLUDE_MODULES.remove("bpy.ops")
279 FILTER_BPY_TYPES = tuple([m[10:] for m in ARGS.partial.split(":") if m.startswith("bpy.types.")])
281 EXCLUDE_MODULES.remove("bpy.types")
283 print(FILTER_BPY_TYPES)
285 EXCLUDE_INFO_DOCS = (not fnmatch.fnmatchcase("info", ARGS.partial))
290 BPY_LOGGER.debug("Partial Doc Build, Skipping: %s\n" % "\n ".join(sorted(EXCLUDE_MODULES)))
299 BPY_LOGGER.debug("Warning: Built without 'aud' module, docs incomplete...")
300 EXCLUDE_MODULES = EXCLUDE_MODULES + ("aud", )
303 EXAMPLES_DIR = os.path.abspath(os.path.join(SCRIPT_DIR, "examples"))
305 for f in os.listdir(EXAMPLES_DIR):
306 if f.endswith(".py"):
307 EXAMPLE_SET.add(os.path.splitext(f)[0])
308 EXAMPLE_SET_USED = set()
311 RST_DIR = os.path.abspath(os.path.join(SCRIPT_DIR, "rst"))
313 # extra info, not api reference docs
314 # stored in ./rst/info_*
316 ("info_quickstart.rst", "Blender/Python Quickstart: new to blender/scripting and want to get your feet wet?"),
317 ("info_overview.rst", "Blender/Python API Overview: a more complete explanation of python integration"),
318 ("info_best_practice.rst", "Best Practice: Conventions to follow for writing good scripts"),
319 ("info_tips_and_tricks.rst", "Tips and Tricks: Hints to help you while writing scripts for blender"),
320 ("info_gotcha.rst", "Gotcha's: some of the problems you may come up against when writing scripts"),
323 # only support for properties atm.
325 # XXX messes up PDF!, really a bug but for now just workaround.
326 "UserPreferencesSystem": {"language", }
331 ("Base Mesh Type", '-'),
333 ("Mesh Elements", '-'),
338 ("Sequence Accessors", '-'),
345 ("Selection History", '-'),
348 ("Custom-Data Layer Access", '-'),
355 ("Custom-Data Layer Types", '-'),
361 # --------------------configure compile time options----------------------------
363 # -------------------------------BLENDER----------------------------------------
365 blender_version_strings = [str(v) for v in bpy.app.version]
367 # converting bytes to strings, due to #30154
368 BLENDER_REVISION = str(bpy.app.build_revision, 'utf_8')
369 BLENDER_DATE = str(bpy.app.build_date, 'utf_8')
371 BLENDER_VERSION_DOTS = ".".join(blender_version_strings) # '2.62.1'
372 if BLENDER_REVISION != "Unknown":
373 BLENDER_VERSION_DOTS += " r" + BLENDER_REVISION # '2.62.1 r44584'
375 BLENDER_VERSION_PATH = "_".join(blender_version_strings) # '2_62_1'
376 if bpy.app.version_cycle == "release":
377 BLENDER_VERSION_PATH = "%s%s_release" % ("_".join(blender_version_strings[:2]),
378 bpy.app.version_char) # '2_62_release'
380 # --------------------------DOWNLOADABLE FILES----------------------------------
382 REFERENCE_NAME = "blender_python_reference_%s" % BLENDER_VERSION_PATH
383 REFERENCE_PATH = os.path.join(ARGS.output_dir, REFERENCE_NAME)
384 BLENDER_PDF_FILENAME = "%s.pdf" % REFERENCE_NAME
385 BLENDER_ZIP_FILENAME = "%s.zip" % REFERENCE_NAME
387 # -------------------------------SPHINX-----------------------------------------
389 SPHINX_THEMES = {'bf': ['blender-org'], # , 'naiad',
400 available_themes = SPHINX_THEMES['bf'] + SPHINX_THEMES['sphinx']
401 if ARGS.sphinx_theme not in available_themes:
402 print("Please choose a theme among: %s" % ', '.join(available_themes))
405 if ARGS.sphinx_theme in SPHINX_THEMES['bf']:
406 SPHINX_THEME_DIR = os.path.join(ARGS.output_dir, ARGS.sphinx_theme)
407 SPHINX_THEME_SVN_DIR = os.path.join(SCRIPT_DIR, ARGS.sphinx_theme)
409 SPHINX_IN = os.path.join(ARGS.output_dir, "sphinx-in")
410 SPHINX_IN_TMP = SPHINX_IN + "-tmp"
411 SPHINX_OUT = os.path.join(ARGS.output_dir, "sphinx-out")
412 if ARGS.sphinx_named_output:
413 SPHINX_OUT += "_%s" % ARGS.sphinx_theme
416 if ARGS.sphinx_build:
417 SPHINX_BUILD = ["sphinx-build", SPHINX_IN, SPHINX_OUT]
420 SPHINX_BUILD_LOG = os.path.join(ARGS.output_dir, ".sphinx-build.log")
421 SPHINX_BUILD = ["sphinx-build",
422 "-w", SPHINX_BUILD_LOG,
423 SPHINX_IN, SPHINX_OUT]
426 if ARGS.sphinx_build_pdf:
427 SPHINX_OUT_PDF = os.path.join(ARGS.output_dir, "sphinx-out_pdf")
428 SPHINX_BUILD_PDF = ["sphinx-build",
430 SPHINX_IN, SPHINX_OUT_PDF]
431 SPHINX_MAKE_PDF = ["make", "-C", SPHINX_OUT_PDF]
432 SPHINX_MAKE_PDF_STDOUT = None
435 SPHINX_BUILD_PDF_LOG = os.path.join(ARGS.output_dir, ".sphinx-build_pdf.log")
436 SPHINX_BUILD_PDF = ["sphinx-build", "-b", "latex",
437 "-w", SPHINX_BUILD_PDF_LOG,
438 SPHINX_IN, SPHINX_OUT_PDF]
440 sphinx_make_pdf_log = os.path.join(ARGS.output_dir, ".latex_make.log")
441 SPHINX_MAKE_PDF_STDOUT = open(sphinx_make_pdf_log, "w", encoding="utf-8")
443 # --------------------------------API DUMP--------------------------------------
445 # lame, python wont give some access
446 ClassMethodDescriptorType = type(dict.__dict__['fromkeys'])
447 MethodDescriptorType = type(dict.get)
448 GetSetDescriptorType = type(int.real)
449 from types import MemberDescriptorType
451 _BPY_STRUCT_FAKE = "bpy_struct"
452 _BPY_PROP_COLLECTION_FAKE = "bpy_prop_collection"
454 if _BPY_PROP_COLLECTION_FAKE:
455 _BPY_PROP_COLLECTION_ID = ":class:`%s`" % _BPY_PROP_COLLECTION_FAKE
457 _BPY_PROP_COLLECTION_ID = "collection"
460 def is_struct_seq(value):
461 return isinstance(value, tuple) and type(tuple) != tuple and hasattr(value, "n_fields")
464 def undocumented_message(module_name, type_name, identifier):
465 if str(type_name).startswith('<module'):
466 preloadtitle = '%s.%s' % (module_name, identifier)
468 preloadtitle = '%s.%s.%s' % (module_name, type_name, identifier)
469 message = ("Undocumented (`contribute "
470 "<http://wiki.blender.org/index.php/"
471 "Dev:2.5/Py/API/Generating_API_Reference/Contribute"
474 "&preload=Dev:2.5/Py/API/Generating_API_Reference/Contribute/Howto-message"
475 "&preloadtitle=%s>`_)\n\n" % preloadtitle)
481 Converts values to strings for the range directive.
482 (unused function it seems)
488 elif type(val) == float:
494 def example_extract_docstring(filepath):
495 file = open(filepath, "r", encoding="utf-8")
496 line = file.readline()
499 if line.startswith('"""'): # assume nothing here
505 for line in file.readlines():
507 if line.startswith('"""'):
510 text.append(line.rstrip())
514 return "\n".join(text), line_no
517 def title_string(text, heading_char, double=False):
518 filler = len(text) * heading_char
521 return "%s\n%s\n%s\n\n" % (filler, text, filler)
523 return "%s\n%s\n\n" % (text, filler)
526 def write_example_ref(ident, fw, example_id, ext="py"):
527 if example_id in EXAMPLE_SET:
529 # extract the comment
530 filepath = os.path.join("..", "examples", "%s.%s" % (example_id, ext))
531 filepath_full = os.path.join(os.path.dirname(fw.__self__.name), filepath)
533 text, line_no = example_extract_docstring(filepath_full)
535 for line in text.split("\n"):
536 fw("%s\n" % (ident + line).rstrip())
539 fw("%s.. literalinclude:: %s\n" % (ident, filepath))
541 fw("%s :lines: %d-\n" % (ident, line_no))
543 EXAMPLE_SET_USED.add(example_id)
546 BPY_LOGGER.debug("\tskipping example: " + example_id)
548 # Support for numbered files bpy.types.Operator -> bpy.types.Operator.1.py
551 example_id_num = "%s.%d" % (example_id, i)
552 if example_id_num in EXAMPLE_SET:
553 write_example_ref(ident, fw, example_id_num, ext)
559 def write_indented_lines(ident, fn, text, strip=True):
561 Apply same indentation to all lines in a multilines text.
566 lines = text.split("\n")
568 # strip empty lines from the start/end
569 while lines and not lines[0].strip():
571 while lines and not lines[-1].strip():
575 # set indentation to <indent>
579 ident_strip = min(ident_strip, len(l) - len(l.lstrip()))
581 fn(ident + l[ident_strip:] + "\n")
583 # add <indent> number of blanks to the current indentation
588 def pymethod2sphinx(ident, fw, identifier, py_func):
590 class method to sphinx
592 arg_str = inspect.formatargspec(*inspect.getargspec(py_func))
593 if arg_str.startswith("(self, "):
594 arg_str = "(" + arg_str[7:]
596 elif arg_str.startswith("(cls, "):
597 arg_str = "(" + arg_str[6:]
598 func_type = "classmethod"
600 func_type = "staticmethod"
602 fw(ident + ".. %s:: %s%s\n\n" % (func_type, identifier, arg_str))
604 write_indented_lines(ident + " ", fw, py_func.__doc__)
608 def pyfunc2sphinx(ident, fw, identifier, py_func, is_class=True):
610 function or class method to sphinx
612 arg_str = inspect.formatargspec(*inspect.getargspec(py_func))
615 func_type = "function"
617 # ther rest are class methods
618 elif arg_str.startswith("(self, "):
619 arg_str = "(" + arg_str[7:]
621 elif arg_str.startswith("(cls, "):
622 arg_str = "(" + arg_str[6:]
623 func_type = "classmethod"
625 func_type = "staticmethod"
627 fw(ident + ".. %s:: %s%s\n\n" % (func_type, identifier, arg_str))
629 write_indented_lines(ident + " ", fw, py_func.__doc__)
633 def py_descr2sphinx(ident, fw, descr, module_name, type_name, identifier):
634 if identifier.startswith("_"):
639 doc = undocumented_message(module_name, type_name, identifier)
641 if type(descr) == GetSetDescriptorType:
642 fw(ident + ".. attribute:: %s\n\n" % identifier)
643 write_indented_lines(ident + " ", fw, doc, False)
645 elif type(descr) == MemberDescriptorType: # same as above but use 'data'
646 fw(ident + ".. data:: %s\n\n" % identifier)
647 write_indented_lines(ident + " ", fw, doc, False)
649 elif type(descr) in (MethodDescriptorType, ClassMethodDescriptorType):
650 write_indented_lines(ident, fw, doc, False)
653 raise TypeError("type was not GetSetDescriptorType, MethodDescriptorType or ClassMethodDescriptorType")
655 write_example_ref(ident + " ", fw, module_name + "." + type_name + "." + identifier)
659 def py_c_func2sphinx(ident, fw, module_name, type_name, identifier, py_func, is_class=True):
661 c defined function to sphinx.
664 # dump the docstring, assume its formatted correctly
666 write_indented_lines(ident, fw, py_func.__doc__, False)
669 fw(ident + ".. function:: %s()\n\n" % identifier)
670 fw(ident + " " + undocumented_message(module_name, type_name, identifier))
673 write_example_ref(ident + " ", fw, module_name + "." + type_name + "." + identifier)
675 write_example_ref(ident + " ", fw, module_name + "." + identifier)
680 def pyprop2sphinx(ident, fw, identifier, py_prop):
682 python property to sphinx
684 # readonly properties use "data" directive, variables use "attribute" directive
685 if py_prop.fset is None:
686 fw(ident + ".. data:: %s\n\n" % identifier)
688 fw(ident + ".. attribute:: %s\n\n" % identifier)
689 write_indented_lines(ident + " ", fw, py_prop.__doc__)
690 if py_prop.fset is None:
691 fw(ident + " (readonly)\n\n")
694 def pymodule2sphinx(basepath, module_name, module, title):
696 attribute_set = set()
697 filepath = os.path.join(basepath, module_name + ".rst")
699 module_all = getattr(module, "__all__", None)
700 module_dir = sorted(dir(module))
703 module_dir = module_all
705 # TODO - currently only used for classes
707 module_grouping = MODULE_GROUPING.get(module_name)
709 def module_grouping_index(name):
710 if module_grouping is not None:
712 return module_grouping.index(name)
717 def module_grouping_heading(name):
718 if module_grouping is not None:
719 i = module_grouping_index(name) - 1
720 if i >= 0 and type(module_grouping[i]) == tuple:
721 return module_grouping[i]
724 def module_grouping_sort_key(name):
725 return module_grouping_index(name)
726 # done grouping support
728 file = open(filepath, "w", encoding="utf-8")
732 fw(title_string("%s (%s)" % (title, module_name), "="))
734 fw(".. module:: %s\n\n" % module_name)
737 # Note, may contain sphinx syntax, dont mangle!
738 fw(module.__doc__.strip())
741 write_example_ref("", fw, module_name)
744 # we could also scan files but this ensures __all__ is used correctly
745 if module_all is not None:
749 for submod_name in module_all:
751 exec_str = "from %s import %s as submod" % (module.__name__, submod_name)
752 exec(exec_str, ns, ns)
753 submod = ns["submod"]
754 if type(submod) == types.ModuleType:
755 submod_ls.append((submod_name, submod))
762 fw(" :maxdepth: 1\n\n")
764 for submod_name, submod in submod_ls:
765 submod_name_full = "%s.%s" % (module_name, submod_name)
766 fw(" %s.rst\n\n" % submod_name_full)
768 pymodule2sphinx(basepath, submod_name_full, submod, "%s submodule" % module_name)
770 # done writing submodules!
772 # write members of the module
773 # only tested with PyStructs which are not exactly modules
774 for key, descr in sorted(type(module).__dict__.items()):
775 if key.startswith("__"):
777 # naughty, we also add getset's into PyStructs, this is not typical py but also not incorrect.
779 # type_name is only used for examples and messages
780 type_name = str(type(module)).strip("<>").split(" ", 1)[-1][1:-1] # "<class 'bpy.app.handlers'>" --> bpy.app.handlers
781 if type(descr) == types.GetSetDescriptorType:
782 py_descr2sphinx("", fw, descr, module_name, type_name, key)
783 attribute_set.add(key)
785 for key, descr in sorted(type(module).__dict__.items()):
786 if key.startswith("__"):
789 if type(descr) == MemberDescriptorType:
791 value = getattr(module, key, None)
793 value_type = type(value)
794 descr_sorted.append((key, descr, value, type(value)))
795 # sort by the valye type
796 descr_sorted.sort(key=lambda descr_data: str(descr_data[3]))
797 for key, descr, value, value_type in descr_sorted:
799 # must be documented as a submodule
800 if is_struct_seq(value):
803 type_name = value_type.__name__
804 py_descr2sphinx("", fw, descr, module_name, type_name, key)
806 attribute_set.add(key)
808 del key, descr, descr_sorted
813 # use this list so we can sort by type
814 module_dir_value_type = []
816 for attribute in module_dir:
817 if attribute.startswith("_"):
820 if attribute in attribute_set:
823 if attribute.startswith("n_"): # annoying exception, needed for bpy.app
826 # workaround for bpy.app documenting .index() and .count()
827 if isinstance(module, tuple) and hasattr(tuple, attribute):
830 value = getattr(module, attribute)
832 module_dir_value_type.append((attribute, value, type(value)))
834 # sort by str of each type
835 # this way lists, functions etc are grouped.
836 module_dir_value_type.sort(key=lambda triple: str(triple[2]))
838 for attribute, value, value_type in module_dir_value_type:
839 if value_type == types.FunctionType:
840 pyfunc2sphinx("", fw, attribute, value, is_class=False)
841 elif value_type in (types.BuiltinMethodType, types.BuiltinFunctionType): # both the same at the moment but to be future proof
842 # note: can't get args from these, so dump the string as is
843 # this means any module used like this must have fully formatted docstrings.
844 py_c_func2sphinx("", fw, module_name, None, attribute, value, is_class=False)
845 elif value_type == type:
846 classes.append((attribute, value))
847 elif issubclass(value_type, types.ModuleType):
848 submodules.append((attribute, value))
849 elif value_type in (bool, int, float, str, tuple):
850 # constant, not much fun we can do here except to list it.
851 # TODO, figure out some way to document these!
852 fw(".. data:: %s\n\n" % attribute)
853 write_indented_lines(" ", fw, "constant value %s" % repr(value), False)
856 BPY_LOGGER.debug("\tnot documenting %s.%s of %r type" % (module_name, attribute, value_type.__name__))
859 attribute_set.add(attribute)
860 # TODO, more types...
861 del module_dir_value_type
863 # TODO, bpy_extras does this already, mathutils not.
872 for attribute, submod in submodules:
873 fw("* :mod:`%s.%s`\n" % (module_name, attribute))
877 if module_grouping is not None:
878 classes.sort(key=lambda pair: module_grouping_sort_key(pair[0]))
880 # write collected classes now
881 for (type_name, value) in classes:
883 if module_grouping is not None:
884 heading, heading_char = module_grouping_heading(type_name)
886 fw(title_string(heading, heading_char))
888 # May need to be its own function
889 fw(".. class:: %s\n\n" % type_name)
891 write_indented_lines(" ", fw, value.__doc__, False)
893 write_example_ref(" ", fw, module_name + "." + type_name)
895 descr_items = [(key, descr) for key, descr in sorted(value.__dict__.items()) if not key.startswith("__")]
897 for key, descr in descr_items:
898 if type(descr) == ClassMethodDescriptorType:
899 py_descr2sphinx(" ", fw, descr, module_name, type_name, key)
901 for key, descr in descr_items:
902 if type(descr) == MethodDescriptorType:
903 py_descr2sphinx(" ", fw, descr, module_name, type_name, key)
905 for key, descr in descr_items:
906 if type(descr) == GetSetDescriptorType:
907 py_descr2sphinx(" ", fw, descr, module_name, type_name, key)
913 # Changes in blender will force errors here
915 "active_base": ("ObjectBase", False),
916 "active_bone": ("EditBone", False),
917 "active_object": ("Object", False),
918 "active_operator": ("Operator", False),
919 "active_pose_bone": ("PoseBone", False),
920 "active_node": ("Node", False),
921 "armature": ("Armature", False),
922 "bone": ("Bone", False),
923 "brush": ("Brush", False),
924 "camera": ("Camera", False),
925 "cloth": ("ClothModifier", False),
926 "collision": ("CollisionModifier", False),
927 "curve": ("Curve", False),
928 "dynamic_paint": ("DynamicPaintModifier", False),
929 "edit_bone": ("EditBone", False),
930 "edit_image": ("Image", False),
931 "edit_mask": ("Mask", False),
932 "edit_movieclip": ("MovieClip", False),
933 "edit_object": ("Object", False),
934 "edit_text": ("Text", False),
935 "editable_bones": ("EditBone", True),
936 "fluid": ("FluidSimulationModifier", False),
937 "image_paint_object": ("Object", False),
938 "lamp": ("Lamp", False),
939 "lattice": ("Lattice", False),
940 "material": ("Material", False),
941 "material_slot": ("MaterialSlot", False),
942 "mesh": ("Mesh", False),
943 "meta_ball": ("MetaBall", False),
944 "object": ("Object", False),
945 "particle_edit_object": ("Object", False),
946 "particle_settings": ("ParticleSettings", False),
947 "particle_system": ("ParticleSystem", False),
948 "particle_system_editable": ("ParticleSystem", False),
949 "pose_bone": ("PoseBone", False),
950 "scene": ("Scene", False),
951 "sculpt_object": ("Object", False),
952 "selectable_bases": ("ObjectBase", True),
953 "selectable_objects": ("Object", True),
954 "selected_bases": ("ObjectBase", True),
955 "selected_bones": ("EditBone", True),
956 "selected_editable_bases": ("ObjectBase", True),
957 "selected_editable_bones": ("EditBone", True),
958 "selected_editable_objects": ("Object", True),
959 "selected_editable_sequences": ("Sequence", True),
960 "selected_nodes": ("Node", True),
961 "selected_objects": ("Object", True),
962 "selected_pose_bones": ("PoseBone", True),
963 "selected_sequences": ("Sequence", True),
964 "sequences": ("Sequence", True),
965 "smoke": ("SmokeModifier", False),
966 "soft_body": ("SoftBodyModifier", False),
967 "speaker": ("Speaker", False),
968 "texture": ("Texture", False),
969 "texture_slot": ("MaterialTextureSlot", False),
970 "texture_user": ("ID", False),
971 "vertex_paint_object": ("Object", False),
972 "visible_bases": ("ObjectBase", True),
973 "visible_bones": ("EditBone", True),
974 "visible_objects": ("Object", True),
975 "visible_pose_bones": ("PoseBone", True),
976 "weight_paint_object": ("Object", False),
977 "world": ("World", False),
980 def pycontext2sphinx(basepath):
981 # Only use once. very irregular
983 filepath = os.path.join(basepath, "bpy.context.rst")
984 file = open(filepath, "w", encoding="utf-8")
986 fw(title_string("Context Access (bpy.context)", "="))
987 fw(".. module:: bpy.context\n")
989 fw("The context members available depend on the area of blender which is currently being accessed.\n")
991 fw("Note that all context values are readonly, but may be modified through the data api or by running operators\n\n")
993 # nasty, get strings directly from blender because there is no other way to get it
997 "screen_context_dir",
998 "view3d_context_dir",
999 "buttons_context_dir",
1000 "image_context_dir",
1004 "sequencer_context_dir",
1009 blend_cdll = ctypes.CDLL("")
1010 for ctx_str in context_strings:
1011 subsection = "%s Context" % ctx_str.split("_")[0].title()
1012 fw("\n%s\n%s\n\n" % (subsection, (len(subsection) * '-')))
1014 attr = ctypes.addressof(getattr(blend_cdll, ctx_str))
1015 c_char_p_p = ctypes.POINTER(ctypes.c_char_p)
1016 char_array = c_char_p_p.from_address(attr)
1018 while char_array[i] is not None:
1019 member = ctypes.string_at(char_array[i]).decode(encoding="ascii")
1020 fw(".. data:: %s\n\n" % member)
1021 member_type, is_seq = context_type_map[member]
1022 fw(" :type: %s :class:`bpy.types.%s`\n\n" % ("sequence of " if is_seq else "", member_type))
1026 # generate typemap...
1027 # for member in sorted(unique):
1028 # print(' "%s": ("", False),' % member)
1029 if len(context_type_map) > len(unique):
1030 raise Exception("Some types are not used: %s" % str([member for member in context_type_map if member not in unique]))
1032 pass # will have raised an error above
1037 def pyrna_enum2sphinx(prop, use_empty_descriptions=False):
1038 """ write a bullet point list of enum + descrptons
1041 if use_empty_descriptions:
1045 for identifier, name, description in prop.enum_items:
1051 return "".join(["* ``%s`` %s.\n" %
1053 ", ".join(val for val in (name, description) if val),
1055 for identifier, name, description in prop.enum_items
1061 def pyrna2sphinx(basepath):
1062 """ bpy.types and bpy.ops
1064 structs, funcs, ops, props = rna_info.BuildRNAInfo()
1065 if FILTER_BPY_TYPES is not None:
1066 structs = {k: v for k, v in structs.items() if k[1] in FILTER_BPY_TYPES}
1068 if FILTER_BPY_OPS is not None:
1069 ops = {k: v for k, v in ops.items() if v.module_name in FILTER_BPY_OPS}
1071 def write_param(ident, fw, prop, is_return=False):
1075 kwargs = {"as_ret": True}
1080 kwargs = {"as_arg": True}
1081 identifier = " %s" % prop.identifier
1083 kwargs["class_fmt"] = ":class:`%s`"
1085 kwargs["collection_id"] = _BPY_PROP_COLLECTION_ID
1087 type_descr = prop.get_type_description(**kwargs)
1089 enum_text = pyrna_enum2sphinx(prop)
1091 if prop.name or prop.description or enum_text:
1092 fw(ident + ":%s%s:\n\n" % (id_name, identifier))
1094 if prop.name or prop.description:
1095 fw(ident + " " + ", ".join(val for val in (prop.name, prop.description) if val) + "\n\n")
1097 # special exception, cant use genric code here for enums
1099 write_indented_lines(ident + " ", fw, enum_text)
1102 # end enum exception
1104 fw(ident + ":%s%s: %s\n" % (id_type, identifier, type_descr))
1106 def write_struct(struct):
1107 #if not struct.identifier.startswith("Sc") and not struct.identifier.startswith("I"):
1110 #if not struct.identifier == "Object":
1113 filepath = os.path.join(basepath, "bpy.types.%s.rst" % struct.identifier)
1114 file = open(filepath, "w", encoding="utf-8")
1117 base_id = getattr(struct.base, "identifier", "")
1118 struct_id = struct.identifier
1120 if _BPY_STRUCT_FAKE:
1122 base_id = _BPY_STRUCT_FAKE
1125 title = "%s(%s)" % (struct_id, base_id)
1129 fw(title_string(title, "="))
1131 fw(".. module:: bpy.types\n\n")
1134 write_example_ref("", fw, "bpy.types.%s" % struct_id)
1136 base_ids = [base.identifier for base in struct.get_bases()]
1138 if _BPY_STRUCT_FAKE:
1139 base_ids.append(_BPY_STRUCT_FAKE)
1144 if len(base_ids) > 1:
1145 fw("base classes --- ")
1147 fw("base class --- ")
1149 fw(", ".join((":class:`%s`" % base_id) for base_id in base_ids))
1152 subclass_ids = [s.identifier for s in structs.values() if s.base is struct if not rna_info.rna_id_ignore(s.identifier)]
1154 fw("subclasses --- \n" + ", ".join((":class:`%s`" % s) for s in subclass_ids) + "\n\n")
1156 base_id = getattr(struct.base, "identifier", "")
1158 if _BPY_STRUCT_FAKE:
1160 base_id = _BPY_STRUCT_FAKE
1163 fw(".. class:: %s(%s)\n\n" % (struct_id, base_id))
1165 fw(".. class:: %s\n\n" % struct_id)
1167 fw(" %s\n\n" % struct.description)
1169 # properties sorted in alphabetical order
1170 sorted_struct_properties = struct.properties[:]
1171 sorted_struct_properties.sort(key=lambda prop: prop.identifier)
1173 # support blacklisting props
1174 struct_blacklist = RNA_BLACKLIST.get(struct_id, ())
1176 for prop in sorted_struct_properties:
1178 # support blacklisting props
1179 if prop.identifier in struct_blacklist:
1182 type_descr = prop.get_type_description(class_fmt=":class:`%s`", collection_id=_BPY_PROP_COLLECTION_ID)
1183 # readonly properties use "data" directive, variables properties use "attribute" directive
1184 if 'readonly' in type_descr:
1185 fw(" .. data:: %s\n\n" % prop.identifier)
1187 fw(" .. attribute:: %s\n\n" % prop.identifier)
1188 if prop.description:
1189 fw(" %s\n\n" % prop.description)
1191 # special exception, cant use genric code here for enums
1192 if prop.type == "enum":
1193 enum_text = pyrna_enum2sphinx(prop)
1195 write_indented_lines(" ", fw, enum_text)
1198 # end enum exception
1200 fw(" :type: %s\n\n" % type_descr)
1203 py_properties = struct.get_py_properties()
1205 for identifier, py_prop in py_properties:
1206 pyprop2sphinx(" ", fw, identifier, py_prop)
1207 del py_properties, py_prop
1209 for func in struct.functions:
1210 args_str = ", ".join(prop.get_arg_default(force=False) for prop in func.args)
1212 fw(" .. %s:: %s(%s)\n\n" % ("classmethod" if func.is_classmethod else "method", func.identifier, args_str))
1213 fw(" %s\n\n" % func.description)
1215 for prop in func.args:
1216 write_param(" ", fw, prop)
1218 if len(func.return_values) == 1:
1219 write_param(" ", fw, func.return_values[0], is_return=True)
1220 elif func.return_values: # multiple return values
1221 fw(" :return (%s):\n" % ", ".join(prop.identifier for prop in func.return_values))
1222 for prop in func.return_values:
1223 # TODO, pyrna_enum2sphinx for multiple return values... actually dont think we even use this but still!!!
1224 type_descr = prop.get_type_description(as_ret=True, class_fmt=":class:`%s`", collection_id=_BPY_PROP_COLLECTION_ID)
1225 descr = prop.description
1228 fw(" `%s`, %s, %s\n\n" % (prop.identifier, descr, type_descr))
1230 write_example_ref(" ", fw, "bpy.types." + struct_id + "." + func.identifier)
1235 py_funcs = struct.get_py_functions()
1238 for identifier, py_func in py_funcs:
1239 pyfunc2sphinx(" ", fw, identifier, py_func, is_class=True)
1240 del py_funcs, py_func
1242 py_funcs = struct.get_py_c_functions()
1245 for identifier, py_func in py_funcs:
1246 py_c_func2sphinx(" ", fw, "bpy.types", struct_id, identifier, py_func, is_class=True)
1250 if struct.base or _BPY_STRUCT_FAKE:
1251 bases = list(reversed(struct.get_bases()))
1256 if _BPY_STRUCT_FAKE:
1257 descr_items = [(key, descr) for key, descr in sorted(bpy.types.Struct.__bases__[0].__dict__.items()) if not key.startswith("__")]
1259 if _BPY_STRUCT_FAKE:
1260 for key, descr in descr_items:
1261 if type(descr) == GetSetDescriptorType:
1262 lines.append(" * :class:`%s.%s`\n" % (_BPY_STRUCT_FAKE, key))
1265 for prop in base.properties:
1266 lines.append(" * :class:`%s.%s`\n" % (base.identifier, prop.identifier))
1268 for identifier, py_prop in base.get_py_properties():
1269 lines.append(" * :class:`%s.%s`\n" % (base.identifier, identifier))
1271 for identifier, py_prop in base.get_py_properties():
1272 lines.append(" * :class:`%s.%s`\n" % (base.identifier, identifier))
1275 fw(".. rubric:: Inherited Properties\n\n")
1278 fw(" :columns: 2\n\n")
1287 if _BPY_STRUCT_FAKE:
1288 for key, descr in descr_items:
1289 if type(descr) == MethodDescriptorType:
1290 lines.append(" * :class:`%s.%s`\n" % (_BPY_STRUCT_FAKE, key))
1293 for func in base.functions:
1294 lines.append(" * :class:`%s.%s`\n" % (base.identifier, func.identifier))
1295 for identifier, py_func in base.get_py_functions():
1296 lines.append(" * :class:`%s.%s`\n" % (base.identifier, identifier))
1299 fw(".. rubric:: Inherited Functions\n\n")
1302 fw(" :columns: 2\n\n")
1310 if struct.references:
1311 # use this otherwise it gets in the index for a normal heading.
1312 fw(".. rubric:: References\n\n")
1315 fw(" :columns: 2\n\n")
1317 # context does its own thing
1318 # "active_base": ("ObjectBase", False),
1319 for ref_attr, (ref_type, ref_is_seq) in sorted(context_type_map.items()):
1320 if ref_type == struct_id:
1321 fw(" * :mod:`bpy.context.%s`\n" % ref_attr)
1322 del ref_attr, ref_type, ref_is_seq
1324 for ref in struct.references:
1325 ref_split = ref.split(".")
1326 if len(ref_split) > 2:
1327 ref = ref_split[-2] + "." + ref_split[-1]
1328 fw(" * :class:`%s`\n" % ref)
1331 # docs last?, disable for now
1332 # write_example_ref("", fw, "bpy.types.%s" % struct_id)
1335 if "bpy.types" not in EXCLUDE_MODULES:
1336 for struct in structs.values():
1337 # TODO, rna_info should filter these out!
1338 if "_OT_" in struct.identifier:
1340 write_struct(struct)
1342 def fake_bpy_type(class_value, class_name, descr_str, use_subclasses=True):
1343 filepath = os.path.join(basepath, "bpy.types.%s.rst" % class_name)
1344 file = open(filepath, "w", encoding="utf-8")
1347 fw(title_string(class_name, "="))
1349 fw(".. module:: bpy.types\n")
1353 subclass_ids = [s.identifier for s in structs.values() if s.base is None if not rna_info.rna_id_ignore(s.identifier)]
1355 fw("subclasses --- \n" + ", ".join((":class:`%s`" % s) for s in sorted(subclass_ids)) + "\n\n")
1357 fw(".. class:: %s\n\n" % class_name)
1358 fw(" %s\n\n" % descr_str)
1359 fw(" .. note::\n\n")
1360 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)
1362 descr_items = [(key, descr) for key, descr in sorted(class_value.__dict__.items()) if not key.startswith("__")]
1364 for key, descr in descr_items:
1365 if type(descr) == MethodDescriptorType: # GetSetDescriptorType, GetSetDescriptorType's are not documented yet
1366 py_descr2sphinx(" ", fw, descr, "bpy.types", class_name, key)
1368 for key, descr in descr_items:
1369 if type(descr) == GetSetDescriptorType:
1370 py_descr2sphinx(" ", fw, descr, "bpy.types", class_name, key)
1373 # write fake classes
1374 if _BPY_STRUCT_FAKE:
1375 class_value = bpy.types.Struct.__bases__[0]
1376 fake_bpy_type(class_value, _BPY_STRUCT_FAKE, "built-in base class for all classes in bpy.types.", use_subclasses=True)
1378 if _BPY_PROP_COLLECTION_FAKE:
1379 class_value = bpy.data.objects.__class__
1380 fake_bpy_type(class_value, _BPY_PROP_COLLECTION_FAKE, "built-in class used for all collections.", use_subclasses=False)
1384 API_BASEURL = "http://svn.blender.org/svnroot/bf-blender/trunk/blender/release/scripts"
1385 API_BASEURL_ADDON = "http://svn.blender.org/svnroot/bf-extensions/trunk/py/scripts"
1386 API_BASEURL_ADDON_CONTRIB = "http://svn.blender.org/svnroot/bf-extensions/contrib/py/scripts"
1389 for op in ops.values():
1390 op_modules.setdefault(op.module_name, []).append(op)
1393 for op_module_name, ops_mod in op_modules.items():
1394 filepath = os.path.join(basepath, "bpy.ops.%s.rst" % op_module_name)
1395 file = open(filepath, "w", encoding="utf-8")
1398 title = "%s Operators" % op_module_name.replace("_", " ").title()
1400 fw(title_string(title, "="))
1402 fw(".. module:: bpy.ops.%s\n\n" % op_module_name)
1404 ops_mod.sort(key=lambda op: op.func_name)
1407 args_str = ", ".join(prop.get_arg_default(force=True) for prop in op.args)
1408 fw(".. function:: %s(%s)\n\n" % (op.func_name, args_str))
1410 # if the description isn't valid, we output the standard warning
1411 # with a link to the wiki so that people can help
1412 if not op.description or op.description == "(undocumented operator)":
1413 operator_description = undocumented_message('bpy.ops', op.module_name, op.func_name)
1415 operator_description = op.description
1417 fw(" %s\n\n" % operator_description)
1418 for prop in op.args:
1419 write_param(" ", fw, prop)
1423 location = op.get_location()
1424 if location != (None, None):
1425 if location[0].startswith("addons_contrib" + os.sep):
1426 url_base = API_BASEURL_ADDON_CONTRIB
1427 elif location[0].startswith("addons" + os.sep):
1428 url_base = API_BASEURL_ADDON
1430 url_base = API_BASEURL
1432 fw(" :file: `%s <%s/%s>`_:%d\n\n" % (location[0],
1439 if "bpy.ops" not in EXCLUDE_MODULES:
1443 def write_sphinx_conf_py(basepath):
1445 Write sphinx's conf.py
1447 filepath = os.path.join(basepath, "conf.py")
1448 file = open(filepath, "w", encoding="utf-8")
1451 fw("project = 'Blender'\n")
1452 # fw("master_doc = 'index'\n")
1453 fw("copyright = u'Blender Foundation'\n")
1454 fw("version = '%s - API'\n" % BLENDER_VERSION_DOTS)
1455 fw("release = '%s - API'\n" % BLENDER_VERSION_DOTS)
1457 if ARGS.sphinx_theme != 'default':
1458 fw("html_theme = '%s'\n" % ARGS.sphinx_theme)
1460 if ARGS.sphinx_theme in SPHINX_THEMES['bf']:
1461 fw("html_theme_path = ['../']\n")
1462 # copied with the theme, exclude else we get an error [#28873]
1463 fw("html_favicon = 'favicon.ico'\n") # in <theme>/static/
1465 # not helpful since the source is generated, adds to upload size.
1466 fw("html_copy_source = False\n")
1469 # needed for latex, pdf gen
1470 fw("latex_documents = [ ('contents', 'contents.tex', 'Blender Index', 'Blender Foundation', 'manual'), ]\n")
1471 fw("latex_paper_size = 'a4paper'\n")
1475 def write_rst_contents(basepath):
1477 Write the rst file of the main page, needed for sphinx (index.html)
1479 filepath = os.path.join(basepath, "contents.rst")
1480 file = open(filepath, "w", encoding="utf-8")
1483 fw(title_string("Blender Documentation Contents", "%", double=True))
1485 fw("Welcome, this document is an API reference for Blender %s, built %s.\n" % (BLENDER_VERSION_DOTS, BLENDER_DATE))
1488 # fw("`A PDF version of this document is also available <%s>`_\n" % BLENDER_PDF_FILENAME)
1489 fw("`A compressed ZIP file of this site is available <%s>`_\n" % BLENDER_ZIP_FILENAME)
1493 if not EXCLUDE_INFO_DOCS:
1494 fw(title_string("Blender/Python Documentation", "=", double=True))
1496 fw(".. toctree::\n")
1497 fw(" :maxdepth: 1\n\n")
1498 for info, info_desc in INFO_DOCS:
1499 fw(" %s <%s>\n\n" % (info_desc, info))
1502 fw(title_string("Application Modules", "=", double=True))
1503 fw(".. toctree::\n")
1504 fw(" :maxdepth: 1\n\n")
1507 "bpy.context", # note: not actually a module
1508 "bpy.data", # note: not actually a module
1522 for mod in app_modules:
1523 if mod not in EXCLUDE_MODULES:
1526 fw(title_string("Standalone Modules", "=", double=True))
1527 fw(".. toctree::\n")
1528 fw(" :maxdepth: 1\n\n")
1530 standalone_modules = (
1532 "mathutils", "mathutils.geometry", "mathutils.noise",
1534 "bgl", "blf", "gpu", "aud", "bpy_extras",
1536 "bmesh", "bmesh.types", "bmesh.utils",
1539 for mod in standalone_modules:
1540 if mod not in EXCLUDE_MODULES:
1544 if "bge" not in EXCLUDE_MODULES:
1545 fw(title_string("Game Engine Modules", "=", double=True))
1546 fw(".. toctree::\n")
1547 fw(" :maxdepth: 1\n\n")
1548 fw(" bge.types.rst\n\n")
1549 fw(" bge.logic.rst\n\n")
1550 fw(" bge.render.rst\n\n")
1551 fw(" bge.texture.rst\n\n")
1552 fw(" bge.events.rst\n\n")
1553 fw(" bge.constraints.rst\n\n")
1555 # rna generated change log
1556 fw(title_string("API Info", "=", double=True))
1557 fw(".. toctree::\n")
1558 fw(" :maxdepth: 1\n\n")
1559 fw(" change_log.rst\n\n")
1563 fw(".. note:: The Blender Python API has areas which are still in development.\n")
1565 fw(" The following areas are subject to change.\n")
1566 fw(" * operator behavior, names and arguments\n")
1567 fw(" * mesh creation and editing functions\n")
1569 fw(" These parts of the API are relatively stable and are unlikely to change significantly\n")
1570 fw(" * data API, access to attributes of blender data such as mesh verts, material color, timeline frames and scene objects\n")
1571 fw(" * user interface functions for defining buttons, creation of menus, headers, panels\n")
1572 fw(" * render engine integration\n")
1573 fw(" * modules: bgl, mathutils & game engine.\n")
1579 def write_rst_bpy(basepath):
1581 Write rst file of bpy module (disabled by default)
1584 filepath = os.path.join(basepath, "bpy.rst")
1585 file = open(filepath, "w", encoding="utf-8")
1590 title = ":mod:`bpy` --- Blender Python Module"
1592 fw(title_string(title, "="))
1594 fw(".. module:: bpy.types\n\n")
1598 def write_rst_types_index(basepath):
1600 Write the rst file of bpy.types module (index)
1602 if "bpy.types" not in EXCLUDE_MODULES:
1603 filepath = os.path.join(basepath, "bpy.types.rst")
1604 file = open(filepath, "w", encoding="utf-8")
1606 fw(title_string("Types (bpy.types)", "="))
1607 fw(".. toctree::\n")
1609 fw(" bpy.types.*\n\n")
1613 def write_rst_ops_index(basepath):
1615 Write the rst file of bpy.ops module (index)
1617 if "bpy.ops" not in EXCLUDE_MODULES:
1618 filepath = os.path.join(basepath, "bpy.ops.rst")
1619 file = open(filepath, "w", encoding="utf-8")
1621 fw(title_string("Operators (bpy.ops)", "="))
1622 write_example_ref("", fw, "bpy.ops")
1623 fw(".. toctree::\n")
1625 fw(" bpy.ops.*\n\n")
1629 def write_rst_data(basepath):
1631 Write the rst file of bpy.data module
1633 if "bpy.data" not in EXCLUDE_MODULES:
1634 # not actually a module, only write this file so we
1635 # can reference in the TOC
1636 filepath = os.path.join(basepath, "bpy.data.rst")
1637 file = open(filepath, "w", encoding="utf-8")
1639 fw(title_string("Data Access (bpy.data)", "="))
1640 fw(".. module:: bpy\n")
1642 fw("This module is used for all blender/python access.\n")
1644 fw(".. data:: data\n")
1646 fw(" Access to blenders internal data\n")
1648 fw(" :type: :class:`bpy.types.BlendData`\n")
1650 fw(".. literalinclude:: ../examples/bpy.data.py\n")
1653 EXAMPLE_SET_USED.add("bpy.data")
1656 def write_rst_importable_modules(basepath):
1658 Write the rst files of importable modules
1660 importable_modules = {
1662 "bpy.path" : "Path Utilities",
1663 "bpy.utils" : "Utilities",
1664 "bpy_extras" : "Extra Utilities",
1667 "aud" : "Audio System",
1668 "blf" : "Font Drawing",
1669 "bmesh" : "BMesh Module",
1670 "bmesh.types" : "BMesh Types",
1671 "bmesh.utils" : "BMesh Utilities",
1672 "bpy.app" : "Application Data",
1673 "bpy.app.handlers" : "Application Handlers",
1674 "bpy.props" : "Property Definitions",
1675 "mathutils" : "Math Types & Utilities",
1676 "mathutils.geometry": "Geometry Utilities",
1677 "mathutils.noise" : "Noise Utilities",
1679 for mod_name, mod_descr in importable_modules.items():
1680 if mod_name not in EXCLUDE_MODULES:
1681 module = __import__(mod_name,
1682 fromlist=[mod_name.rsplit(".", 1)[-1]])
1683 pymodule2sphinx(basepath, mod_name, module, mod_descr)
1686 def copy_handwritten_rsts(basepath):
1689 if not EXCLUDE_INFO_DOCS:
1690 for info, info_desc in INFO_DOCS:
1691 shutil.copy2(os.path.join(RST_DIR, info), basepath)
1693 # TODO put this docs in blender's code and use import as per modules above
1694 handwritten_modules = [
1701 "bgl", # "Blender OpenGl wrapper"
1702 "gpu", # "GPU Shader Module"
1707 for mod_name in handwritten_modules:
1708 if mod_name not in EXCLUDE_MODULES:
1709 # copy2 keeps time/date stamps
1710 shutil.copy2(os.path.join(RST_DIR, "%s.rst" % mod_name), basepath)
1713 shutil.copy2(os.path.join(RST_DIR, "change_log.rst"), basepath)
1716 def rna2sphinx(basepath):
1724 write_sphinx_conf_py(basepath)
1727 write_rst_contents(basepath)
1730 if "bpy.context" not in EXCLUDE_MODULES:
1731 # one of a kind, context doc (uses ctypes to extract info!)
1732 # doesn't work on mac
1733 if PLATFORM != "darwin":
1734 pycontext2sphinx(basepath)
1737 write_rst_bpy(basepath) # bpy, disabled by default
1738 write_rst_types_index(basepath) # bpy.types
1739 write_rst_ops_index(basepath) # bpy.ops
1740 pyrna2sphinx(basepath) # bpy.types.* and bpy.ops.*
1741 write_rst_data(basepath) # bpy.data
1742 write_rst_importable_modules(basepath)
1744 # copy the other rsts
1745 copy_handwritten_rsts(basepath)
1748 def align_sphinx_in_to_sphinx_in_tmp():
1750 Move changed files from SPHINX_IN_TMP to SPHINX_IN
1754 sphinx_in_files = set(os.listdir(SPHINX_IN))
1755 sphinx_in_tmp_files = set(os.listdir(SPHINX_IN_TMP))
1757 # remove deprecated files that have been removed
1758 for f in sorted(sphinx_in_files):
1759 if f not in sphinx_in_tmp_files:
1760 BPY_LOGGER.debug("\tdeprecated: %s" % f)
1761 os.remove(os.path.join(SPHINX_IN, f))
1763 # freshen with new files.
1764 for f in sorted(sphinx_in_tmp_files):
1765 f_from = os.path.join(SPHINX_IN_TMP, f)
1766 f_to = os.path.join(SPHINX_IN, f)
1769 if f in sphinx_in_files:
1770 if filecmp.cmp(f_from, f_to):
1774 BPY_LOGGER.debug("\tupdating: %s" % f)
1775 shutil.copy(f_from, f_to)
1778 def refactor_sphinx_log(sphinx_logfile):
1780 with open(sphinx_logfile, "r", encoding="utf-8") as original_logfile:
1781 lines = set(original_logfile.readlines())
1783 if 'warning' in line.lower() or 'error' in line.lower():
1784 line = line.strip().split(None, 2)
1786 location, kind, msg = line
1787 location = os.path.relpath(location, start=SPHINX_IN)
1788 refactored_log.append((kind, location, msg))
1789 with open(sphinx_logfile, "w", encoding="utf-8") as refactored_logfile:
1790 for log in sorted(refactored_log):
1791 refactored_logfile.write("%-12s %s\n %s\n" % log)
1796 # eventually, create the dirs
1797 for dir_path in [ARGS.output_dir, SPHINX_IN]:
1798 if not os.path.exists(dir_path):
1801 # eventually, log in files
1803 bpy_logfile = os.path.join(ARGS.output_dir, ".bpy.log")
1804 bpy_logfilehandler = logging.FileHandler(bpy_logfile, mode="w")
1805 bpy_logfilehandler.setLevel(logging.DEBUG)
1806 BPY_LOGGER.addHandler(bpy_logfilehandler)
1808 # using a FileHandler seems to disable the stdout, so we add a StreamHandler
1809 bpy_log_stdout_handler = logging.StreamHandler(stream=sys.stdout)
1810 bpy_log_stdout_handler.setLevel(logging.DEBUG)
1811 BPY_LOGGER.addHandler(bpy_log_stdout_handler)
1813 # in case of out-of-source build, copy the needed dirs
1814 if ARGS.output_dir != SCRIPT_DIR:
1816 examples_dir_copy = os.path.join(ARGS.output_dir, "examples")
1817 if os.path.exists(examples_dir_copy):
1818 shutil.rmtree(examples_dir_copy, True)
1819 shutil.copytree(EXAMPLES_DIR,
1821 ignore=shutil.ignore_patterns(*(".svn",)),
1822 copy_function=shutil.copy)
1824 # eventually, copy the theme dir
1825 if ARGS.sphinx_theme in SPHINX_THEMES['bf']:
1826 if os.path.exists(SPHINX_THEME_DIR):
1827 shutil.rmtree(SPHINX_THEME_DIR, True)
1828 shutil.copytree(SPHINX_THEME_SVN_DIR,
1830 ignore=shutil.ignore_patterns(*(".svn",)),
1831 copy_function=shutil.copy)
1833 # dump the api in rst files
1834 if os.path.exists(SPHINX_IN_TMP):
1835 shutil.rmtree(SPHINX_IN_TMP, True)
1837 rna2sphinx(SPHINX_IN_TMP)
1839 if ARGS.full_rebuild:
1840 # only for full updates
1841 shutil.rmtree(SPHINX_IN, True)
1842 shutil.copytree(SPHINX_IN_TMP,
1844 copy_function=shutil.copy)
1845 if ARGS.sphinx_build and os.path.exists(SPHINX_OUT):
1846 shutil.rmtree(SPHINX_OUT, True)
1847 if ARGS.sphinx_build_pdf and os.path.exists(SPHINX_OUT_PDF):
1848 shutil.rmtree(SPHINX_OUT_PDF, True)
1850 # move changed files in SPHINX_IN
1851 align_sphinx_in_to_sphinx_in_tmp()
1853 # report which example files weren't used
1854 EXAMPLE_SET_UNUSED = EXAMPLE_SET - EXAMPLE_SET_USED
1855 if EXAMPLE_SET_UNUSED:
1856 BPY_LOGGER.debug("\nUnused examples found in '%s'..." % EXAMPLES_DIR)
1857 for f in sorted(EXAMPLE_SET_UNUSED):
1858 BPY_LOGGER.debug(" %s.py" % f)
1859 BPY_LOGGER.debug(" %d total\n" % len(EXAMPLE_SET_UNUSED))
1861 # eventually, build the html docs
1862 if ARGS.sphinx_build:
1864 subprocess.call(SPHINX_BUILD)
1866 # sphinx-build log cleanup+sort
1868 if os.stat(SPHINX_BUILD_LOG).st_size:
1869 refactor_sphinx_log(SPHINX_BUILD_LOG)
1871 # eventually, build the pdf docs
1872 if ARGS.sphinx_build_pdf:
1874 subprocess.call(SPHINX_BUILD_PDF)
1875 subprocess.call(SPHINX_MAKE_PDF, stdout=SPHINX_MAKE_PDF_STDOUT)
1877 # sphinx-build log cleanup+sort
1879 if os.stat(SPHINX_BUILD_PDF_LOG).st_size:
1880 refactor_sphinx_log(SPHINX_BUILD_PDF_LOG)
1882 # eventually, prepare the dir to be deployed online (REFERENCE_PATH)
1883 if ARGS.pack_reference:
1885 if ARGS.sphinx_build:
1886 # delete REFERENCE_PATH
1887 if os.path.exists(REFERENCE_PATH):
1888 shutil.rmtree(REFERENCE_PATH, True)
1890 # copy SPHINX_OUT to the REFERENCE_PATH
1891 ignores = ('.doctrees', 'objects.inv', '.buildinfo')
1892 shutil.copytree(SPHINX_OUT,
1894 ignore=shutil.ignore_patterns(*ignores))
1895 shutil.copy(os.path.join(REFERENCE_PATH, "contents.html"),
1896 os.path.join(REFERENCE_PATH, "index.html"))
1898 # zip REFERENCE_PATH
1899 basename = os.path.join(ARGS.output_dir, REFERENCE_NAME)
1900 tmp_path = shutil.make_archive(basename, 'zip',
1901 root_dir=ARGS.output_dir,
1902 base_dir=REFERENCE_NAME)
1903 final_path = os.path.join(REFERENCE_PATH, BLENDER_ZIP_FILENAME)
1904 os.rename(tmp_path, final_path)
1906 if ARGS.sphinx_build_pdf:
1907 # copy the pdf to REFERENCE_PATH
1908 shutil.copy(os.path.join(SPHINX_OUT_PDF, "contents.pdf"),
1909 os.path.join(REFERENCE_PATH, BLENDER_PDF_FILENAME))
1914 if __name__ == '__main__':