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