Update py API doc generation tools to comply to new name scheme on server.
[blender.git] / doc / python_api / sphinx_doc_gen.py
1 # ##### BEGIN GPL LICENSE BLOCK #####
2 #
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.
7 #
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.
12 #
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.
16 #
17 # Contributor(s): Campbell Barton
18 #
19 # ##### END GPL LICENSE BLOCK #####
20
21 # <pep8 compliant>
22
23 SCRIPT_HELP_MSG = """
24
25 API dump in RST files
26 ---------------------
27   Run this script from Blender's root path once you have compiled Blender
28
29     blender --background --factory-startup -noaudio --python doc/python_api/sphinx_doc_gen.py
30
31   This will generate python files in doc/python_api/sphinx-in/
32   providing ./blender is or links to the blender executable
33
34   To choose sphinx-in directory:
35     blender --background --factory-startup --python doc/python_api/sphinx_doc_gen.py -- --output ../python_api
36
37   For quick builds:
38     blender --background --factory-startup --python doc/python_api/sphinx_doc_gen.py -- --partial bmesh.*
39
40
41 Sphinx: HTML generation
42 -----------------------
43   After you have built doc/python_api/sphinx-in (see above),
44   generate html docs by running:
45
46     cd doc/python_api
47     sphinx-build sphinx-in sphinx-out
48
49
50 Sphinx: PDF generation
51 ----------------------
52   After you have built doc/python_api/sphinx-in (see above),
53   generate the pdf doc by running:
54
55     sphinx-build -b latex doc/python_api/sphinx-in doc/python_api/sphinx-out
56     cd doc/python_api/sphinx-out
57     make
58
59 """
60
61 try:
62     import bpy  # Blender module
63 except ImportError:
64     print("\nERROR: this script must run from inside Blender")
65     print(SCRIPT_HELP_MSG)
66     import sys
67     sys.exit()
68
69 import rna_info  # Blender module
70
71
72 def rna_info_BuildRNAInfo_cache():
73     if rna_info_BuildRNAInfo_cache.ret is None:
74         rna_info_BuildRNAInfo_cache.ret = rna_info.BuildRNAInfo()
75     return rna_info_BuildRNAInfo_cache.ret
76 rna_info_BuildRNAInfo_cache.ret = None
77 # --- end rna_info cache
78
79 # import rpdb2; rpdb2.start_embedded_debugger('test')
80 import os
81 import sys
82 import inspect
83 import shutil
84 import logging
85
86 from platform import platform
87 PLATFORM = platform().split('-')[0].lower()  # 'linux', 'darwin', 'windows'
88
89 SCRIPT_DIR = os.path.abspath(os.path.dirname(__file__))
90
91
92 def handle_args():
93     '''
94     Parse the args passed to Blender after "--", ignored by Blender
95     '''
96     import argparse
97
98     # When --help is given, print the usage text
99     parser = argparse.ArgumentParser(
100         formatter_class=argparse.RawTextHelpFormatter,
101         usage=SCRIPT_HELP_MSG
102     )
103
104     # optional arguments
105     parser.add_argument("-p", "--partial",
106                         dest="partial",
107                         type=str,
108                         default="",
109                         help="Use a wildcard to only build specific module(s)\n"
110                              "Example: --partial bmesh*\n",
111                         required=False)
112
113     parser.add_argument("-f", "--fullrebuild",
114                         dest="full_rebuild",
115                         default=False,
116                         action='store_true',
117                         help="Rewrite all rst files in sphinx-in/ "
118                              "(default=False)",
119                         required=False)
120
121     parser.add_argument("-b", "--bpy",
122                         dest="bpy",
123                         default=False,
124                         action='store_true',
125                         help="Write the rst file of the bpy module "
126                              "(default=False)",
127                         required=False)
128
129     parser.add_argument("-o", "--output",
130                         dest="output_dir",
131                         type=str,
132                         default=SCRIPT_DIR,
133                         help="Path of the API docs (default=<script dir>)",
134                         required=False)
135
136     parser.add_argument("-T", "--sphinx-theme",
137                         dest="sphinx_theme",
138                         type=str,
139                         default="classic",
140                         help="Sphinx theme (default='classic'), "
141                         "see: http://sphinx-doc.org/theming.html",
142                         required=False)
143
144     parser.add_argument("-N", "--sphinx-named-output",
145                         dest="sphinx_named_output",
146                         default=False,
147                         action='store_true',
148                         help="Add the theme name to the html dir name.\n"
149                              "Example: \"sphinx-out_haiku\" (default=False)",
150                         required=False)
151
152     parser.add_argument("-B", "--sphinx-build",
153                         dest="sphinx_build",
154                         default=False,
155                         action='store_true',
156                         help="Build the html docs by running:\n"
157                              "sphinx-build SPHINX_IN SPHINX_OUT\n"
158                              "(default=False; does not depend on -P)",
159                         required=False)
160
161     parser.add_argument("-P", "--sphinx-build-pdf",
162                         dest="sphinx_build_pdf",
163                         default=False,
164                         action='store_true',
165                         help="Build the pdf by running:\n"
166                              "sphinx-build -b latex SPHINX_IN SPHINX_OUT_PDF\n"
167                              "(default=False; does not depend on -B)",
168                         required=False)
169
170     parser.add_argument("-R", "--pack-reference",
171                         dest="pack_reference",
172                         default=False,
173                         action='store_true',
174                         help="Pack all necessary files in the deployed dir.\n"
175                              "(default=False; use with -B and -P)",
176                         required=False)
177
178     parser.add_argument("-l", "--log",
179                         dest="log",
180                         default=False,
181                         action='store_true',
182                         help="Log the output of the api dump and sphinx|latex "
183                              "warnings and errors (default=False).\n"
184                              "If given, save logs in:\n"
185                              "* OUTPUT_DIR/.bpy.log\n"
186                              "* OUTPUT_DIR/.sphinx-build.log\n"
187                              "* OUTPUT_DIR/.sphinx-build_pdf.log\n"
188                              "* OUTPUT_DIR/.latex_make.log",
189                         required=False)
190
191     # parse only the args passed after '--'
192     argv = []
193     if "--" in sys.argv:
194         argv = sys.argv[sys.argv.index("--") + 1:]  # get all args after "--"
195
196     return parser.parse_args(argv)
197
198
199 ARGS = handle_args()
200
201 # ----------------------------------BPY-----------------------------------------
202
203 BPY_LOGGER = logging.getLogger('bpy')
204 BPY_LOGGER.setLevel(logging.DEBUG)
205
206 """
207 # for quick rebuilds
208 rm -rf /b/doc/python_api/sphinx-* && \
209 ./blender -b -noaudio --factory-startup -P doc/python_api/sphinx_doc_gen.py && \
210 sphinx-build doc/python_api/sphinx-in doc/python_api/sphinx-out
211
212 or
213
214 ./blender -b -noaudio --factory-startup -P doc/python_api/sphinx_doc_gen.py -- -f -B
215 """
216
217 # Switch for quick testing so doc-builds don't take so long
218 if not ARGS.partial:
219     # full build
220     FILTER_BPY_OPS = None
221     FILTER_BPY_TYPES = None
222     EXCLUDE_INFO_DOCS = False
223     EXCLUDE_MODULES = []
224
225 else:
226     # can manually edit this too:
227     # FILTER_BPY_OPS = ("import.scene", )  # allow
228     # FILTER_BPY_TYPES = ("bpy_struct", "Operator", "ID")  # allow
229     EXCLUDE_INFO_DOCS = True
230     EXCLUDE_MODULES = [
231         "aud",
232         "bge",
233         "bge.app"
234         "bge.constraints",
235         "bge.events",
236         "bge.logic",
237         "bge.render",
238         "bge.texture",
239         "bge.types",
240         "bgl",
241         "blf",
242         "bmesh",
243         "bmesh.ops",
244         "bmesh.types",
245         "bmesh.utils",
246         "bmesh.geometry",
247         "bpy.app",
248         "bpy.app.handlers",
249         "bpy.app.translations",
250         "bpy.context",
251         "bpy.data",
252         "bpy.ops",  # supports filtering
253         "bpy.path",
254         "bpy.props",
255         "bpy.types",  # supports filtering
256         "bpy.utils",
257         "bpy.utils.previews",
258         "bpy_extras",
259         "gpu",
260         "gpu.offscreen",
261         "idprop.types",
262         "mathutils",
263         "mathutils.bvhtree",
264         "mathutils.geometry",
265         "mathutils.interpolate",
266         "mathutils.kdtree",
267         "mathutils.noise",
268         "freestyle",
269         "freestyle.chainingiterators",
270         "freestyle.functions",
271         "freestyle.predicates",
272         "freestyle.shaders",
273         "freestyle.types",
274         "freestyle.utils",
275     ]
276
277     # ------
278     # Filter
279     #
280     # TODO, support bpy.ops and bpy.types filtering
281     import fnmatch
282     m = None
283     EXCLUDE_MODULES = [m for m in EXCLUDE_MODULES if not fnmatch.fnmatchcase(m, ARGS.partial)]
284
285     # special support for bpy.types.XXX
286     FILTER_BPY_OPS = tuple([m[8:] for m in ARGS.partial.split(":") if m.startswith("bpy.ops.")])
287     if FILTER_BPY_OPS:
288         EXCLUDE_MODULES.remove("bpy.ops")
289
290     FILTER_BPY_TYPES = tuple([m[10:] for m in ARGS.partial.split(":") if m.startswith("bpy.types.")])
291     if FILTER_BPY_TYPES:
292         EXCLUDE_MODULES.remove("bpy.types")
293
294     print(FILTER_BPY_TYPES)
295
296     EXCLUDE_INFO_DOCS = (not fnmatch.fnmatchcase("info", ARGS.partial))
297
298     del m
299     del fnmatch
300
301     BPY_LOGGER.debug(
302         "Partial Doc Build, Skipping: %s\n" %
303         "\n                             ".join(sorted(EXCLUDE_MODULES)))
304
305     #
306     # done filtering
307     # --------------
308
309 try:
310     __import__("aud")
311 except ImportError:
312     BPY_LOGGER.debug("Warning: Built without 'aud' module, docs incomplete...")
313     EXCLUDE_MODULES.append("aud")
314
315 try:
316     __import__("freestyle")
317 except ImportError:
318     BPY_LOGGER.debug("Warning: Built without 'freestyle' module, docs incomplete...")
319     EXCLUDE_MODULES.extend([
320         "freestyle",
321         "freestyle.chainingiterators",
322         "freestyle.functions",
323         "freestyle.predicates",
324         "freestyle.shaders",
325         "freestyle.types",
326         "freestyle.utils",
327     ])
328
329 # Source files we use, and need to copy to the OUTPUT_DIR
330 # to have working out-of-source builds.
331 # Note that ".." is replaced by "__" in the RST files,
332 # to avoid having to match Blender's source tree.
333 EXTRA_SOURCE_FILES = (
334     "../../../release/scripts/templates_py/bmesh_simple.py",
335     "../../../release/scripts/templates_py/operator_simple.py",
336     "../../../release/scripts/templates_py/ui_panel_simple.py",
337     "../../../release/scripts/templates_py/ui_previews_custom_icon.py",
338     "../examples/bge.constraints.py",
339     "../examples/bge.texture.1.py",
340     "../examples/bge.texture.2.py",
341     "../examples/bge.texture.py",
342     "../examples/bmesh.ops.1.py",
343     "../examples/bpy.app.translations.py",
344 )
345
346
347 # examples
348 EXAMPLES_DIR = os.path.abspath(os.path.join(SCRIPT_DIR, "examples"))
349 EXAMPLE_SET = set()
350 for f in os.listdir(EXAMPLES_DIR):
351     if f.endswith(".py"):
352         EXAMPLE_SET.add(os.path.splitext(f)[0])
353 EXAMPLE_SET_USED = set()
354
355 # rst files dir
356 RST_DIR = os.path.abspath(os.path.join(SCRIPT_DIR, "rst"))
357
358 # extra info, not api reference docs
359 # stored in ./rst/info_*
360 INFO_DOCS = (
361     ("info_quickstart.rst",
362      "Blender/Python Quickstart: new to Blender/scripting and want to get your feet wet?"),
363     ("info_overview.rst",
364      "Blender/Python API Overview: a more complete explanation of Python integration"),
365     ("info_tutorial_addon.rst",
366      "Blender/Python Add-on Tutorial: a step by step guide on how to write an add-on from scratch"),
367     ("info_api_reference.rst",
368      "Blender/Python API Reference Usage: examples of how to use the API reference docs"),
369     ("info_best_practice.rst",
370      "Best Practice: Conventions to follow for writing good scripts"),
371     ("info_tips_and_tricks.rst",
372      "Tips and Tricks: Hints to help you while writing scripts for Blender"),
373     ("info_gotcha.rst",
374      "Gotcha's: some of the problems you may come up against when writing scripts"),
375 )
376
377 # only support for properties atm.
378 RNA_BLACKLIST = {
379     # XXX messes up PDF!, really a bug but for now just workaround.
380     "UserPreferencesSystem": {"language", }
381 }
382
383 MODULE_GROUPING = {
384     "bmesh.types": (
385         ("Base Mesh Type", '-'),
386         "BMesh",
387         ("Mesh Elements", '-'),
388         "BMVert",
389         "BMEdge",
390         "BMFace",
391         "BMLoop",
392         ("Sequence Accessors", '-'),
393         "BMElemSeq",
394         "BMVertSeq",
395         "BMEdgeSeq",
396         "BMFaceSeq",
397         "BMLoopSeq",
398         "BMIter",
399         ("Selection History", '-'),
400         "BMEditSelSeq",
401         "BMEditSelIter",
402         ("Custom-Data Layer Access", '-'),
403         "BMLayerAccessVert",
404         "BMLayerAccessEdge",
405         "BMLayerAccessFace",
406         "BMLayerAccessLoop",
407         "BMLayerCollection",
408         "BMLayerItem",
409         ("Custom-Data Layer Types", '-'),
410         "BMLoopUV",
411         "BMDeformVert"
412     )
413 }
414
415 # --------------------configure compile time options----------------------------
416
417 # -------------------------------BLENDER----------------------------------------
418
419 blender_version_strings = [str(v) for v in bpy.app.version]
420
421 # converting bytes to strings, due to T30154
422 BLENDER_REVISION = str(bpy.app.build_hash, 'utf_8')
423 BLENDER_DATE = str(bpy.app.build_date, 'utf_8')
424
425 BLENDER_VERSION_DOTS = ".".join(blender_version_strings)    # '2.62.1'
426 if BLENDER_REVISION != "Unknown":
427     BLENDER_VERSION_DOTS += " " + BLENDER_REVISION          # '2.62.1 SHA1'
428
429 BLENDER_VERSION_PATH = "_".join(blender_version_strings)    # '2_62_1'
430 if bpy.app.version_cycle in {"rc", "release"}:
431     # '2_62a_release'
432     BLENDER_VERSION_PATH = "%s%s_release" % ("_".join(blender_version_strings[:2]), bpy.app.version_char)
433
434 # --------------------------DOWNLOADABLE FILES----------------------------------
435
436 REFERENCE_NAME = "blender_python_reference_%s" % BLENDER_VERSION_PATH
437 REFERENCE_PATH = os.path.join(ARGS.output_dir, REFERENCE_NAME)
438 BLENDER_PDF_FILENAME = "%s.pdf" % REFERENCE_NAME
439 BLENDER_ZIP_FILENAME = "%s.zip" % REFERENCE_NAME
440
441 # -------------------------------SPHINX-----------------------------------------
442
443 if ARGS.sphinx_theme == "blender-org":
444     SPHINX_THEME_DIR = os.path.join(ARGS.output_dir, ARGS.sphinx_theme)
445     SPHINX_THEME_SVN_DIR = os.path.join(SCRIPT_DIR, ARGS.sphinx_theme)
446
447 SPHINX_IN = os.path.join(ARGS.output_dir, "sphinx-in")
448 SPHINX_IN_TMP = SPHINX_IN + "-tmp"
449 SPHINX_OUT = os.path.join(ARGS.output_dir, "sphinx-out")
450 if ARGS.sphinx_named_output:
451     SPHINX_OUT += "_%s" % ARGS.sphinx_theme
452
453 # html build
454 if ARGS.sphinx_build:
455     SPHINX_BUILD = ["sphinx-build", SPHINX_IN, SPHINX_OUT]
456
457     if ARGS.log:
458         SPHINX_BUILD_LOG = os.path.join(ARGS.output_dir, ".sphinx-build.log")
459         SPHINX_BUILD = ["sphinx-build",
460                         "-w", SPHINX_BUILD_LOG,
461                         SPHINX_IN, SPHINX_OUT]
462
463 # pdf build
464 if ARGS.sphinx_build_pdf:
465     SPHINX_OUT_PDF = os.path.join(ARGS.output_dir, "sphinx-out_pdf")
466     SPHINX_BUILD_PDF = ["sphinx-build",
467                         "-b", "latex",
468                         SPHINX_IN, SPHINX_OUT_PDF]
469     SPHINX_MAKE_PDF = ["make", "-C", SPHINX_OUT_PDF]
470     SPHINX_MAKE_PDF_STDOUT = None
471
472     if ARGS.log:
473         SPHINX_BUILD_PDF_LOG = os.path.join(ARGS.output_dir, ".sphinx-build_pdf.log")
474         SPHINX_BUILD_PDF = ["sphinx-build", "-b", "latex",
475                             "-w", SPHINX_BUILD_PDF_LOG,
476                             SPHINX_IN, SPHINX_OUT_PDF]
477
478         sphinx_make_pdf_log = os.path.join(ARGS.output_dir, ".latex_make.log")
479         SPHINX_MAKE_PDF_STDOUT = open(sphinx_make_pdf_log, "w", encoding="utf-8")
480
481 # --------------------------------API DUMP--------------------------------------
482
483 # lame, python wont give some access
484 ClassMethodDescriptorType = type(dict.__dict__['fromkeys'])
485 MethodDescriptorType = type(dict.get)
486 GetSetDescriptorType = type(int.real)
487 StaticMethodType = type(staticmethod(lambda: None))
488 from types import (
489     MemberDescriptorType,
490     MethodType,
491     FunctionType,
492 )
493
494 _BPY_STRUCT_FAKE = "bpy_struct"
495 _BPY_PROP_COLLECTION_FAKE = "bpy_prop_collection"
496
497 if _BPY_PROP_COLLECTION_FAKE:
498     _BPY_PROP_COLLECTION_ID = ":class:`%s`" % _BPY_PROP_COLLECTION_FAKE
499 else:
500     _BPY_PROP_COLLECTION_ID = "collection"
501
502
503 def escape_rst(text):
504     """ Escape plain text which may contain characters used by RST.
505     """
506     return text.translate(escape_rst.trans)
507 escape_rst.trans = str.maketrans({
508     "`": "\\`",
509     "|": "\\|",
510     "*": "\\*",
511     "\\": "\\\\",
512 })
513
514
515 def is_struct_seq(value):
516     return isinstance(value, tuple) and type(tuple) != tuple and hasattr(value, "n_fields")
517
518
519 def undocumented_message(module_name, type_name, identifier):
520     return "Undocumented"
521
522     """
523     if str(type_name).startswith('<module'):
524         preloadtitle = '%s.%s' % (module_name, identifier)
525     else:
526         preloadtitle = '%s.%s.%s' % (module_name, type_name, identifier)
527     message = ("Undocumented (`contribute "
528                "<http://wiki.blender.org/index.php/"
529                "Dev:2.5/Py/API/Generating_API_Reference/Contribute"
530                "?action=edit"
531                "&section=new"
532                "&preload=Dev:2.5/Py/API/Generating_API_Reference/Contribute/Howto-message"
533                "&preloadtitle=%s>`_)\n\n" % preloadtitle)
534     return message
535     """
536
537
538 def range_str(val):
539     '''
540     Converts values to strings for the range directive.
541     (unused function it seems)
542     '''
543     if val < -10000000:
544         return '-inf'
545     elif val > 10000000:
546         return 'inf'
547     elif type(val) == float:
548         return '%g' % val
549     else:
550         return str(val)
551
552
553 def example_extract_docstring(filepath):
554     file = open(filepath, "r", encoding="utf-8")
555     line = file.readline()
556     line_no = 0
557     text = []
558     if line.startswith('"""'):  # assume nothing here
559         line_no += 1
560     else:
561         file.close()
562         return "", 0
563
564     for line in file.readlines():
565         line_no += 1
566         if line.startswith('"""'):
567             break
568         else:
569             text.append(line.rstrip())
570
571     line_no += 1
572     file.close()
573     return "\n".join(text), line_no
574
575
576 def title_string(text, heading_char, double=False):
577     filler = len(text) * heading_char
578
579     if double:
580         return "%s\n%s\n%s\n\n" % (filler, text, filler)
581     else:
582         return "%s\n%s\n\n" % (text, filler)
583
584
585 def write_example_ref(ident, fw, example_id, ext="py"):
586     if example_id in EXAMPLE_SET:
587
588         # extract the comment
589         filepath = os.path.join("..", "examples", "%s.%s" % (example_id, ext))
590         filepath_full = os.path.join(os.path.dirname(fw.__self__.name), filepath)
591
592         text, line_no = example_extract_docstring(filepath_full)
593
594         for line in text.split("\n"):
595             fw("%s\n" % (ident + line).rstrip())
596         fw("\n")
597
598         fw("%s.. literalinclude:: %s\n" % (ident, filepath))
599         if line_no > 0:
600             fw("%s   :lines: %d-\n" % (ident, line_no))
601         fw("\n")
602         EXAMPLE_SET_USED.add(example_id)
603     else:
604         if bpy.app.debug:
605             BPY_LOGGER.debug("\tskipping example: " + example_id)
606
607     # Support for numbered files bpy.types.Operator -> bpy.types.Operator.1.py
608     i = 1
609     while True:
610         example_id_num = "%s.%d" % (example_id, i)
611         if example_id_num in EXAMPLE_SET:
612             write_example_ref(ident, fw, example_id_num, ext)
613             i += 1
614         else:
615             break
616
617
618 def write_indented_lines(ident, fn, text, strip=True):
619     '''
620     Apply same indentation to all lines in a multilines text.
621     '''
622     if text is None:
623         return
624
625     lines = text.split("\n")
626
627     # strip empty lines from the start/end
628     while lines and not lines[0].strip():
629         del lines[0]
630     while lines and not lines[-1].strip():
631         del lines[-1]
632
633     if strip:
634         # set indentation to <indent>
635         ident_strip = 1000
636         for l in lines:
637             if l.strip():
638                 ident_strip = min(ident_strip, len(l) - len(l.lstrip()))
639         for l in lines:
640             fn(ident + l[ident_strip:] + "\n")
641     else:
642         # add <indent> number of blanks to the current indentation
643         for l in lines:
644             fn(ident + l + "\n")
645
646
647 def pymethod2sphinx(ident, fw, identifier, py_func):
648     '''
649     class method to sphinx
650     '''
651     arg_str = inspect.formatargspec(*inspect.getargspec(py_func))
652     if arg_str.startswith("(self, "):
653         arg_str = "(" + arg_str[7:]
654         func_type = "method"
655     elif arg_str.startswith("(cls, "):
656         arg_str = "(" + arg_str[6:]
657         func_type = "classmethod"
658     else:
659         func_type = "staticmethod"
660
661     fw(ident + ".. %s:: %s%s\n\n" % (func_type, identifier, arg_str))
662     if py_func.__doc__:
663         write_indented_lines(ident + "   ", fw, py_func.__doc__)
664         fw("\n")
665
666
667 def pyfunc2sphinx(ident, fw, module_name, type_name, identifier, py_func, is_class=True):
668     '''
669     function or class method to sphinx
670     '''
671
672     if type(py_func) == MethodType:
673         return
674
675     arg_str = inspect.formatargspec(*inspect.getfullargspec(py_func))
676
677     if not is_class:
678         func_type = "function"
679
680         # ther rest are class methods
681     elif arg_str.startswith("(self, ") or arg_str == "(self)":
682         arg_str = "()" if (arg_str == "(self)") else ("(" + arg_str[7:])
683         func_type = "method"
684     elif arg_str.startswith("(cls, "):
685         arg_str = "()" if (arg_str == "(cls)") else ("(" + arg_str[6:])
686         func_type = "classmethod"
687     else:
688         func_type = "staticmethod"
689
690     doc = py_func.__doc__
691     if (not doc) or (not doc.startswith(".. %s:: " % func_type)):
692         fw(ident + ".. %s:: %s%s\n\n" % (func_type, identifier, arg_str))
693         ident_temp = ident + "   "
694     else:
695         ident_temp = ident
696
697     if doc:
698         write_indented_lines(ident_temp, fw, doc)
699         fw("\n")
700     del doc, ident_temp
701
702     if is_class:
703         write_example_ref(ident + "   ", fw, module_name + "." + type_name + "." + identifier)
704     else:
705         write_example_ref(ident + "   ", fw, module_name + "." + identifier)
706
707
708 def py_descr2sphinx(ident, fw, descr, module_name, type_name, identifier):
709     if identifier.startswith("_"):
710         return
711
712     doc = descr.__doc__
713     if not doc:
714         doc = undocumented_message(module_name, type_name, identifier)
715
716     if type(descr) == GetSetDescriptorType:
717         fw(ident + ".. attribute:: %s\n\n" % identifier)
718         write_indented_lines(ident + "   ", fw, doc, False)
719         fw("\n")
720     elif type(descr) == MemberDescriptorType:  # same as above but use 'data'
721         fw(ident + ".. data:: %s\n\n" % identifier)
722         write_indented_lines(ident + "   ", fw, doc, False)
723         fw("\n")
724     elif type(descr) in {MethodDescriptorType, ClassMethodDescriptorType}:
725         write_indented_lines(ident, fw, doc, False)
726         fw("\n")
727     else:
728         raise TypeError("type was not GetSetDescriptorType, MethodDescriptorType or ClassMethodDescriptorType")
729
730     write_example_ref(ident + "   ", fw, module_name + "." + type_name + "." + identifier)
731     fw("\n")
732
733
734 def py_c_func2sphinx(ident, fw, module_name, type_name, identifier, py_func, is_class=True):
735     '''
736     c defined function to sphinx.
737     '''
738
739     # dump the docstring, assume its formatted correctly
740     if py_func.__doc__:
741         write_indented_lines(ident, fw, py_func.__doc__, False)
742         fw("\n")
743     else:
744         fw(ident + ".. function:: %s()\n\n" % identifier)
745         fw(ident + "   " + undocumented_message(module_name, type_name, identifier))
746
747     if is_class:
748         write_example_ref(ident + "   ", fw, module_name + "." + type_name + "." + identifier)
749     else:
750         write_example_ref(ident + "   ", fw, module_name + "." + identifier)
751
752     fw("\n")
753
754
755 def pyprop2sphinx(ident, fw, identifier, py_prop):
756     '''
757     Python property to sphinx
758     '''
759     # readonly properties use "data" directive, variables use "attribute" directive
760     if py_prop.fset is None:
761         fw(ident + ".. data:: %s\n\n" % identifier)
762     else:
763         fw(ident + ".. attribute:: %s\n\n" % identifier)
764     write_indented_lines(ident + "   ", fw, py_prop.__doc__)
765     if py_prop.fset is None:
766         fw(ident + "   (readonly)\n\n")
767     else:
768         fw("\n")
769
770
771 def pymodule2sphinx(basepath, module_name, module, title):
772     import types
773     attribute_set = set()
774     filepath = os.path.join(basepath, module_name + ".rst")
775
776     module_all = getattr(module, "__all__", None)
777     module_dir = sorted(dir(module))
778
779     if module_all:
780         module_dir = module_all
781
782     # TODO - currently only used for classes
783     # grouping support
784     module_grouping = MODULE_GROUPING.get(module_name)
785
786     def module_grouping_index(name):
787         if module_grouping is not None:
788             try:
789                 return module_grouping.index(name)
790             except ValueError:
791                 pass
792         return -1
793
794     def module_grouping_heading(name):
795         if module_grouping is not None:
796             i = module_grouping_index(name) - 1
797             if i >= 0 and type(module_grouping[i]) == tuple:
798                 return module_grouping[i]
799         return None, None
800
801     def module_grouping_sort_key(name):
802         return module_grouping_index(name)
803     # done grouping support
804
805     file = open(filepath, "w", encoding="utf-8")
806
807     fw = file.write
808
809     fw(title_string("%s (%s)" % (title, module_name), "="))
810
811     fw(".. module:: %s\n\n" % module_name)
812
813     if module.__doc__:
814         # Note, may contain sphinx syntax, dont mangle!
815         fw(module.__doc__.strip())
816         fw("\n\n")
817
818     write_example_ref("", fw, module_name)
819
820     # write submodules
821     # we could also scan files but this ensures __all__ is used correctly
822     if module_all is not None:
823         submod_name = None
824         submod = None
825         submod_ls = []
826         for submod_name in module_all:
827             ns = {}
828             exec_str = "from %s import %s as submod" % (module.__name__, submod_name)
829             exec(exec_str, ns, ns)
830             submod = ns["submod"]
831             if type(submod) == types.ModuleType:
832                 submod_ls.append((submod_name, submod))
833
834         del submod_name
835         del submod
836
837         if submod_ls:
838             fw(".. toctree::\n")
839             fw("   :maxdepth: 1\n\n")
840
841             for submod_name, submod in submod_ls:
842                 submod_name_full = "%s.%s" % (module_name, submod_name)
843                 fw("   %s.rst\n\n" % submod_name_full)
844
845                 pymodule2sphinx(basepath, submod_name_full, submod, "%s submodule" % module_name)
846         del submod_ls
847     # done writing submodules!
848
849     # write members of the module
850     # only tested with PyStructs which are not exactly modules
851     for key, descr in sorted(type(module).__dict__.items()):
852         if key.startswith("__"):
853             continue
854         # naughty, we also add getset's into PyStructs, this is not typical py but also not incorrect.
855
856         # type_name is only used for examples and messages
857         # "<class 'bpy.app.handlers'>" --> bpy.app.handlers
858         type_name = str(type(module)).strip("<>").split(" ", 1)[-1][1:-1]
859         if type(descr) == types.GetSetDescriptorType:
860             py_descr2sphinx("", fw, descr, module_name, type_name, key)
861             attribute_set.add(key)
862     descr_sorted = []
863     for key, descr in sorted(type(module).__dict__.items()):
864         if key.startswith("__"):
865             continue
866
867         if type(descr) == MemberDescriptorType:
868             if descr.__doc__:
869                 value = getattr(module, key, None)
870
871                 value_type = type(value)
872                 descr_sorted.append((key, descr, value, type(value)))
873     # sort by the valye type
874     descr_sorted.sort(key=lambda descr_data: str(descr_data[3]))
875     for key, descr, value, value_type in descr_sorted:
876
877         # must be documented as a submodule
878         if is_struct_seq(value):
879             continue
880
881         type_name = value_type.__name__
882         py_descr2sphinx("", fw, descr, module_name, type_name, key)
883
884         attribute_set.add(key)
885
886     del key, descr, descr_sorted
887
888     classes = []
889     submodules = []
890
891     # use this list so we can sort by type
892     module_dir_value_type = []
893
894     for attribute in module_dir:
895         if attribute.startswith("_"):
896             continue
897
898         if attribute in attribute_set:
899             continue
900
901         if attribute.startswith("n_"):  # annoying exception, needed for bpy.app
902             continue
903
904         # workaround for bpy.app documenting .index() and .count()
905         if isinstance(module, tuple) and hasattr(tuple, attribute):
906             continue
907
908         value = getattr(module, attribute)
909
910         module_dir_value_type.append((attribute, value, type(value)))
911
912     # sort by str of each type
913     # this way lists, functions etc are grouped.
914     module_dir_value_type.sort(key=lambda triple: str(triple[2]))
915
916     for attribute, value, value_type in module_dir_value_type:
917         if value_type == FunctionType:
918             pyfunc2sphinx("", fw, module_name, None, attribute, value, is_class=False)
919         # both the same at the moment but to be future proof
920         elif value_type in {types.BuiltinMethodType, types.BuiltinFunctionType}:
921             # note: can't get args from these, so dump the string as is
922             # this means any module used like this must have fully formatted docstrings.
923             py_c_func2sphinx("", fw, module_name, None, attribute, value, is_class=False)
924         elif value_type == type:
925             classes.append((attribute, value))
926         elif issubclass(value_type, types.ModuleType):
927             submodules.append((attribute, value))
928         elif issubclass(value_type, (bool, int, float, str, tuple)):
929             # constant, not much fun we can do here except to list it.
930             # TODO, figure out some way to document these!
931             fw(".. data:: %s\n\n" % attribute)
932             write_indented_lines("   ", fw, "constant value %s" % repr(value), False)
933             fw("\n")
934         else:
935             BPY_LOGGER.debug("\tnot documenting %s.%s of %r type" % (module_name, attribute, value_type.__name__))
936             continue
937
938         attribute_set.add(attribute)
939         # TODO, more types...
940     del module_dir_value_type
941
942     # TODO, bpy_extras does this already, mathutils not.
943     '''
944     if submodules:
945         fw("\n"
946            "**********\n"
947            "Submodules\n"
948            "**********\n"
949            "\n"
950            )
951         for attribute, submod in submodules:
952             fw("* :mod:`%s.%s`\n" % (module_name, attribute))
953         fw("\n")
954     '''
955
956     if module_grouping is not None:
957         classes.sort(key=lambda pair: module_grouping_sort_key(pair[0]))
958
959     # write collected classes now
960     for (type_name, value) in classes:
961
962         if module_grouping is not None:
963             heading, heading_char = module_grouping_heading(type_name)
964             if heading:
965                 fw(title_string(heading, heading_char))
966
967         # May need to be its own function
968         if value.__doc__:
969             if value.__doc__.startswith(".. class::"):
970                 fw(value.__doc__)
971             else:
972                 fw(".. class:: %s\n\n" % type_name)
973                 write_indented_lines("   ", fw, value.__doc__, True)
974         else:
975             fw(".. class:: %s\n\n" % type_name)
976         fw("\n")
977
978         write_example_ref("   ", fw, module_name + "." + type_name)
979
980         descr_items = [(key, descr) for key, descr in sorted(value.__dict__.items()) if not key.startswith("_")]
981
982         for key, descr in descr_items:
983             if type(descr) == ClassMethodDescriptorType:
984                 py_descr2sphinx("   ", fw, descr, module_name, type_name, key)
985
986         # needed for pure Python classes
987         for key, descr in descr_items:
988             if type(descr) == FunctionType:
989                 pyfunc2sphinx("   ", fw, module_name, type_name, key, descr, is_class=True)
990
991         for key, descr in descr_items:
992             if type(descr) == MethodDescriptorType:
993                 py_descr2sphinx("   ", fw, descr, module_name, type_name, key)
994
995         for key, descr in descr_items:
996             if type(descr) == GetSetDescriptorType:
997                 py_descr2sphinx("   ", fw, descr, module_name, type_name, key)
998
999         for key, descr in descr_items:
1000             if type(descr) == StaticMethodType:
1001                 descr = getattr(value, key)
1002                 write_indented_lines("   ", fw, descr.__doc__ or "Undocumented", False)
1003                 fw("\n")
1004
1005         fw("\n\n")
1006
1007     file.close()
1008
1009 # Changes in Blender will force errors here
1010 context_type_map = {
1011     "active_base": ("ObjectBase", False),
1012     "active_bone": ("EditBone", False),
1013     "active_gpencil_frame": ("GreasePencilLayer", True),
1014     "active_gpencil_layer": ("GPencilLayer", True),
1015     "active_gpencil_brush": ("GPencilSculptBrush", False),
1016     "active_gpencil_palette": ("GPencilPalette", True),
1017     "active_gpencil_palettecolor": ("GPencilPaletteColor", True),
1018     "active_node": ("Node", False),
1019     "active_object": ("Object", False),
1020     "active_operator": ("Operator", False),
1021     "active_pose_bone": ("PoseBone", False),
1022     "armature": ("Armature", False),
1023     "bone": ("Bone", False),
1024     "brush": ("Brush", False),
1025     "camera": ("Camera", False),
1026     "cloth": ("ClothModifier", False),
1027     "collision": ("CollisionModifier", False),
1028     "curve": ("Curve", False),
1029     "dynamic_paint": ("DynamicPaintModifier", False),
1030     "edit_bone": ("EditBone", False),
1031     "edit_image": ("Image", False),
1032     "edit_mask": ("Mask", False),
1033     "edit_movieclip": ("MovieClip", False),
1034     "edit_object": ("Object", False),
1035     "edit_text": ("Text", False),
1036     "editable_bases": ("ObjectBase", True),
1037     "editable_bones": ("EditBone", True),
1038     "editable_gpencil_layers": ("GPencilLayer", True),
1039     "editable_gpencil_strokes": ("GPencilStroke", True),
1040     "editable_objects": ("Object", True),
1041     "fluid": ("FluidSimulationModifier", False),
1042     "gpencil_data": ("GreasePencel", False),
1043     "gpencil_data_owner": ("ID", False),
1044     "image_paint_object": ("Object", False),
1045     "lamp": ("Lamp", False),
1046     "lattice": ("Lattice", False),
1047     "line_style": ("FreestyleLineStyle", False),
1048     "material": ("Material", False),
1049     "material_slot": ("MaterialSlot", False),
1050     "mesh": ("Mesh", False),
1051     "meta_ball": ("MetaBall", False),
1052     "object": ("Object", False),
1053     "particle_edit_object": ("Object", False),
1054     "particle_settings": ("ParticleSettings", False),
1055     "particle_system": ("ParticleSystem", False),
1056     "particle_system_editable": ("ParticleSystem", False),
1057     "pose_bone": ("PoseBone", False),
1058     "scene": ("Scene", False),
1059     "sculpt_object": ("Object", False),
1060     "selectable_bases": ("ObjectBase", True),
1061     "selectable_objects": ("Object", True),
1062     "selected_bases": ("ObjectBase", True),
1063     "selected_bones": ("EditBone", True),
1064     "selected_editable_bases": ("ObjectBase", True),
1065     "selected_editable_bones": ("EditBone", True),
1066     "selected_editable_objects": ("Object", True),
1067     "selected_editable_sequences": ("Sequence", True),
1068     "selected_nodes": ("Node", True),
1069     "selected_objects": ("Object", True),
1070     "selected_pose_bones": ("PoseBone", True),
1071     "selected_sequences": ("Sequence", True),
1072     "sequences": ("Sequence", True),
1073     "smoke": ("SmokeModifier", False),
1074     "soft_body": ("SoftBodyModifier", False),
1075     "speaker": ("Speaker", False),
1076     "texture": ("Texture", False),
1077     "texture_slot": ("MaterialTextureSlot", False),
1078     "texture_user": ("ID", False),
1079     "texture_user_property": ("Property", False),
1080     "vertex_paint_object": ("Object", False),
1081     "visible_bases": ("ObjectBase", True),
1082     "visible_bones": ("EditBone", True),
1083     "visible_gpencil_layers": ("GPencilLayer", True),
1084     "visible_objects": ("Object", True),
1085     "visible_pose_bones": ("PoseBone", True),
1086     "weight_paint_object": ("Object", False),
1087     "world": ("World", False),
1088 }
1089
1090
1091 def pycontext2sphinx(basepath):
1092     # Only use once. very irregular
1093
1094     filepath = os.path.join(basepath, "bpy.context.rst")
1095     file = open(filepath, "w", encoding="utf-8")
1096     fw = file.write
1097     fw(title_string("Context Access (bpy.context)", "="))
1098     fw(".. module:: bpy.context\n")
1099     fw("\n")
1100     fw("The context members available depend on the area of Blender which is currently being accessed.\n")
1101     fw("\n")
1102     fw("Note that all context values are readonly,\n")
1103     fw("but may be modified through the data api or by running operators\n\n")
1104
1105     def write_contex_cls():
1106
1107         fw(title_string("Global Context", "-"))
1108         fw("These properties are avilable in any contexts.\n\n")
1109
1110         # very silly. could make these global and only access once.
1111         # structs, funcs, ops, props = rna_info.BuildRNAInfo()
1112         structs, funcs, ops, props = rna_info_BuildRNAInfo_cache()
1113         struct = structs[("", "Context")]
1114         struct_blacklist = RNA_BLACKLIST.get(struct.identifier, ())
1115         del structs, funcs, ops, props
1116
1117         sorted_struct_properties = struct.properties[:]
1118         sorted_struct_properties.sort(key=lambda prop: prop.identifier)
1119
1120         # First write RNA
1121         for prop in sorted_struct_properties:
1122             # support blacklisting props
1123             if prop.identifier in struct_blacklist:
1124                 continue
1125
1126             type_descr = prop.get_type_description(
1127                 class_fmt=":class:`bpy.types.%s`", collection_id=_BPY_PROP_COLLECTION_ID)
1128             fw(".. data:: %s\n\n" % prop.identifier)
1129             if prop.description:
1130                 fw("   %s\n\n" % prop.description)
1131
1132             # special exception, cant use genric code here for enums
1133             if prop.type == "enum":
1134                 enum_text = pyrna_enum2sphinx(prop)
1135                 if enum_text:
1136                     write_indented_lines("   ", fw, enum_text)
1137                     fw("\n")
1138                 del enum_text
1139             # end enum exception
1140
1141             fw("   :type: %s\n\n" % type_descr)
1142
1143     write_contex_cls()
1144     del write_contex_cls
1145     # end
1146
1147     # nasty, get strings directly from Blender because there is no other way to get it
1148     import ctypes
1149
1150     context_strings = (
1151         "screen_context_dir",
1152         "view3d_context_dir",
1153         "buttons_context_dir",
1154         "image_context_dir",
1155         "node_context_dir",
1156         "text_context_dir",
1157         "clip_context_dir",
1158         "sequencer_context_dir",
1159     )
1160
1161     unique = set()
1162     blend_cdll = ctypes.CDLL("")
1163     for ctx_str in context_strings:
1164         subsection = "%s Context" % ctx_str.split("_")[0].title()
1165         fw("\n%s\n%s\n\n" % (subsection, (len(subsection) * '-')))
1166
1167         attr = ctypes.addressof(getattr(blend_cdll, ctx_str))
1168         c_char_p_p = ctypes.POINTER(ctypes.c_char_p)
1169         char_array = c_char_p_p.from_address(attr)
1170         i = 0
1171         while char_array[i] is not None:
1172             member = ctypes.string_at(char_array[i]).decode(encoding="ascii")
1173             fw(".. data:: %s\n\n" % member)
1174             member_type, is_seq = context_type_map[member]
1175             fw("   :type: %s :class:`bpy.types.%s`\n\n" % ("sequence of " if is_seq else "", member_type))
1176             unique.add(member)
1177             i += 1
1178
1179     # generate typemap...
1180     # for member in sorted(unique):
1181     #     print('        "%s": ("", False),' % member)
1182     if len(context_type_map) > len(unique):
1183         raise Exception(
1184             "Some types are not used: %s" %
1185             str([member for member in context_type_map if member not in unique]))
1186     else:
1187         pass  # will have raised an error above
1188
1189     file.close()
1190
1191
1192 def pyrna_enum2sphinx(prop, use_empty_descriptions=False):
1193     """ write a bullet point list of enum + descriptions
1194     """
1195
1196     if use_empty_descriptions:
1197         ok = True
1198     else:
1199         ok = False
1200         for identifier, name, description in prop.enum_items:
1201             if description:
1202                 ok = True
1203                 break
1204
1205     if ok:
1206         return "".join(["* ``%s`` %s.\n" %
1207                         (identifier,
1208                          ", ".join(escape_rst(val) for val in (name, description) if val),
1209                          )
1210                         for identifier, name, description in prop.enum_items
1211                         ])
1212     else:
1213         return ""
1214
1215
1216 def pyrna2sphinx(basepath):
1217     """ bpy.types and bpy.ops
1218     """
1219     # structs, funcs, ops, props = rna_info.BuildRNAInfo()
1220     structs, funcs, ops, props = rna_info_BuildRNAInfo_cache()
1221
1222     if FILTER_BPY_TYPES is not None:
1223         structs = {k: v for k, v in structs.items() if k[1] in FILTER_BPY_TYPES}
1224
1225     if FILTER_BPY_OPS is not None:
1226         ops = {k: v for k, v in ops.items() if v.module_name in FILTER_BPY_OPS}
1227
1228     def write_param(ident, fw, prop, is_return=False):
1229         if is_return:
1230             id_name = "return"
1231             id_type = "rtype"
1232             kwargs = {"as_ret": True}
1233             identifier = ""
1234         else:
1235             id_name = "arg"
1236             id_type = "type"
1237             kwargs = {"as_arg": True}
1238             identifier = " %s" % prop.identifier
1239
1240         kwargs["class_fmt"] = ":class:`%s`"
1241
1242         kwargs["collection_id"] = _BPY_PROP_COLLECTION_ID
1243
1244         type_descr = prop.get_type_description(**kwargs)
1245
1246         enum_text = pyrna_enum2sphinx(prop)
1247
1248         if prop.name or prop.description or enum_text:
1249             fw(ident + ":%s%s:\n\n" % (id_name, identifier))
1250
1251             if prop.name or prop.description:
1252                 fw(ident + "   " + ", ".join(val for val in (prop.name, prop.description) if val) + "\n\n")
1253
1254             # special exception, cant use genric code here for enums
1255             if enum_text:
1256                 write_indented_lines(ident + "   ", fw, enum_text)
1257                 fw("\n")
1258             del enum_text
1259             # end enum exception
1260
1261         fw(ident + ":%s%s: %s\n" % (id_type, identifier, type_descr))
1262
1263     def write_struct(struct):
1264         # if not struct.identifier.startswith("Sc") and not struct.identifier.startswith("I"):
1265         #     return
1266
1267         # if not struct.identifier == "Object":
1268         #     return
1269
1270         filepath = os.path.join(basepath, "bpy.types.%s.rst" % struct.identifier)
1271         file = open(filepath, "w", encoding="utf-8")
1272         fw = file.write
1273
1274         base_id = getattr(struct.base, "identifier", "")
1275         struct_id = struct.identifier
1276
1277         if _BPY_STRUCT_FAKE:
1278             if not base_id:
1279                 base_id = _BPY_STRUCT_FAKE
1280
1281         if base_id:
1282             title = "%s(%s)" % (struct_id, base_id)
1283         else:
1284             title = struct_id
1285
1286         fw(title_string(title, "="))
1287
1288         fw(".. module:: bpy.types\n\n")
1289
1290         # docs first?, ok
1291         write_example_ref("", fw, "bpy.types.%s" % struct_id)
1292
1293         base_ids = [base.identifier for base in struct.get_bases()]
1294
1295         if _BPY_STRUCT_FAKE:
1296             base_ids.append(_BPY_STRUCT_FAKE)
1297
1298         base_ids.reverse()
1299
1300         if base_ids:
1301             if len(base_ids) > 1:
1302                 fw("base classes --- ")
1303             else:
1304                 fw("base class --- ")
1305
1306             fw(", ".join((":class:`%s`" % base_id) for base_id in base_ids))
1307             fw("\n\n")
1308
1309         subclass_ids = [
1310             s.identifier for s in structs.values()
1311             if s.base is struct
1312             if not rna_info.rna_id_ignore(s.identifier)
1313         ]
1314         subclass_ids.sort()
1315         if subclass_ids:
1316             fw("subclasses --- \n" + ", ".join((":class:`%s`" % s) for s in subclass_ids) + "\n\n")
1317
1318         base_id = getattr(struct.base, "identifier", "")
1319
1320         if _BPY_STRUCT_FAKE:
1321             if not base_id:
1322                 base_id = _BPY_STRUCT_FAKE
1323
1324         if base_id:
1325             fw(".. class:: %s(%s)\n\n" % (struct_id, base_id))
1326         else:
1327             fw(".. class:: %s\n\n" % struct_id)
1328
1329         fw("   %s\n\n" % struct.description)
1330
1331         # properties sorted in alphabetical order
1332         sorted_struct_properties = struct.properties[:]
1333         sorted_struct_properties.sort(key=lambda prop: prop.identifier)
1334
1335         # support blacklisting props
1336         struct_blacklist = RNA_BLACKLIST.get(struct_id, ())
1337
1338         for prop in sorted_struct_properties:
1339
1340             # support blacklisting props
1341             if prop.identifier in struct_blacklist:
1342                 continue
1343
1344             type_descr = prop.get_type_description(class_fmt=":class:`%s`", collection_id=_BPY_PROP_COLLECTION_ID)
1345             # readonly properties use "data" directive, variables properties use "attribute" directive
1346             if 'readonly' in type_descr:
1347                 fw("   .. data:: %s\n\n" % prop.identifier)
1348             else:
1349                 fw("   .. attribute:: %s\n\n" % prop.identifier)
1350             if prop.description:
1351                 fw("      %s\n\n" % prop.description)
1352
1353             # special exception, cant use genric code here for enums
1354             if prop.type == "enum":
1355                 enum_text = pyrna_enum2sphinx(prop)
1356                 if enum_text:
1357                     write_indented_lines("      ", fw, enum_text)
1358                     fw("\n")
1359                 del enum_text
1360             # end enum exception
1361
1362             fw("      :type: %s\n\n" % type_descr)
1363
1364         # Python attributes
1365         py_properties = struct.get_py_properties()
1366         py_prop = None
1367         for identifier, py_prop in py_properties:
1368             pyprop2sphinx("   ", fw, identifier, py_prop)
1369         del py_properties, py_prop
1370
1371         for func in struct.functions:
1372             args_str = ", ".join(prop.get_arg_default(force=False) for prop in func.args)
1373
1374             fw("   .. %s:: %s(%s)\n\n" %
1375                ("classmethod" if func.is_classmethod else "method", func.identifier, args_str))
1376             fw("      %s\n\n" % func.description)
1377
1378             for prop in func.args:
1379                 write_param("      ", fw, prop)
1380
1381             if len(func.return_values) == 1:
1382                 write_param("      ", fw, func.return_values[0], is_return=True)
1383             elif func.return_values:  # multiple return values
1384                 fw("      :return (%s):\n" % ", ".join(prop.identifier for prop in func.return_values))
1385                 for prop in func.return_values:
1386                     # TODO, pyrna_enum2sphinx for multiple return values... actually dont
1387                     # think we even use this but still!!!
1388                     type_descr = prop.get_type_description(
1389                         as_ret=True, class_fmt=":class:`%s`", collection_id=_BPY_PROP_COLLECTION_ID)
1390                     descr = prop.description
1391                     if not descr:
1392                         descr = prop.name
1393                     # In rare cases descr may be empty
1394                     fw("         `%s`, %s\n\n" %
1395                        (prop.identifier,
1396                         ", ".join((val for val in (descr, type_descr) if val))))
1397
1398             write_example_ref("      ", fw, "bpy.types." + struct_id + "." + func.identifier)
1399
1400             fw("\n")
1401
1402         # Python methods
1403         py_funcs = struct.get_py_functions()
1404         py_func = None
1405
1406         for identifier, py_func in py_funcs:
1407             pyfunc2sphinx("   ", fw, "bpy.types", struct_id, identifier, py_func, is_class=True)
1408         del py_funcs, py_func
1409
1410         py_funcs = struct.get_py_c_functions()
1411         py_func = None
1412
1413         for identifier, py_func in py_funcs:
1414             py_c_func2sphinx("   ", fw, "bpy.types", struct_id, identifier, py_func, is_class=True)
1415
1416         lines = []
1417
1418         if struct.base or _BPY_STRUCT_FAKE:
1419             bases = list(reversed(struct.get_bases()))
1420
1421             # props
1422             del lines[:]
1423
1424             if _BPY_STRUCT_FAKE:
1425                 descr_items = [
1426                     (key, descr) for key, descr in sorted(bpy.types.Struct.__bases__[0].__dict__.items())
1427                     if not key.startswith("__")
1428                 ]
1429
1430             if _BPY_STRUCT_FAKE:
1431                 for key, descr in descr_items:
1432                     if type(descr) == GetSetDescriptorType:
1433                         lines.append("   * :class:`%s.%s`\n" % (_BPY_STRUCT_FAKE, key))
1434
1435             for base in bases:
1436                 for prop in base.properties:
1437                     lines.append("   * :class:`%s.%s`\n" % (base.identifier, prop.identifier))
1438
1439                 for identifier, py_prop in base.get_py_properties():
1440                     lines.append("   * :class:`%s.%s`\n" % (base.identifier, identifier))
1441
1442                 for identifier, py_prop in base.get_py_properties():
1443                     lines.append("   * :class:`%s.%s`\n" % (base.identifier, identifier))
1444
1445             if lines:
1446                 fw(".. rubric:: Inherited Properties\n\n")
1447
1448                 fw(".. hlist::\n")
1449                 fw("   :columns: 2\n\n")
1450
1451                 for line in lines:
1452                     fw(line)
1453                 fw("\n")
1454
1455             # funcs
1456             del lines[:]
1457
1458             if _BPY_STRUCT_FAKE:
1459                 for key, descr in descr_items:
1460                     if type(descr) == MethodDescriptorType:
1461                         lines.append("   * :class:`%s.%s`\n" % (_BPY_STRUCT_FAKE, key))
1462
1463             for base in bases:
1464                 for func in base.functions:
1465                     lines.append("   * :class:`%s.%s`\n" % (base.identifier, func.identifier))
1466                 for identifier, py_func in base.get_py_functions():
1467                     lines.append("   * :class:`%s.%s`\n" % (base.identifier, identifier))
1468
1469             if lines:
1470                 fw(".. rubric:: Inherited Functions\n\n")
1471
1472                 fw(".. hlist::\n")
1473                 fw("   :columns: 2\n\n")
1474
1475                 for line in lines:
1476                     fw(line)
1477                 fw("\n")
1478
1479             del lines[:]
1480
1481         if struct.references:
1482             # use this otherwise it gets in the index for a normal heading.
1483             fw(".. rubric:: References\n\n")
1484
1485             fw(".. hlist::\n")
1486             fw("   :columns: 2\n\n")
1487
1488             # context does its own thing
1489             # "active_base": ("ObjectBase", False),
1490             for ref_attr, (ref_type, ref_is_seq) in sorted(context_type_map.items()):
1491                 if ref_type == struct_id:
1492                     fw("   * :mod:`bpy.context.%s`\n" % ref_attr)
1493             del ref_attr, ref_type, ref_is_seq
1494
1495             for ref in struct.references:
1496                 ref_split = ref.split(".")
1497                 if len(ref_split) > 2:
1498                     ref = ref_split[-2] + "." + ref_split[-1]
1499                 fw("   * :class:`%s`\n" % ref)
1500             fw("\n")
1501
1502         # docs last?, disable for now
1503         # write_example_ref("", fw, "bpy.types.%s" % struct_id)
1504         file.close()
1505
1506     if "bpy.types" not in EXCLUDE_MODULES:
1507         for struct in structs.values():
1508             # TODO, rna_info should filter these out!
1509             if "_OT_" in struct.identifier:
1510                 continue
1511             write_struct(struct)
1512
1513         def fake_bpy_type(class_value, class_name, descr_str, use_subclasses=True):
1514             filepath = os.path.join(basepath, "bpy.types.%s.rst" % class_name)
1515             file = open(filepath, "w", encoding="utf-8")
1516             fw = file.write
1517
1518             fw(title_string(class_name, "="))
1519
1520             fw(".. module:: bpy.types\n")
1521             fw("\n")
1522
1523             if use_subclasses:
1524                 subclass_ids = [
1525                     s.identifier for s in structs.values()
1526                     if s.base is None
1527                     if not rna_info.rna_id_ignore(s.identifier)
1528                 ]
1529                 if subclass_ids:
1530                     fw("subclasses --- \n" + ", ".join((":class:`%s`" % s) for s in sorted(subclass_ids)) + "\n\n")
1531
1532             fw(".. class:: %s\n\n" % class_name)
1533             fw("   %s\n\n" % descr_str)
1534             fw("   .. note::\n\n")
1535             fw("      Note that bpy.types.%s is not actually available from within Blender,\n"
1536                "      it only exists for the purpose of documentation.\n\n" % class_name)
1537
1538             descr_items = [
1539                 (key, descr) for key, descr in sorted(class_value.__dict__.items())
1540                 if not key.startswith("__")
1541             ]
1542
1543             for key, descr in descr_items:
1544                 # GetSetDescriptorType, GetSetDescriptorType's are not documented yet
1545                 if type(descr) == MethodDescriptorType:
1546                     py_descr2sphinx("   ", fw, descr, "bpy.types", class_name, key)
1547
1548             for key, descr in descr_items:
1549                 if type(descr) == GetSetDescriptorType:
1550                     py_descr2sphinx("   ", fw, descr, "bpy.types", class_name, key)
1551             file.close()
1552
1553         # write fake classes
1554         if _BPY_STRUCT_FAKE:
1555             class_value = bpy.types.Struct.__bases__[0]
1556             fake_bpy_type(
1557                 class_value, _BPY_STRUCT_FAKE,
1558                 "built-in base class for all classes in bpy.types.", use_subclasses=True)
1559
1560         if _BPY_PROP_COLLECTION_FAKE:
1561             class_value = bpy.data.objects.__class__
1562             fake_bpy_type(
1563                 class_value, _BPY_PROP_COLLECTION_FAKE,
1564                 "built-in class used for all collections.", use_subclasses=False)
1565
1566     # operators
1567     def write_ops():
1568         API_BASEURL = "https://developer.blender.org/diffusion/B/browse/master/release/scripts "
1569         API_BASEURL_ADDON = "https://developer.blender.org/diffusion/BA"
1570         API_BASEURL_ADDON_CONTRIB = "https://developer.blender.org/diffusion/BAC"
1571
1572         op_modules = {}
1573         for op in ops.values():
1574             op_modules.setdefault(op.module_name, []).append(op)
1575         del op
1576
1577         for op_module_name, ops_mod in op_modules.items():
1578             filepath = os.path.join(basepath, "bpy.ops.%s.rst" % op_module_name)
1579             file = open(filepath, "w", encoding="utf-8")
1580             fw = file.write
1581
1582             title = "%s Operators" % op_module_name.replace("_", " ").title()
1583
1584             fw(title_string(title, "="))
1585
1586             fw(".. module:: bpy.ops.%s\n\n" % op_module_name)
1587
1588             ops_mod.sort(key=lambda op: op.func_name)
1589
1590             for op in ops_mod:
1591                 args_str = ", ".join(prop.get_arg_default(force=True) for prop in op.args)
1592                 fw(".. function:: %s(%s)\n\n" % (op.func_name, args_str))
1593
1594                 # if the description isn't valid, we output the standard warning
1595                 # with a link to the wiki so that people can help
1596                 if not op.description or op.description == "(undocumented operator)":
1597                     operator_description = undocumented_message('bpy.ops', op.module_name, op.func_name)
1598                 else:
1599                     operator_description = op.description
1600
1601                 fw("   %s\n\n" % operator_description)
1602                 for prop in op.args:
1603                     write_param("   ", fw, prop)
1604                 if op.args:
1605                     fw("\n")
1606
1607                 location = op.get_location()
1608                 if location != (None, None):
1609                     if location[0].startswith("addons_contrib" + os.sep):
1610                         url_base = API_BASEURL_ADDON_CONTRIB
1611                     elif location[0].startswith("addons" + os.sep):
1612                         url_base = API_BASEURL_ADDON
1613                     else:
1614                         url_base = API_BASEURL
1615
1616                     fw("   :file: `%s <%s/%s>`_:%d\n\n" % (location[0],
1617                                                            url_base,
1618                                                            location[0],
1619                                                            location[1]))
1620
1621             file.close()
1622
1623     if "bpy.ops" not in EXCLUDE_MODULES:
1624         write_ops()
1625
1626
1627 def write_sphinx_conf_py(basepath):
1628     '''
1629     Write sphinx's conf.py
1630     '''
1631     filepath = os.path.join(basepath, "conf.py")
1632     file = open(filepath, "w", encoding="utf-8")
1633     fw = file.write
1634
1635     fw("import sys, os\n\n")
1636     fw("extensions = ['sphinx.ext.intersphinx']\n\n")
1637     fw("intersphinx_mapping = {'blender_manual': ('https://docs.blender.org/manual/en/dev/', None)}\n\n")
1638     fw("project = 'Blender'\n")
1639     # fw("master_doc = 'index'\n")
1640     fw("copyright = u'Blender Foundation'\n")
1641     fw("version = '%s - API'\n" % BLENDER_VERSION_DOTS)
1642     fw("release = '%s - API'\n" % BLENDER_VERSION_DOTS)
1643
1644     if ARGS.sphinx_theme != 'default':
1645         fw("html_theme = '%s'\n" % ARGS.sphinx_theme)
1646
1647     if ARGS.sphinx_theme == "blender-org":
1648         fw("html_theme_path = ['../']\n")
1649         # copied with the theme, exclude else we get an error [T28873]
1650         fw("html_favicon = 'favicon.ico'\n")    # in <theme>/static/
1651
1652     # not helpful since the source is generated, adds to upload size.
1653     fw("html_copy_source = False\n")
1654     fw("html_show_sphinx = False\n")
1655     fw("html_split_index = True\n")
1656     fw("\n")
1657
1658     # needed for latex, pdf gen
1659     fw("latex_elements = {\n")
1660     fw("  'papersize': 'a4paper',\n")
1661     fw("}\n\n")
1662
1663     fw("latex_documents = [ ('contents', 'contents.tex', 'Blender Index', 'Blender Foundation', 'manual'), ]\n")
1664     file.close()
1665
1666
1667 def execfile(filepath):
1668     global_namespace = {"__file__": filepath, "__name__": "__main__"}
1669     file_handle = open(filepath)
1670     exec(compile(file_handle.read(), filepath, 'exec'), global_namespace)
1671     file_handle.close()
1672
1673
1674 def write_rst_contents(basepath):
1675     '''
1676     Write the rst file of the main page, needed for sphinx (index.html)
1677     '''
1678     filepath = os.path.join(basepath, "contents.rst")
1679     file = open(filepath, "w", encoding="utf-8")
1680     fw = file.write
1681
1682     fw(title_string("Blender Documentation Contents", "%", double=True))
1683     fw("\n")
1684     fw("Welcome, this document is an API reference for Blender %s, built %s.\n" %
1685        (BLENDER_VERSION_DOTS, BLENDER_DATE))
1686     fw("\n")
1687
1688     # fw("`A PDF version of this document is also available <%s>`_\n" % BLENDER_PDF_FILENAME)
1689     fw("This site can be downloaded for offline use `Download the full Documentation (zipped HTML files) <%s>`_\n" %
1690        BLENDER_ZIP_FILENAME)
1691
1692     fw("\n")
1693
1694     if not EXCLUDE_INFO_DOCS:
1695         fw(title_string("Blender/Python Documentation", "=", double=True))
1696
1697         fw(".. toctree::\n")
1698         fw("   :maxdepth: 1\n\n")
1699         for info, info_desc in INFO_DOCS:
1700             fw("   %s <%s>\n\n" % (info_desc, info))
1701         fw("\n")
1702
1703     fw(title_string("Application Modules", "=", double=True))
1704     fw(".. toctree::\n")
1705     fw("   :maxdepth: 1\n\n")
1706
1707     app_modules = (
1708         "bpy.context",  # note: not actually a module
1709         "bpy.data",     # note: not actually a module
1710         "bpy.ops",
1711         "bpy.types",
1712
1713         # py modules
1714         "bpy.utils",
1715         "bpy.utils.previews",
1716         "bpy.path",
1717         "bpy.app",
1718         "bpy.app.handlers",
1719         "bpy.app.translations",
1720
1721         # C modules
1722         "bpy.props",
1723     )
1724
1725     for mod in app_modules:
1726         if mod not in EXCLUDE_MODULES:
1727             fw("   %s\n\n" % mod)
1728
1729     fw(title_string("Standalone Modules", "=", double=True))
1730     fw(".. toctree::\n")
1731     fw("   :maxdepth: 1\n\n")
1732
1733     standalone_modules = (
1734         # mathutils
1735         "mathutils",
1736         "mathutils.geometry",
1737         "mathutils.bvhtree", "mathutils.kdtree",
1738         "mathutils.interpolate",
1739         "mathutils.noise",
1740         # misc
1741         "freestyle", "bgl", "blf",
1742         "gpu", "gpu.offscreen",
1743         "aud", "bpy_extras",
1744         "idprop.types",
1745         # bmesh, submodules are in own page
1746         "bmesh",
1747     )
1748
1749     for mod in standalone_modules:
1750         if mod not in EXCLUDE_MODULES:
1751             fw("   %s\n\n" % mod)
1752
1753     # special case, this 'bmesh.ops.rst' is extracted from C source
1754     if "bmesh.ops" not in EXCLUDE_MODULES:
1755         execfile(os.path.join(SCRIPT_DIR, "rst_from_bmesh_opdefines.py"))
1756
1757     # game engine
1758     if "bge" not in EXCLUDE_MODULES:
1759         fw(title_string("Game Engine Modules", "=", double=True))
1760         fw(".. toctree::\n")
1761         fw("   :maxdepth: 1\n\n")
1762         fw("   bge.types.rst\n\n")
1763         fw("   bge.logic.rst\n\n")
1764         fw("   bge.render.rst\n\n")
1765         fw("   bge.texture.rst\n\n")
1766         fw("   bge.events.rst\n\n")
1767         fw("   bge.constraints.rst\n\n")
1768         fw("   bge.app.rst\n\n")
1769
1770     # rna generated change log
1771     fw(title_string("API Info", "=", double=True))
1772     fw(".. toctree::\n")
1773     fw("   :maxdepth: 1\n\n")
1774     fw("   change_log.rst\n\n")
1775
1776     fw("\n")
1777     fw("\n")
1778     fw(".. note:: The Blender Python API has areas which are still in development.\n")
1779     fw("   \n")
1780     fw("   The following areas are subject to change.\n")
1781     fw("      * operator behavior, names and arguments\n")
1782     fw("      * mesh creation and editing functions\n")
1783     fw("   \n")
1784     fw("   These parts of the API are relatively stable and are unlikely to change significantly\n")
1785     fw("      * data API, access to attributes of Blender data such as mesh verts, material color,\n")
1786     fw("        timeline frames and scene objects\n")
1787     fw("      * user interface functions for defining buttons, creation of menus, headers, panels\n")
1788     fw("      * render engine integration\n")
1789     fw("      * modules: bgl, mathutils & game engine.\n")
1790     fw("\n")
1791
1792     file.close()
1793
1794
1795 def write_rst_bpy(basepath):
1796     '''
1797     Write rst file of bpy module (disabled by default)
1798     '''
1799     if ARGS.bpy:
1800         filepath = os.path.join(basepath, "bpy.rst")
1801         file = open(filepath, "w", encoding="utf-8")
1802         fw = file.write
1803
1804         fw("\n")
1805
1806         title = ":mod:`bpy` --- Blender Python Module"
1807
1808         fw(title_string(title, "="))
1809
1810         fw(".. module:: bpy.types\n\n")
1811         file.close()
1812
1813
1814 def write_rst_types_index(basepath):
1815     '''
1816     Write the rst file of bpy.types module (index)
1817     '''
1818     if "bpy.types" not in EXCLUDE_MODULES:
1819         filepath = os.path.join(basepath, "bpy.types.rst")
1820         file = open(filepath, "w", encoding="utf-8")
1821         fw = file.write
1822         fw(title_string("Types (bpy.types)", "="))
1823         fw(".. toctree::\n")
1824         fw("   :glob:\n\n")
1825         fw("   bpy.types.*\n\n")
1826         file.close()
1827
1828
1829 def write_rst_ops_index(basepath):
1830     '''
1831     Write the rst file of bpy.ops module (index)
1832     '''
1833     if "bpy.ops" not in EXCLUDE_MODULES:
1834         filepath = os.path.join(basepath, "bpy.ops.rst")
1835         file = open(filepath, "w", encoding="utf-8")
1836         fw = file.write
1837         fw(title_string("Operators (bpy.ops)", "="))
1838         write_example_ref("", fw, "bpy.ops")
1839         fw(".. toctree::\n")
1840         fw("   :glob:\n\n")
1841         fw("   bpy.ops.*\n\n")
1842         file.close()
1843
1844
1845 def write_rst_data(basepath):
1846     '''
1847     Write the rst file of bpy.data module
1848     '''
1849     if "bpy.data" not in EXCLUDE_MODULES:
1850         # not actually a module, only write this file so we
1851         # can reference in the TOC
1852         filepath = os.path.join(basepath, "bpy.data.rst")
1853         file = open(filepath, "w", encoding="utf-8")
1854         fw = file.write
1855         fw(title_string("Data Access (bpy.data)", "="))
1856         fw(".. module:: bpy\n")
1857         fw("\n")
1858         fw("This module is used for all Blender/Python access.\n")
1859         fw("\n")
1860         fw(".. data:: data\n")
1861         fw("\n")
1862         fw("   Access to Blender's internal data\n")
1863         fw("\n")
1864         fw("   :type: :class:`bpy.types.BlendData`\n")
1865         fw("\n")
1866         fw(".. literalinclude:: ../examples/bpy.data.py\n")
1867         file.close()
1868
1869         EXAMPLE_SET_USED.add("bpy.data")
1870
1871
1872 def write_rst_importable_modules(basepath):
1873     '''
1874     Write the rst files of importable modules
1875     '''
1876     importable_modules = {
1877         # Python_modules
1878         "bpy.path": "Path Utilities",
1879         "bpy.utils": "Utilities",
1880         "bpy_extras": "Extra Utilities",
1881
1882         # C_modules
1883         "aud": "Audio System",
1884         "blf": "Font Drawing",
1885         "gpu.offscreen": "GPU Off-Screen Buffer",
1886         "bmesh": "BMesh Module",
1887         "bmesh.types": "BMesh Types",
1888         "bmesh.utils": "BMesh Utilities",
1889         "bmesh.geometry": "BMesh Geometry Utilities",
1890         "bpy.app": "Application Data",
1891         "bpy.app.handlers": "Application Handlers",
1892         "bpy.app.translations": "Application Translations",
1893         "bpy.props": "Property Definitions",
1894         "idprop.types": "ID Property Access",
1895         "mathutils": "Math Types & Utilities",
1896         "mathutils.geometry": "Geometry Utilities",
1897         "mathutils.bvhtree": "BVHTree Utilities",
1898         "mathutils.kdtree": "KDTree Utilities",
1899         "mathutils.interpolate": "Interpolation Utilities",
1900         "mathutils.noise": "Noise Utilities",
1901         "freestyle": "Freestyle Module",
1902         "freestyle.types": "Freestyle Types",
1903         "freestyle.predicates": "Freestyle Predicates",
1904         "freestyle.functions": "Freestyle Functions",
1905         "freestyle.chainingiterators": "Freestyle Chaining Iterators",
1906         "freestyle.shaders": "Freestyle Shaders",
1907         "freestyle.utils": "Freestyle Utilities",
1908     }
1909     for mod_name, mod_descr in importable_modules.items():
1910         if mod_name not in EXCLUDE_MODULES:
1911             module = __import__(mod_name,
1912                                 fromlist=[mod_name.rsplit(".", 1)[-1]])
1913             pymodule2sphinx(basepath, mod_name, module, mod_descr)
1914
1915
1916 def copy_handwritten_rsts(basepath):
1917
1918     # info docs
1919     if not EXCLUDE_INFO_DOCS:
1920         for info, info_desc in INFO_DOCS:
1921             shutil.copy2(os.path.join(RST_DIR, info), basepath)
1922
1923     # TODO put this docs in Blender's code and use import as per modules above
1924     handwritten_modules = [
1925         "bge.logic",
1926         "bge.render",
1927         "bge.texture",
1928         "bge.events",
1929         "bge.constraints",
1930         "bge.app",
1931         "bgl",  # "Blender OpenGl wrapper"
1932         "gpu",  # "GPU Shader Module"
1933
1934         "bmesh.ops",  # generated by rst_from_bmesh_opdefines.py
1935
1936         # includes...
1937         "include__bmesh",
1938     ]
1939     for mod_name in handwritten_modules:
1940         if mod_name not in EXCLUDE_MODULES:
1941             # copy2 keeps time/date stamps
1942             shutil.copy2(os.path.join(RST_DIR, "%s.rst" % mod_name), basepath)
1943
1944     if "bge.types" not in EXCLUDE_MODULES:
1945         shutil.copy2(os.path.join(RST_DIR, "bge.types.rst"), basepath)
1946
1947         bge_types_dir = os.path.join(RST_DIR, "bge_types")
1948
1949         for i in os.listdir(bge_types_dir):
1950             if i.startswith("."):
1951                 # Avoid things like .svn dir...
1952                 continue
1953             shutil.copy2(os.path.join(bge_types_dir, i), basepath)
1954
1955     # changelog
1956     shutil.copy2(os.path.join(RST_DIR, "change_log.rst"), basepath)
1957
1958     # copy images, could be smarter but just glob for now.
1959     for f in os.listdir(RST_DIR):
1960         if f.endswith(".png"):
1961             shutil.copy2(os.path.join(RST_DIR, f), basepath)
1962
1963
1964 def copy_handwritten_extra(basepath):
1965     for f_src in EXTRA_SOURCE_FILES:
1966         if os.sep != "/":
1967             f_src = os.sep.join(f_src.split("/"))
1968
1969         f_dst = f_src.replace("..", "__")
1970
1971         f_src = os.path.join(RST_DIR, f_src)
1972         f_dst = os.path.join(basepath, f_dst)
1973
1974         os.makedirs(os.path.dirname(f_dst), exist_ok=True)
1975
1976         shutil.copy2(f_src, f_dst)
1977
1978
1979 def rna2sphinx(basepath):
1980
1981     try:
1982         os.mkdir(basepath)
1983     except:
1984         pass
1985
1986     # sphinx setup
1987     write_sphinx_conf_py(basepath)
1988
1989     # main page
1990     write_rst_contents(basepath)
1991
1992     # context
1993     if "bpy.context" not in EXCLUDE_MODULES:
1994         # one of a kind, context doc (uses ctypes to extract info!)
1995         # doesn't work on mac and windows
1996         if PLATFORM not in {"darwin", "windows"}:
1997             pycontext2sphinx(basepath)
1998
1999     # internal modules
2000     write_rst_bpy(basepath)                 # bpy, disabled by default
2001     write_rst_types_index(basepath)         # bpy.types
2002     write_rst_ops_index(basepath)           # bpy.ops
2003     pyrna2sphinx(basepath)                  # bpy.types.* and bpy.ops.*
2004     write_rst_data(basepath)                # bpy.data
2005     write_rst_importable_modules(basepath)
2006
2007     # copy the other rsts
2008     copy_handwritten_rsts(basepath)
2009
2010     # copy source files referenced
2011     copy_handwritten_extra(basepath)
2012
2013
2014 def align_sphinx_in_to_sphinx_in_tmp(dir_src, dir_dst):
2015     '''
2016     Move changed files from SPHINX_IN_TMP to SPHINX_IN
2017     '''
2018     import filecmp
2019
2020     # possible the dir doesn't exist when running recursively
2021     os.makedirs(dir_dst, exist_ok=True)
2022
2023     sphinx_dst_files = set(os.listdir(dir_dst))
2024     sphinx_src_files = set(os.listdir(dir_src))
2025
2026     # remove deprecated files that have been removed
2027     for f in sorted(sphinx_dst_files):
2028         if f not in sphinx_src_files:
2029             BPY_LOGGER.debug("\tdeprecated: %s" % f)
2030             f_dst = os.path.join(dir_dst, f)
2031             if os.path.isdir(f_dst):
2032                 shutil.rmtree(f_dst, True)
2033             else:
2034                 os.remove(f_dst)
2035
2036     # freshen with new files.
2037     for f in sorted(sphinx_src_files):
2038         f_src = os.path.join(dir_src, f)
2039         f_dst = os.path.join(dir_dst, f)
2040
2041         if os.path.isdir(f_src):
2042             align_sphinx_in_to_sphinx_in_tmp(f_src, f_dst)
2043         else:
2044             do_copy = True
2045             if f in sphinx_dst_files:
2046                 if filecmp.cmp(f_src, f_dst):
2047                     do_copy = False
2048
2049             if do_copy:
2050                 BPY_LOGGER.debug("\tupdating: %s" % f)
2051                 shutil.copy(f_src, f_dst)
2052
2053
2054 def refactor_sphinx_log(sphinx_logfile):
2055     refactored_log = []
2056     with open(sphinx_logfile, "r", encoding="utf-8") as original_logfile:
2057         lines = set(original_logfile.readlines())
2058         for line in lines:
2059             if 'warning' in line.lower() or 'error' in line.lower():
2060                 line = line.strip().split(None, 2)
2061                 if len(line) == 3:
2062                     location, kind, msg = line
2063                     location = os.path.relpath(location, start=SPHINX_IN)
2064                     refactored_log.append((kind, location, msg))
2065     with open(sphinx_logfile, "w", encoding="utf-8") as refactored_logfile:
2066         for log in sorted(refactored_log):
2067             refactored_logfile.write("%-12s %s\n             %s\n" % log)
2068
2069
2070 def monkey_patch():
2071     filepath = os.path.join(SCRIPT_DIR, "sphinx_doc_gen_monkeypatch.py")
2072     global_namespace = {"__file__": filepath, "__name__": "__main__"}
2073     file = open(filepath, 'rb')
2074     exec(compile(file.read(), filepath, 'exec'), global_namespace)
2075     file.close()
2076
2077
2078 def main():
2079
2080     # first monkey patch to load in fake members
2081     monkey_patch()
2082
2083     # eventually, create the dirs
2084     for dir_path in [ARGS.output_dir, SPHINX_IN]:
2085         if not os.path.exists(dir_path):
2086             os.mkdir(dir_path)
2087
2088     # eventually, log in files
2089     if ARGS.log:
2090         bpy_logfile = os.path.join(ARGS.output_dir, ".bpy.log")
2091         bpy_logfilehandler = logging.FileHandler(bpy_logfile, mode="w")
2092         bpy_logfilehandler.setLevel(logging.DEBUG)
2093         BPY_LOGGER.addHandler(bpy_logfilehandler)
2094
2095         # using a FileHandler seems to disable the stdout, so we add a StreamHandler
2096         bpy_log_stdout_handler = logging.StreamHandler(stream=sys.stdout)
2097         bpy_log_stdout_handler.setLevel(logging.DEBUG)
2098         BPY_LOGGER.addHandler(bpy_log_stdout_handler)
2099
2100     # in case of out-of-source build, copy the needed dirs
2101     if ARGS.output_dir != SCRIPT_DIR:
2102         # examples dir
2103         examples_dir_copy = os.path.join(ARGS.output_dir, "examples")
2104         if os.path.exists(examples_dir_copy):
2105             shutil.rmtree(examples_dir_copy, True)
2106         shutil.copytree(EXAMPLES_DIR,
2107                         examples_dir_copy,
2108                         ignore=shutil.ignore_patterns(*(".svn",)),
2109                         copy_function=shutil.copy)
2110
2111         # eventually, copy the theme dir
2112         if ARGS.sphinx_theme == "blender-org":
2113             if os.path.exists(SPHINX_THEME_DIR):
2114                 shutil.rmtree(SPHINX_THEME_DIR, True)
2115             shutil.copytree(SPHINX_THEME_SVN_DIR,
2116                             SPHINX_THEME_DIR,
2117                             ignore=shutil.ignore_patterns(*(".svn",)),
2118                             copy_function=shutil.copy)
2119
2120     # dump the api in rst files
2121     if os.path.exists(SPHINX_IN_TMP):
2122         shutil.rmtree(SPHINX_IN_TMP, True)
2123
2124     rna2sphinx(SPHINX_IN_TMP)
2125
2126     if ARGS.full_rebuild:
2127         # only for full updates
2128         shutil.rmtree(SPHINX_IN, True)
2129         shutil.copytree(SPHINX_IN_TMP,
2130                         SPHINX_IN,
2131                         copy_function=shutil.copy)
2132         if ARGS.sphinx_build and os.path.exists(SPHINX_OUT):
2133             shutil.rmtree(SPHINX_OUT, True)
2134         if ARGS.sphinx_build_pdf and os.path.exists(SPHINX_OUT_PDF):
2135             shutil.rmtree(SPHINX_OUT_PDF, True)
2136     else:
2137         # move changed files in SPHINX_IN
2138         align_sphinx_in_to_sphinx_in_tmp(SPHINX_IN_TMP, SPHINX_IN)
2139
2140     # report which example files weren't used
2141     EXAMPLE_SET_UNUSED = EXAMPLE_SET - EXAMPLE_SET_USED
2142     if EXAMPLE_SET_UNUSED:
2143         BPY_LOGGER.debug("\nUnused examples found in '%s'..." % EXAMPLES_DIR)
2144         for f in sorted(EXAMPLE_SET_UNUSED):
2145             BPY_LOGGER.debug("    %s.py" % f)
2146         BPY_LOGGER.debug("  %d total\n" % len(EXAMPLE_SET_UNUSED))
2147
2148     # eventually, build the html docs
2149     if ARGS.sphinx_build:
2150         import subprocess
2151         subprocess.call(SPHINX_BUILD)
2152
2153         # sphinx-build log cleanup+sort
2154         if ARGS.log:
2155             if os.stat(SPHINX_BUILD_LOG).st_size:
2156                 refactor_sphinx_log(SPHINX_BUILD_LOG)
2157
2158     # eventually, build the pdf docs
2159     if ARGS.sphinx_build_pdf:
2160         import subprocess
2161         subprocess.call(SPHINX_BUILD_PDF)
2162         subprocess.call(SPHINX_MAKE_PDF, stdout=SPHINX_MAKE_PDF_STDOUT)
2163
2164         # sphinx-build log cleanup+sort
2165         if ARGS.log:
2166             if os.stat(SPHINX_BUILD_PDF_LOG).st_size:
2167                 refactor_sphinx_log(SPHINX_BUILD_PDF_LOG)
2168
2169     # eventually, prepare the dir to be deployed online (REFERENCE_PATH)
2170     if ARGS.pack_reference:
2171
2172         if ARGS.sphinx_build:
2173             # delete REFERENCE_PATH
2174             if os.path.exists(REFERENCE_PATH):
2175                 shutil.rmtree(REFERENCE_PATH, True)
2176
2177             # copy SPHINX_OUT to the REFERENCE_PATH
2178             ignores = ('.doctrees', 'objects.inv', '.buildinfo')
2179             shutil.copytree(SPHINX_OUT,
2180                             REFERENCE_PATH,
2181                             ignore=shutil.ignore_patterns(*ignores))
2182             shutil.copy(os.path.join(REFERENCE_PATH, "contents.html"),
2183                         os.path.join(REFERENCE_PATH, "index.html"))
2184
2185             # zip REFERENCE_PATH
2186             basename = os.path.join(ARGS.output_dir, REFERENCE_NAME)
2187             tmp_path = shutil.make_archive(basename, 'zip',
2188                                            root_dir=ARGS.output_dir,
2189                                            base_dir=REFERENCE_NAME)
2190             final_path = os.path.join(REFERENCE_PATH, BLENDER_ZIP_FILENAME)
2191             os.rename(tmp_path, final_path)
2192
2193         if ARGS.sphinx_build_pdf:
2194             # copy the pdf to REFERENCE_PATH
2195             shutil.copy(os.path.join(SPHINX_OUT_PDF, "contents.pdf"),
2196                         os.path.join(REFERENCE_PATH, BLENDER_PDF_FILENAME))
2197
2198     sys.exit()
2199
2200
2201 if __name__ == '__main__':
2202     main()