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