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)
914 def pycontext2sphinx(basepath):
915 # Only use once. very irregular
917 filepath = os.path.join(basepath, "bpy.context.rst")
918 file = open(filepath, "w", encoding="utf-8")
920 fw(title_string("Context Access (bpy.context)", "="))
921 fw(".. module:: bpy.context\n")
923 fw("The context members available depend on the area of blender which is currently being accessed.\n")
925 fw("Note that all context values are readonly, but may be modified through the data api or by running operators\n\n")
927 # nasty, get strings directly from blender because there is no other way to get it
931 "screen_context_dir",
932 "view3d_context_dir",
933 "buttons_context_dir",
938 "sequencer_context_dir",
941 # Changes in blender will force errors here
943 "active_base": ("ObjectBase", False),
944 "active_bone": ("Bone", False),
945 "active_object": ("Object", False),
946 "active_operator": ("Operator", False),
947 "active_pose_bone": ("PoseBone", False),
948 "active_node": ("Node", False),
949 "armature": ("Armature", False),
950 "bone": ("Bone", False),
951 "brush": ("Brush", False),
952 "camera": ("Camera", False),
953 "cloth": ("ClothModifier", False),
954 "collision": ("CollisionModifier", False),
955 "curve": ("Curve", False),
956 "dynamic_paint": ("DynamicPaintModifier", False),
957 "edit_bone": ("EditBone", False),
958 "edit_image": ("Image", False),
959 "edit_mask": ("Mask", False),
960 "edit_movieclip": ("MovieClip", False),
961 "edit_object": ("Object", False),
962 "edit_text": ("Text", False),
963 "editable_bones": ("EditBone", True),
964 "fluid": ("FluidSimulationModifier", False),
965 "image_paint_object": ("Object", False),
966 "lamp": ("Lamp", False),
967 "lattice": ("Lattice", False),
968 "material": ("Material", False),
969 "material_slot": ("MaterialSlot", False),
970 "mesh": ("Mesh", False),
971 "meta_ball": ("MetaBall", False),
972 "object": ("Object", False),
973 "particle_edit_object": ("Object", False),
974 "particle_system": ("ParticleSystem", False),
975 "particle_system_editable": ("ParticleSystem", False),
976 "pose_bone": ("PoseBone", False),
977 "scene": ("Scene", False),
978 "sculpt_object": ("Object", False),
979 "selectable_bases": ("ObjectBase", True),
980 "selectable_objects": ("Object", True),
981 "selected_bases": ("ObjectBase", True),
982 "selected_bones": ("Bone", True),
983 "selected_editable_bases": ("ObjectBase", True),
984 "selected_editable_bones": ("Bone", True),
985 "selected_editable_objects": ("Object", True),
986 "selected_editable_sequences": ("Sequence", True),
987 "selected_nodes": ("Node", True),
988 "selected_objects": ("Object", True),
989 "selected_pose_bones": ("PoseBone", True),
990 "selected_sequences": ("Sequence", True),
991 "sequences": ("Sequence", True),
992 "smoke": ("SmokeModifier", False),
993 "soft_body": ("SoftBodyModifier", False),
994 "speaker": ("Speaker", False),
995 "texture": ("Texture", False),
996 "texture_slot": ("MaterialTextureSlot", False),
997 "texture_user": ("ID", False),
998 "vertex_paint_object": ("Object", False),
999 "visible_bases": ("ObjectBase", True),
1000 "visible_bones": ("Object", True),
1001 "visible_objects": ("Object", True),
1002 "visible_pose_bones": ("PoseBone", True),
1003 "weight_paint_object": ("Object", False),
1004 "world": ("World", False),
1008 blend_cdll = ctypes.CDLL("")
1009 for ctx_str in context_strings:
1010 subsection = "%s Context" % ctx_str.split("_")[0].title()
1011 fw("\n%s\n%s\n\n" % (subsection, (len(subsection) * '-')))
1013 attr = ctypes.addressof(getattr(blend_cdll, ctx_str))
1014 c_char_p_p = ctypes.POINTER(ctypes.c_char_p)
1015 char_array = c_char_p_p.from_address(attr)
1017 while char_array[i] is not None:
1018 member = ctypes.string_at(char_array[i]).decode(encoding="ascii")
1019 fw(".. data:: %s\n\n" % member)
1020 member_type, is_seq = type_map[member]
1021 fw(" :type: %s :class:`bpy.types.%s`\n\n" % ("sequence of " if is_seq else "", member_type))
1025 # generate typemap...
1026 # for member in sorted(unique):
1027 # print(' "%s": ("", False),' % member)
1028 if len(type_map) > len(unique):
1029 raise Exception("Some types are not used: %s" % str([member for member in type_map if member not in unique]))
1031 pass # will have raised an error above
1036 def pyrna_enum2sphinx(prop, use_empty_descriptions=False):
1037 """ write a bullet point list of enum + descrptons
1040 if use_empty_descriptions:
1044 for identifier, name, description in prop.enum_items:
1050 return "".join(["* ``%s`` %s.\n" %
1052 ", ".join(val for val in (name, description) if val),
1054 for identifier, name, description in prop.enum_items
1060 def pyrna2sphinx(basepath):
1061 """ bpy.types and bpy.ops
1063 structs, funcs, ops, props = rna_info.BuildRNAInfo()
1064 if FILTER_BPY_TYPES is not None:
1065 structs = {k: v for k, v in structs.items() if k[1] in FILTER_BPY_TYPES}
1067 if FILTER_BPY_OPS is not None:
1068 ops = {k: v for k, v in ops.items() if v.module_name in FILTER_BPY_OPS}
1070 def write_param(ident, fw, prop, is_return=False):
1074 kwargs = {"as_ret": True}
1079 kwargs = {"as_arg": True}
1080 identifier = " %s" % prop.identifier
1082 kwargs["class_fmt"] = ":class:`%s`"
1084 kwargs["collection_id"] = _BPY_PROP_COLLECTION_ID
1086 type_descr = prop.get_type_description(**kwargs)
1088 enum_text = pyrna_enum2sphinx(prop)
1090 if prop.name or prop.description or enum_text:
1091 fw(ident + ":%s%s:\n\n" % (id_name, identifier))
1093 if prop.name or prop.description:
1094 fw(ident + " " + ", ".join(val for val in (prop.name, prop.description) if val) + "\n\n")
1096 # special exception, cant use genric code here for enums
1098 write_indented_lines(ident + " ", fw, enum_text)
1101 # end enum exception
1103 fw(ident + ":%s%s: %s\n" % (id_type, identifier, type_descr))
1105 def write_struct(struct):
1106 #if not struct.identifier.startswith("Sc") and not struct.identifier.startswith("I"):
1109 #if not struct.identifier == "Object":
1112 filepath = os.path.join(basepath, "bpy.types.%s.rst" % struct.identifier)
1113 file = open(filepath, "w", encoding="utf-8")
1116 base_id = getattr(struct.base, "identifier", "")
1117 struct_id = struct.identifier
1119 if _BPY_STRUCT_FAKE:
1121 base_id = _BPY_STRUCT_FAKE
1124 title = "%s(%s)" % (struct_id, base_id)
1128 fw(title_string(title, "="))
1130 fw(".. module:: bpy.types\n\n")
1133 write_example_ref("", fw, "bpy.types.%s" % struct_id)
1135 base_ids = [base.identifier for base in struct.get_bases()]
1137 if _BPY_STRUCT_FAKE:
1138 base_ids.append(_BPY_STRUCT_FAKE)
1143 if len(base_ids) > 1:
1144 fw("base classes --- ")
1146 fw("base class --- ")
1148 fw(", ".join((":class:`%s`" % base_id) for base_id in base_ids))
1151 subclass_ids = [s.identifier for s in structs.values() if s.base is struct if not rna_info.rna_id_ignore(s.identifier)]
1153 fw("subclasses --- \n" + ", ".join((":class:`%s`" % s) for s in subclass_ids) + "\n\n")
1155 base_id = getattr(struct.base, "identifier", "")
1157 if _BPY_STRUCT_FAKE:
1159 base_id = _BPY_STRUCT_FAKE
1162 fw(".. class:: %s(%s)\n\n" % (struct_id, base_id))
1164 fw(".. class:: %s\n\n" % struct_id)
1166 fw(" %s\n\n" % struct.description)
1168 # properties sorted in alphabetical order
1169 sorted_struct_properties = struct.properties[:]
1170 sorted_struct_properties.sort(key=lambda prop: prop.identifier)
1172 # support blacklisting props
1173 struct_blacklist = RNA_BLACKLIST.get(struct_id, ())
1175 for prop in sorted_struct_properties:
1177 # support blacklisting props
1178 if prop.identifier in struct_blacklist:
1181 type_descr = prop.get_type_description(class_fmt=":class:`%s`", collection_id=_BPY_PROP_COLLECTION_ID)
1182 # readonly properties use "data" directive, variables properties use "attribute" directive
1183 if 'readonly' in type_descr:
1184 fw(" .. data:: %s\n\n" % prop.identifier)
1186 fw(" .. attribute:: %s\n\n" % prop.identifier)
1187 if prop.description:
1188 fw(" %s\n\n" % prop.description)
1190 # special exception, cant use genric code here for enums
1191 if prop.type == "enum":
1192 enum_text = pyrna_enum2sphinx(prop)
1194 write_indented_lines(" ", fw, enum_text)
1197 # end enum exception
1199 fw(" :type: %s\n\n" % type_descr)
1202 py_properties = struct.get_py_properties()
1204 for identifier, py_prop in py_properties:
1205 pyprop2sphinx(" ", fw, identifier, py_prop)
1206 del py_properties, py_prop
1208 for func in struct.functions:
1209 args_str = ", ".join(prop.get_arg_default(force=False) for prop in func.args)
1211 fw(" .. %s:: %s(%s)\n\n" % ("classmethod" if func.is_classmethod else "method", func.identifier, args_str))
1212 fw(" %s\n\n" % func.description)
1214 for prop in func.args:
1215 write_param(" ", fw, prop)
1217 if len(func.return_values) == 1:
1218 write_param(" ", fw, func.return_values[0], is_return=True)
1219 elif func.return_values: # multiple return values
1220 fw(" :return (%s):\n" % ", ".join(prop.identifier for prop in func.return_values))
1221 for prop in func.return_values:
1222 # TODO, pyrna_enum2sphinx for multiple return values... actually dont think we even use this but still!!!
1223 type_descr = prop.get_type_description(as_ret=True, class_fmt=":class:`%s`", collection_id=_BPY_PROP_COLLECTION_ID)
1224 descr = prop.description
1227 fw(" `%s`, %s, %s\n\n" % (prop.identifier, descr, type_descr))
1229 write_example_ref(" ", fw, "bpy.types." + struct_id + "." + func.identifier)
1234 py_funcs = struct.get_py_functions()
1237 for identifier, py_func in py_funcs:
1238 pyfunc2sphinx(" ", fw, identifier, py_func, is_class=True)
1239 del py_funcs, py_func
1241 py_funcs = struct.get_py_c_functions()
1244 for identifier, py_func in py_funcs:
1245 py_c_func2sphinx(" ", fw, "bpy.types", struct_id, identifier, py_func, is_class=True)
1249 if struct.base or _BPY_STRUCT_FAKE:
1250 bases = list(reversed(struct.get_bases()))
1255 if _BPY_STRUCT_FAKE:
1256 descr_items = [(key, descr) for key, descr in sorted(bpy.types.Struct.__bases__[0].__dict__.items()) if not key.startswith("__")]
1258 if _BPY_STRUCT_FAKE:
1259 for key, descr in descr_items:
1260 if type(descr) == GetSetDescriptorType:
1261 lines.append(" * :class:`%s.%s`\n" % (_BPY_STRUCT_FAKE, key))
1264 for prop in base.properties:
1265 lines.append(" * :class:`%s.%s`\n" % (base.identifier, prop.identifier))
1267 for identifier, py_prop in base.get_py_properties():
1268 lines.append(" * :class:`%s.%s`\n" % (base.identifier, identifier))
1270 for identifier, py_prop in base.get_py_properties():
1271 lines.append(" * :class:`%s.%s`\n" % (base.identifier, identifier))
1274 fw(".. rubric:: Inherited Properties\n\n")
1277 fw(" :columns: 2\n\n")
1286 if _BPY_STRUCT_FAKE:
1287 for key, descr in descr_items:
1288 if type(descr) == MethodDescriptorType:
1289 lines.append(" * :class:`%s.%s`\n" % (_BPY_STRUCT_FAKE, key))
1292 for func in base.functions:
1293 lines.append(" * :class:`%s.%s`\n" % (base.identifier, func.identifier))
1294 for identifier, py_func in base.get_py_functions():
1295 lines.append(" * :class:`%s.%s`\n" % (base.identifier, identifier))
1298 fw(".. rubric:: Inherited Functions\n\n")
1301 fw(" :columns: 2\n\n")
1309 if struct.references:
1310 # use this otherwise it gets in the index for a normal heading.
1311 fw(".. rubric:: References\n\n")
1314 fw(" :columns: 2\n\n")
1316 for ref in struct.references:
1317 ref_split = ref.split(".")
1318 if len(ref_split) > 2:
1319 ref = ref_split[-2] + "." + ref_split[-1]
1320 fw(" * :class:`%s`\n" % ref)
1323 # docs last?, disable for now
1324 # write_example_ref("", fw, "bpy.types.%s" % struct_id)
1327 if "bpy.types" not in EXCLUDE_MODULES:
1328 for struct in structs.values():
1329 # TODO, rna_info should filter these out!
1330 if "_OT_" in struct.identifier:
1332 write_struct(struct)
1334 def fake_bpy_type(class_value, class_name, descr_str, use_subclasses=True):
1335 filepath = os.path.join(basepath, "bpy.types.%s.rst" % class_name)
1336 file = open(filepath, "w", encoding="utf-8")
1339 fw(title_string(class_name, "="))
1341 fw(".. module:: bpy.types\n")
1345 subclass_ids = [s.identifier for s in structs.values() if s.base is None if not rna_info.rna_id_ignore(s.identifier)]
1347 fw("subclasses --- \n" + ", ".join((":class:`%s`" % s) for s in sorted(subclass_ids)) + "\n\n")
1349 fw(".. class:: %s\n\n" % class_name)
1350 fw(" %s\n\n" % descr_str)
1351 fw(" .. note::\n\n")
1352 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)
1354 descr_items = [(key, descr) for key, descr in sorted(class_value.__dict__.items()) if not key.startswith("__")]
1356 for key, descr in descr_items:
1357 if type(descr) == MethodDescriptorType: # GetSetDescriptorType, GetSetDescriptorType's are not documented yet
1358 py_descr2sphinx(" ", fw, descr, "bpy.types", class_name, key)
1360 for key, descr in descr_items:
1361 if type(descr) == GetSetDescriptorType:
1362 py_descr2sphinx(" ", fw, descr, "bpy.types", class_name, key)
1365 # write fake classes
1366 if _BPY_STRUCT_FAKE:
1367 class_value = bpy.types.Struct.__bases__[0]
1368 fake_bpy_type(class_value, _BPY_STRUCT_FAKE, "built-in base class for all classes in bpy.types.", use_subclasses=True)
1370 if _BPY_PROP_COLLECTION_FAKE:
1371 class_value = bpy.data.objects.__class__
1372 fake_bpy_type(class_value, _BPY_PROP_COLLECTION_FAKE, "built-in class used for all collections.", use_subclasses=False)
1376 API_BASEURL = "http://svn.blender.org/svnroot/bf-blender/trunk/blender/release/scripts"
1377 API_BASEURL_ADDON = "http://svn.blender.org/svnroot/bf-extensions/trunk/py/scripts"
1378 API_BASEURL_ADDON_CONTRIB = "http://svn.blender.org/svnroot/bf-extensions/contrib/py/scripts"
1381 for op in ops.values():
1382 op_modules.setdefault(op.module_name, []).append(op)
1385 for op_module_name, ops_mod in op_modules.items():
1386 filepath = os.path.join(basepath, "bpy.ops.%s.rst" % op_module_name)
1387 file = open(filepath, "w", encoding="utf-8")
1390 title = "%s Operators" % op_module_name.replace("_", " ").title()
1392 fw(title_string(title, "="))
1394 fw(".. module:: bpy.ops.%s\n\n" % op_module_name)
1396 ops_mod.sort(key=lambda op: op.func_name)
1399 args_str = ", ".join(prop.get_arg_default(force=True) for prop in op.args)
1400 fw(".. function:: %s(%s)\n\n" % (op.func_name, args_str))
1402 # if the description isn't valid, we output the standard warning
1403 # with a link to the wiki so that people can help
1404 if not op.description or op.description == "(undocumented operator)":
1405 operator_description = undocumented_message('bpy.ops', op.module_name, op.func_name)
1407 operator_description = op.description
1409 fw(" %s\n\n" % operator_description)
1410 for prop in op.args:
1411 write_param(" ", fw, prop)
1415 location = op.get_location()
1416 if location != (None, None):
1417 if location[0].startswith("addons_contrib" + os.sep):
1418 url_base = API_BASEURL_ADDON_CONTRIB
1419 elif location[0].startswith("addons" + os.sep):
1420 url_base = API_BASEURL_ADDON
1422 url_base = API_BASEURL
1424 fw(" :file: `%s <%s/%s>`_:%d\n\n" % (location[0],
1431 if "bpy.ops" not in EXCLUDE_MODULES:
1435 def write_sphinx_conf_py(basepath):
1437 Write sphinx's conf.py
1439 filepath = os.path.join(basepath, "conf.py")
1440 file = open(filepath, "w", encoding="utf-8")
1443 fw("project = 'Blender'\n")
1444 # fw("master_doc = 'index'\n")
1445 fw("copyright = u'Blender Foundation'\n")
1446 fw("version = '%s - API'\n" % BLENDER_VERSION_DOTS)
1447 fw("release = '%s - API'\n" % BLENDER_VERSION_DOTS)
1449 if ARGS.sphinx_theme != 'default':
1450 fw("html_theme = '%s'\n" % ARGS.sphinx_theme)
1452 if ARGS.sphinx_theme in SPHINX_THEMES['bf']:
1453 fw("html_theme_path = ['../']\n")
1454 # copied with the theme, exclude else we get an error [#28873]
1455 fw("html_favicon = 'favicon.ico'\n") # in <theme>/static/
1457 # not helpful since the source is generated, adds to upload size.
1458 fw("html_copy_source = False\n")
1461 # needed for latex, pdf gen
1462 fw("latex_documents = [ ('contents', 'contents.tex', 'Blender Index', 'Blender Foundation', 'manual'), ]\n")
1463 fw("latex_paper_size = 'a4paper'\n")
1467 def write_rst_contents(basepath):
1469 Write the rst file of the main page, needed for sphinx (index.html)
1471 filepath = os.path.join(basepath, "contents.rst")
1472 file = open(filepath, "w", encoding="utf-8")
1475 fw(title_string("Blender Documentation Contents", "%", double=True))
1477 fw("Welcome, this document is an API reference for Blender %s, built %s.\n" % (BLENDER_VERSION_DOTS, BLENDER_DATE))
1480 # fw("`A PDF version of this document is also available <%s>`_\n" % BLENDER_PDF_FILENAME)
1481 fw("`A compressed ZIP file of this site is available <%s>`_\n" % BLENDER_ZIP_FILENAME)
1485 if not EXCLUDE_INFO_DOCS:
1486 fw(title_string("Blender/Python Documentation", "=", double=True))
1488 fw(".. toctree::\n")
1489 fw(" :maxdepth: 1\n\n")
1490 for info, info_desc in INFO_DOCS:
1491 fw(" %s <%s>\n\n" % (info_desc, info))
1494 fw(title_string("Application Modules", "=", double=True))
1495 fw(".. toctree::\n")
1496 fw(" :maxdepth: 1\n\n")
1499 "bpy.context", # note: not actually a module
1500 "bpy.data", # note: not actually a module
1514 for mod in app_modules:
1515 if mod not in EXCLUDE_MODULES:
1518 fw(title_string("Standalone Modules", "=", double=True))
1519 fw(".. toctree::\n")
1520 fw(" :maxdepth: 1\n\n")
1522 standalone_modules = (
1524 "mathutils", "mathutils.geometry", "mathutils.noise",
1526 "bgl", "blf", "gpu", "aud", "bpy_extras",
1528 "bmesh", "bmesh.types", "bmesh.utils",
1531 for mod in standalone_modules:
1532 if mod not in EXCLUDE_MODULES:
1536 if "bge" not in EXCLUDE_MODULES:
1537 fw(title_string("Game Engine Modules", "=", double=True))
1538 fw(".. toctree::\n")
1539 fw(" :maxdepth: 1\n\n")
1540 fw(" bge.types.rst\n\n")
1541 fw(" bge.logic.rst\n\n")
1542 fw(" bge.render.rst\n\n")
1543 fw(" bge.texture.rst\n\n")
1544 fw(" bge.events.rst\n\n")
1545 fw(" bge.constraints.rst\n\n")
1547 # rna generated change log
1548 fw(title_string("API Info", "=", double=True))
1549 fw(".. toctree::\n")
1550 fw(" :maxdepth: 1\n\n")
1551 fw(" change_log.rst\n\n")
1555 fw(".. note:: The Blender Python API has areas which are still in development.\n")
1557 fw(" The following areas are subject to change.\n")
1558 fw(" * operator behavior, names and arguments\n")
1559 fw(" * mesh creation and editing functions\n")
1561 fw(" These parts of the API are relatively stable and are unlikely to change significantly\n")
1562 fw(" * data API, access to attributes of blender data such as mesh verts, material color, timeline frames and scene objects\n")
1563 fw(" * user interface functions for defining buttons, creation of menus, headers, panels\n")
1564 fw(" * render engine integration\n")
1565 fw(" * modules: bgl, mathutils & game engine.\n")
1571 def write_rst_bpy(basepath):
1573 Write rst file of bpy module (disabled by default)
1576 filepath = os.path.join(basepath, "bpy.rst")
1577 file = open(filepath, "w", encoding="utf-8")
1582 title = ":mod:`bpy` --- Blender Python Module"
1584 fw(title_string(title, "="))
1586 fw(".. module:: bpy.types\n\n")
1590 def write_rst_types_index(basepath):
1592 Write the rst file of bpy.types module (index)
1594 if "bpy.types" not in EXCLUDE_MODULES:
1595 filepath = os.path.join(basepath, "bpy.types.rst")
1596 file = open(filepath, "w", encoding="utf-8")
1598 fw(title_string("Types (bpy.types)", "="))
1599 fw(".. toctree::\n")
1601 fw(" bpy.types.*\n\n")
1605 def write_rst_ops_index(basepath):
1607 Write the rst file of bpy.ops module (index)
1609 if "bpy.ops" not in EXCLUDE_MODULES:
1610 filepath = os.path.join(basepath, "bpy.ops.rst")
1611 file = open(filepath, "w", encoding="utf-8")
1613 fw(title_string("Operators (bpy.ops)", "="))
1614 write_example_ref("", fw, "bpy.ops")
1615 fw(".. toctree::\n")
1617 fw(" bpy.ops.*\n\n")
1621 def write_rst_data(basepath):
1623 Write the rst file of bpy.data module
1625 if "bpy.data" not in EXCLUDE_MODULES:
1626 # not actually a module, only write this file so we
1627 # can reference in the TOC
1628 filepath = os.path.join(basepath, "bpy.data.rst")
1629 file = open(filepath, "w", encoding="utf-8")
1631 fw(title_string("Data Access (bpy.data)", "="))
1632 fw(".. module:: bpy\n")
1634 fw("This module is used for all blender/python access.\n")
1636 fw(".. data:: data\n")
1638 fw(" Access to blenders internal data\n")
1640 fw(" :type: :class:`bpy.types.BlendData`\n")
1642 fw(".. literalinclude:: ../examples/bpy.data.py\n")
1645 EXAMPLE_SET_USED.add("bpy.data")
1648 def write_rst_importable_modules(basepath):
1650 Write the rst files of importable modules
1652 importable_modules = {
1654 "bpy.path" : "Path Utilities",
1655 "bpy.utils" : "Utilities",
1656 "bpy_extras" : "Extra Utilities",
1659 "aud" : "Audio System",
1660 "blf" : "Font Drawing",
1661 "bmesh" : "BMesh Module",
1662 "bmesh.types" : "BMesh Types",
1663 "bmesh.utils" : "BMesh Utilities",
1664 "bpy.app" : "Application Data",
1665 "bpy.app.handlers" : "Application Handlers",
1666 "bpy.props" : "Property Definitions",
1667 "mathutils" : "Math Types & Utilities",
1668 "mathutils.geometry": "Geometry Utilities",
1669 "mathutils.noise" : "Noise Utilities",
1671 for mod_name, mod_descr in importable_modules.items():
1672 if mod_name not in EXCLUDE_MODULES:
1673 module = __import__(mod_name,
1674 fromlist=[mod_name.rsplit(".", 1)[-1]])
1675 pymodule2sphinx(basepath, mod_name, module, mod_descr)
1678 def copy_handwritten_rsts(basepath):
1681 if not EXCLUDE_INFO_DOCS:
1682 for info, info_desc in INFO_DOCS:
1683 shutil.copy2(os.path.join(RST_DIR, info), basepath)
1685 # TODO put this docs in blender's code and use import as per modules above
1686 handwritten_modules = [
1693 "bgl", # "Blender OpenGl wrapper"
1694 "gpu", # "GPU Shader Module"
1699 for mod_name in handwritten_modules:
1700 if mod_name not in EXCLUDE_MODULES:
1701 # copy2 keeps time/date stamps
1702 shutil.copy2(os.path.join(RST_DIR, "%s.rst" % mod_name), basepath)
1705 shutil.copy2(os.path.join(RST_DIR, "change_log.rst"), basepath)
1708 def rna2sphinx(basepath):
1716 write_sphinx_conf_py(basepath)
1719 write_rst_contents(basepath)
1722 if "bpy.context" not in EXCLUDE_MODULES:
1723 # one of a kind, context doc (uses ctypes to extract info!)
1724 # doesn't work on mac
1725 if PLATFORM != "darwin":
1726 pycontext2sphinx(basepath)
1729 write_rst_bpy(basepath) # bpy, disabled by default
1730 write_rst_types_index(basepath) # bpy.types
1731 write_rst_ops_index(basepath) # bpy.ops
1732 pyrna2sphinx(basepath) # bpy.types.* and bpy.ops.*
1733 write_rst_data(basepath) # bpy.data
1734 write_rst_importable_modules(basepath)
1736 # copy the other rsts
1737 copy_handwritten_rsts(basepath)
1740 def align_sphinx_in_to_sphinx_in_tmp():
1742 Move changed files from SPHINX_IN_TMP to SPHINX_IN
1746 sphinx_in_files = set(os.listdir(SPHINX_IN))
1747 sphinx_in_tmp_files = set(os.listdir(SPHINX_IN_TMP))
1749 # remove deprecated files that have been removed
1750 for f in sorted(sphinx_in_files):
1751 if f not in sphinx_in_tmp_files:
1752 BPY_LOGGER.debug("\tdeprecated: %s" % f)
1753 os.remove(os.path.join(SPHINX_IN, f))
1755 # freshen with new files.
1756 for f in sorted(sphinx_in_tmp_files):
1757 f_from = os.path.join(SPHINX_IN_TMP, f)
1758 f_to = os.path.join(SPHINX_IN, f)
1761 if f in sphinx_in_files:
1762 if filecmp.cmp(f_from, f_to):
1766 BPY_LOGGER.debug("\tupdating: %s" % f)
1767 shutil.copy(f_from, f_to)
1770 def refactor_sphinx_log(sphinx_logfile):
1772 with open(sphinx_logfile, "r", encoding="utf-8") as original_logfile:
1773 lines = set(original_logfile.readlines())
1775 if 'warning' in line.lower() or 'error' in line.lower():
1776 line = line.strip().split(None, 2)
1778 location, kind, msg = line
1779 location = os.path.relpath(location, start=SPHINX_IN)
1780 refactored_log.append((kind, location, msg))
1781 with open(sphinx_logfile, "w", encoding="utf-8") as refactored_logfile:
1782 for log in sorted(refactored_log):
1783 refactored_logfile.write("%-12s %s\n %s\n" % log)
1788 # eventually, create the dirs
1789 for dir_path in [ARGS.output_dir, SPHINX_IN]:
1790 if not os.path.exists(dir_path):
1793 # eventually, log in files
1795 bpy_logfile = os.path.join(ARGS.output_dir, ".bpy.log")
1796 bpy_logfilehandler = logging.FileHandler(bpy_logfile, mode="w")
1797 bpy_logfilehandler.setLevel(logging.DEBUG)
1798 BPY_LOGGER.addHandler(bpy_logfilehandler)
1800 # using a FileHandler seems to disable the stdout, so we add a StreamHandler
1801 bpy_log_stdout_handler = logging.StreamHandler(stream=sys.stdout)
1802 bpy_log_stdout_handler.setLevel(logging.DEBUG)
1803 BPY_LOGGER.addHandler(bpy_log_stdout_handler)
1805 # in case of out-of-source build, copy the needed dirs
1806 if ARGS.output_dir != SCRIPT_DIR:
1808 examples_dir_copy = os.path.join(ARGS.output_dir, "examples")
1809 if os.path.exists(examples_dir_copy):
1810 shutil.rmtree(examples_dir_copy, True)
1811 shutil.copytree(EXAMPLES_DIR,
1813 ignore=shutil.ignore_patterns(*(".svn",)),
1814 copy_function=shutil.copy)
1816 # eventually, copy the theme dir
1817 if ARGS.sphinx_theme in SPHINX_THEMES['bf']:
1818 if os.path.exists(SPHINX_THEME_DIR):
1819 shutil.rmtree(SPHINX_THEME_DIR, True)
1820 shutil.copytree(SPHINX_THEME_SVN_DIR,
1822 ignore=shutil.ignore_patterns(*(".svn",)),
1823 copy_function=shutil.copy)
1825 # dump the api in rst files
1826 if os.path.exists(SPHINX_IN_TMP):
1827 shutil.rmtree(SPHINX_IN_TMP, True)
1829 rna2sphinx(SPHINX_IN_TMP)
1831 if ARGS.full_rebuild:
1832 # only for full updates
1833 shutil.rmtree(SPHINX_IN, True)
1834 shutil.copytree(SPHINX_IN_TMP,
1836 copy_function=shutil.copy)
1837 if ARGS.sphinx_build and os.path.exists(SPHINX_OUT):
1838 shutil.rmtree(SPHINX_OUT, True)
1839 if ARGS.sphinx_build_pdf and os.path.exists(SPHINX_OUT_PDF):
1840 shutil.rmtree(SPHINX_OUT_PDF, True)
1842 # move changed files in SPHINX_IN
1843 align_sphinx_in_to_sphinx_in_tmp()
1845 # report which example files weren't used
1846 EXAMPLE_SET_UNUSED = EXAMPLE_SET - EXAMPLE_SET_USED
1847 if EXAMPLE_SET_UNUSED:
1848 BPY_LOGGER.debug("\nUnused examples found in '%s'..." % EXAMPLES_DIR)
1849 for f in sorted(EXAMPLE_SET_UNUSED):
1850 BPY_LOGGER.debug(" %s.py" % f)
1851 BPY_LOGGER.debug(" %d total\n" % len(EXAMPLE_SET_UNUSED))
1853 # eventually, build the html docs
1854 if ARGS.sphinx_build:
1856 subprocess.call(SPHINX_BUILD)
1858 # sphinx-build log cleanup+sort
1860 if os.stat(SPHINX_BUILD_LOG).st_size:
1861 refactor_sphinx_log(SPHINX_BUILD_LOG)
1863 # eventually, build the pdf docs
1864 if ARGS.sphinx_build_pdf:
1866 subprocess.call(SPHINX_BUILD_PDF)
1867 subprocess.call(SPHINX_MAKE_PDF, stdout=SPHINX_MAKE_PDF_STDOUT)
1869 # sphinx-build log cleanup+sort
1871 if os.stat(SPHINX_BUILD_PDF_LOG).st_size:
1872 refactor_sphinx_log(SPHINX_BUILD_PDF_LOG)
1874 # eventually, prepare the dir to be deployed online (REFERENCE_PATH)
1875 if ARGS.pack_reference:
1877 if ARGS.sphinx_build:
1878 # delete REFERENCE_PATH
1879 if os.path.exists(REFERENCE_PATH):
1880 shutil.rmtree(REFERENCE_PATH, True)
1882 # copy SPHINX_OUT to the REFERENCE_PATH
1883 ignores = ('.doctrees', 'objects.inv', '.buildinfo')
1884 shutil.copytree(SPHINX_OUT,
1886 ignore=shutil.ignore_patterns(*ignores))
1887 shutil.copy(os.path.join(REFERENCE_PATH, "contents.html"),
1888 os.path.join(REFERENCE_PATH, "index.html"))
1890 # zip REFERENCE_PATH
1891 basename = os.path.join(ARGS.output_dir, REFERENCE_NAME)
1892 tmp_path = shutil.make_archive(basename, 'zip',
1893 root_dir=ARGS.output_dir,
1894 base_dir=REFERENCE_NAME)
1895 final_path = os.path.join(REFERENCE_PATH, BLENDER_ZIP_FILENAME)
1896 os.rename(tmp_path, final_path)
1898 if ARGS.sphinx_build_pdf:
1899 # copy the pdf to REFERENCE_PATH
1900 shutil.copy(os.path.join(SPHINX_OUT_PDF, "contents.pdf"),
1901 os.path.join(REFERENCE_PATH, BLENDER_PDF_FILENAME))
1906 if __name__ == '__main__':