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