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