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