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