Peer pressure :) Fix some very public (but probably harmless) errors in extern/bullet...
[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
914 def pycontext2sphinx(basepath):
915     # Only use once. very irregular
916
917     filepath = os.path.join(basepath, "bpy.context.rst")
918     file = open(filepath, "w", encoding="utf-8")
919     fw = file.write
920     fw(title_string("Context Access (bpy.context)", "="))
921     fw(".. module:: bpy.context\n")
922     fw("\n")
923     fw("The context members available depend on the area of blender which is currently being accessed.\n")
924     fw("\n")
925     fw("Note that all context values are readonly, but may be modified through the data api or by running operators\n\n")
926
927     # nasty, get strings directly from blender because there is no other way to get it
928     import ctypes
929
930     context_strings = (
931         "screen_context_dir",
932         "view3d_context_dir",
933         "buttons_context_dir",
934         "image_context_dir",
935         "node_context_dir",
936         "text_context_dir",
937     )
938
939     # Changes in blender will force errors here
940     type_map = {
941         "active_base": ("ObjectBase", False),
942         "active_bone": ("Bone", False),
943         "active_object": ("Object", False),
944         "active_operator": ("Operator", False),
945         "active_pose_bone": ("PoseBone", False),
946         "armature": ("Armature", False),
947         "bone": ("Bone", False),
948         "brush": ("Brush", False),
949         "camera": ("Camera", False),
950         "cloth": ("ClothModifier", False),
951         "collision": ("CollisionModifier", False),
952         "curve": ("Curve", False),
953         "dynamic_paint": ("DynamicPaintModifier", False),
954         "edit_bone": ("EditBone", False),
955         "edit_image": ("Image", False),
956         "edit_object": ("Object", False),
957         "edit_text": ("Text", False),
958         "editable_bones": ("EditBone", True),
959         "fluid": ("FluidSimulationModifier", False),
960         "image_paint_object": ("Object", False),
961         "lamp": ("Lamp", False),
962         "lattice": ("Lattice", False),
963         "material": ("Material", False),
964         "material_slot": ("MaterialSlot", False),
965         "mesh": ("Mesh", False),
966         "meta_ball": ("MetaBall", False),
967         "object": ("Object", False),
968         "particle_edit_object": ("Object", False),
969         "particle_system": ("ParticleSystem", False),
970         "particle_system_editable": ("ParticleSystem", False),
971         "pose_bone": ("PoseBone", False),
972         "scene": ("Scene", False),
973         "sculpt_object": ("Object", False),
974         "selectable_bases": ("ObjectBase", True),
975         "selectable_objects": ("Object", True),
976         "selected_bases": ("ObjectBase", True),
977         "selected_bones": ("Bone", True),
978         "selected_editable_bases": ("ObjectBase", True),
979         "selected_editable_bones": ("Bone", True),
980         "selected_editable_objects": ("Object", True),
981         "selected_editable_sequences": ("Sequence", True),
982         "selected_nodes": ("Node", True),
983         "selected_objects": ("Object", True),
984         "selected_pose_bones": ("PoseBone", True),
985         "selected_sequences": ("Sequence", True),
986         "sequences": ("Sequence", True),
987         "smoke": ("SmokeModifier", False),
988         "soft_body": ("SoftBodyModifier", False),
989         "speaker": ("Speaker", False),
990         "texture": ("Texture", False),
991         "texture_slot": ("MaterialTextureSlot", False),
992         "texture_user": ("ID", False),
993         "vertex_paint_object": ("Object", False),
994         "visible_bases": ("ObjectBase", True),
995         "visible_bones": ("Object", True),
996         "visible_objects": ("Object", True),
997         "visible_pose_bones": ("PoseBone", True),
998         "weight_paint_object": ("Object", False),
999         "world": ("World", False),
1000     }
1001
1002     unique = set()
1003     blend_cdll = ctypes.CDLL("")
1004     for ctx_str in context_strings:
1005         subsection = "%s Context" % ctx_str.split("_")[0].title()
1006         fw("\n%s\n%s\n\n" % (subsection, (len(subsection) * '-')))
1007
1008         attr = ctypes.addressof(getattr(blend_cdll, ctx_str))
1009         c_char_p_p = ctypes.POINTER(ctypes.c_char_p)
1010         char_array = c_char_p_p.from_address(attr)
1011         i = 0
1012         while char_array[i] is not None:
1013             member = ctypes.string_at(char_array[i]).decode(encoding="ascii")
1014             fw(".. data:: %s\n\n" % member)
1015             member_type, is_seq = type_map[member]
1016             fw("   :type: %s :class:`bpy.types.%s`\n\n" % ("sequence of " if is_seq else "", member_type))
1017             unique.add(member)
1018             i += 1
1019
1020     # generate typemap...
1021     # for member in sorted(unique):
1022     #     print('        "%s": ("", False),' % member)
1023     if len(type_map) > len(unique):
1024         raise Exception("Some types are not used: %s" % str([member for member in type_map if member not in unique]))
1025     else:
1026         pass  # will have raised an error above
1027
1028     file.close()
1029
1030
1031 def pyrna_enum2sphinx(prop, use_empty_descriptions=False):
1032     """ write a bullet point list of enum + descrptons
1033     """
1034
1035     if use_empty_descriptions:
1036         ok = True
1037     else:
1038         ok = False
1039         for identifier, name, description in prop.enum_items:
1040             if description:
1041                 ok = True
1042                 break
1043
1044     if ok:
1045         return "".join(["* ``%s`` %s.\n" %
1046                         (identifier,
1047                          ", ".join(val for val in (name, description) if val),
1048                          )
1049                         for identifier, name, description in prop.enum_items
1050                         ])
1051     else:
1052         return ""
1053
1054
1055 def pyrna2sphinx(basepath):
1056     """ bpy.types and bpy.ops
1057     """
1058     structs, funcs, ops, props = rna_info.BuildRNAInfo()
1059     if FILTER_BPY_TYPES is not None:
1060         structs = {k: v for k, v in structs.items() if k[1] in FILTER_BPY_TYPES}
1061
1062     if FILTER_BPY_OPS is not None:
1063         ops = {k: v for k, v in ops.items() if v.module_name in FILTER_BPY_OPS}
1064
1065     def write_param(ident, fw, prop, is_return=False):
1066         if is_return:
1067             id_name = "return"
1068             id_type = "rtype"
1069             kwargs = {"as_ret": True}
1070             identifier = ""
1071         else:
1072             id_name = "arg"
1073             id_type = "type"
1074             kwargs = {"as_arg": True}
1075             identifier = " %s" % prop.identifier
1076
1077         kwargs["class_fmt"] = ":class:`%s`"
1078
1079         kwargs["collection_id"] = _BPY_PROP_COLLECTION_ID
1080
1081         type_descr = prop.get_type_description(**kwargs)
1082
1083         enum_text = pyrna_enum2sphinx(prop)
1084
1085         if prop.name or prop.description or enum_text:
1086             fw(ident + ":%s%s:\n\n" % (id_name, identifier))
1087
1088             if prop.name or prop.description:
1089                 fw(ident + "   " + ", ".join(val for val in (prop.name, prop.description) if val) + "\n\n")
1090
1091             # special exception, cant use genric code here for enums
1092             if enum_text:
1093                 write_indented_lines(ident + "   ", fw, enum_text)
1094                 fw("\n")
1095             del enum_text
1096             # end enum exception
1097
1098         fw(ident + ":%s%s: %s\n" % (id_type, identifier, type_descr))
1099
1100     def write_struct(struct):
1101         #if not struct.identifier.startswith("Sc") and not struct.identifier.startswith("I"):
1102         #    return
1103
1104         #if not struct.identifier == "Object":
1105         #    return
1106
1107         filepath = os.path.join(basepath, "bpy.types.%s.rst" % struct.identifier)
1108         file = open(filepath, "w", encoding="utf-8")
1109         fw = file.write
1110
1111         base_id = getattr(struct.base, "identifier", "")
1112         struct_id = struct.identifier
1113
1114         if _BPY_STRUCT_FAKE:
1115             if not base_id:
1116                 base_id = _BPY_STRUCT_FAKE
1117
1118         if base_id:
1119             title = "%s(%s)" % (struct_id, base_id)
1120         else:
1121             title = struct_id
1122
1123         fw(title_string(title, "="))
1124
1125         fw(".. module:: bpy.types\n\n")
1126
1127         # docs first?, ok
1128         write_example_ref("", fw, "bpy.types.%s" % struct_id)
1129
1130         base_ids = [base.identifier for base in struct.get_bases()]
1131
1132         if _BPY_STRUCT_FAKE:
1133             base_ids.append(_BPY_STRUCT_FAKE)
1134
1135         base_ids.reverse()
1136
1137         if base_ids:
1138             if len(base_ids) > 1:
1139                 fw("base classes --- ")
1140             else:
1141                 fw("base class --- ")
1142
1143             fw(", ".join((":class:`%s`" % base_id) for base_id in base_ids))
1144             fw("\n\n")
1145
1146         subclass_ids = [s.identifier for s in structs.values() if s.base is struct if not rna_info.rna_id_ignore(s.identifier)]
1147         if subclass_ids:
1148             fw("subclasses --- \n" + ", ".join((":class:`%s`" % s) for s in subclass_ids) + "\n\n")
1149
1150         base_id = getattr(struct.base, "identifier", "")
1151
1152         if _BPY_STRUCT_FAKE:
1153             if not base_id:
1154                 base_id = _BPY_STRUCT_FAKE
1155
1156         if base_id:
1157             fw(".. class:: %s(%s)\n\n" % (struct_id, base_id))
1158         else:
1159             fw(".. class:: %s\n\n" % struct_id)
1160
1161         fw("   %s\n\n" % struct.description)
1162
1163         # properties sorted in alphabetical order
1164         sorted_struct_properties = struct.properties[:]
1165         sorted_struct_properties.sort(key=lambda prop: prop.identifier)
1166
1167         # support blacklisting props
1168         struct_blacklist = RNA_BLACKLIST.get(struct_id, ())
1169
1170         for prop in sorted_struct_properties:
1171
1172             # support blacklisting props
1173             if prop.identifier in struct_blacklist:
1174                 continue
1175
1176             type_descr = prop.get_type_description(class_fmt=":class:`%s`", collection_id=_BPY_PROP_COLLECTION_ID)
1177             # readonly properties use "data" directive, variables properties use "attribute" directive
1178             if 'readonly' in type_descr:
1179                 fw("   .. data:: %s\n\n" % prop.identifier)
1180             else:
1181                 fw("   .. attribute:: %s\n\n" % prop.identifier)
1182             if prop.description:
1183                 fw("      %s\n\n" % prop.description)
1184
1185             # special exception, cant use genric code here for enums
1186             if prop.type == "enum":
1187                 enum_text = pyrna_enum2sphinx(prop)
1188                 if enum_text:
1189                     write_indented_lines("      ", fw, enum_text)
1190                     fw("\n")
1191                 del enum_text
1192             # end enum exception
1193
1194             fw("      :type: %s\n\n" % type_descr)
1195
1196         # python attributes
1197         py_properties = struct.get_py_properties()
1198         py_prop = None
1199         for identifier, py_prop in py_properties:
1200             pyprop2sphinx("   ", fw, identifier, py_prop)
1201         del py_properties, py_prop
1202
1203         for func in struct.functions:
1204             args_str = ", ".join(prop.get_arg_default(force=False) for prop in func.args)
1205
1206             fw("   .. %s:: %s(%s)\n\n" % ("classmethod" if func.is_classmethod else "method", func.identifier, args_str))
1207             fw("      %s\n\n" % func.description)
1208
1209             for prop in func.args:
1210                 write_param("      ", fw, prop)
1211
1212             if len(func.return_values) == 1:
1213                 write_param("      ", fw, func.return_values[0], is_return=True)
1214             elif func.return_values:  # multiple return values
1215                 fw("      :return (%s):\n" % ", ".join(prop.identifier for prop in func.return_values))
1216                 for prop in func.return_values:
1217                     # TODO, pyrna_enum2sphinx for multiple return values... actually dont think we even use this but still!!!
1218                     type_descr = prop.get_type_description(as_ret=True, class_fmt=":class:`%s`", collection_id=_BPY_PROP_COLLECTION_ID)
1219                     descr = prop.description
1220                     if not descr:
1221                         descr = prop.name
1222                     fw("         `%s`, %s, %s\n\n" % (prop.identifier, descr, type_descr))
1223
1224             write_example_ref("      ", fw, "bpy.types." + struct_id + "." + func.identifier)
1225
1226             fw("\n")
1227
1228         # python methods
1229         py_funcs = struct.get_py_functions()
1230         py_func = None
1231
1232         for identifier, py_func in py_funcs:
1233             pyfunc2sphinx("   ", fw, identifier, py_func, is_class=True)
1234         del py_funcs, py_func
1235
1236         py_funcs = struct.get_py_c_functions()
1237         py_func = None
1238
1239         for identifier, py_func in py_funcs:
1240             py_c_func2sphinx("   ", fw, "bpy.types", struct_id, identifier, py_func, is_class=True)
1241
1242         lines = []
1243
1244         if struct.base or _BPY_STRUCT_FAKE:
1245             bases = list(reversed(struct.get_bases()))
1246
1247             # props
1248             lines[:] = []
1249
1250             if _BPY_STRUCT_FAKE:
1251                 descr_items = [(key, descr) for key, descr in sorted(bpy.types.Struct.__bases__[0].__dict__.items()) if not key.startswith("__")]
1252
1253             if _BPY_STRUCT_FAKE:
1254                 for key, descr in descr_items:
1255                     if type(descr) == GetSetDescriptorType:
1256                         lines.append("   * :class:`%s.%s`\n" % (_BPY_STRUCT_FAKE, key))
1257
1258             for base in bases:
1259                 for prop in base.properties:
1260                     lines.append("   * :class:`%s.%s`\n" % (base.identifier, prop.identifier))
1261
1262                 for identifier, py_prop in base.get_py_properties():
1263                     lines.append("   * :class:`%s.%s`\n" % (base.identifier, identifier))
1264
1265                 for identifier, py_prop in base.get_py_properties():
1266                     lines.append("   * :class:`%s.%s`\n" % (base.identifier, identifier))
1267
1268             if lines:
1269                 fw(".. rubric:: Inherited Properties\n\n")
1270
1271                 fw(".. hlist::\n")
1272                 fw("   :columns: 2\n\n")
1273
1274                 for line in lines:
1275                     fw(line)
1276                 fw("\n")
1277
1278             # funcs
1279             lines[:] = []
1280
1281             if _BPY_STRUCT_FAKE:
1282                 for key, descr in descr_items:
1283                     if type(descr) == MethodDescriptorType:
1284                         lines.append("   * :class:`%s.%s`\n" % (_BPY_STRUCT_FAKE, key))
1285
1286             for base in bases:
1287                 for func in base.functions:
1288                     lines.append("   * :class:`%s.%s`\n" % (base.identifier, func.identifier))
1289                 for identifier, py_func in base.get_py_functions():
1290                     lines.append("   * :class:`%s.%s`\n" % (base.identifier, identifier))
1291
1292             if lines:
1293                 fw(".. rubric:: Inherited Functions\n\n")
1294
1295                 fw(".. hlist::\n")
1296                 fw("   :columns: 2\n\n")
1297
1298                 for line in lines:
1299                     fw(line)
1300                 fw("\n")
1301
1302             lines[:] = []
1303
1304         if struct.references:
1305             # use this otherwise it gets in the index for a normal heading.
1306             fw(".. rubric:: References\n\n")
1307
1308             fw(".. hlist::\n")
1309             fw("   :columns: 2\n\n")
1310
1311             for ref in struct.references:
1312                 ref_split = ref.split(".")
1313                 if len(ref_split) > 2:
1314                     ref = ref_split[-2] + "." + ref_split[-1]
1315                 fw("   * :class:`%s`\n" % ref)
1316             fw("\n")
1317
1318         # docs last?, disable for now
1319         # write_example_ref("", fw, "bpy.types.%s" % struct_id)
1320         file.close()
1321
1322     if "bpy.types" not in EXCLUDE_MODULES:
1323         for struct in structs.values():
1324             # TODO, rna_info should filter these out!
1325             if "_OT_" in struct.identifier:
1326                 continue
1327             write_struct(struct)
1328
1329         def fake_bpy_type(class_value, class_name, descr_str, use_subclasses=True):
1330             filepath = os.path.join(basepath, "bpy.types.%s.rst" % class_name)
1331             file = open(filepath, "w", encoding="utf-8")
1332             fw = file.write
1333
1334             fw(title_string(class_name, "="))
1335
1336             fw(".. module:: bpy.types\n")
1337             fw("\n")
1338
1339             if use_subclasses:
1340                 subclass_ids = [s.identifier for s in structs.values() if s.base is None if not rna_info.rna_id_ignore(s.identifier)]
1341                 if subclass_ids:
1342                     fw("subclasses --- \n" + ", ".join((":class:`%s`" % s) for s in sorted(subclass_ids)) + "\n\n")
1343
1344             fw(".. class:: %s\n\n" % class_name)
1345             fw("   %s\n\n" % descr_str)
1346             fw("   .. note::\n\n")
1347             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)
1348
1349             descr_items = [(key, descr) for key, descr in sorted(class_value.__dict__.items()) if not key.startswith("__")]
1350
1351             for key, descr in descr_items:
1352                 if type(descr) == MethodDescriptorType:  # GetSetDescriptorType, GetSetDescriptorType's are not documented yet
1353                     py_descr2sphinx("   ", fw, descr, "bpy.types", class_name, key)
1354
1355             for key, descr in descr_items:
1356                 if type(descr) == GetSetDescriptorType:
1357                     py_descr2sphinx("   ", fw, descr, "bpy.types", class_name, key)
1358             file.close()
1359
1360         # write fake classes
1361         if _BPY_STRUCT_FAKE:
1362             class_value = bpy.types.Struct.__bases__[0]
1363             fake_bpy_type(class_value, _BPY_STRUCT_FAKE, "built-in base class for all classes in bpy.types.", use_subclasses=True)
1364
1365         if _BPY_PROP_COLLECTION_FAKE:
1366             class_value = bpy.data.objects.__class__
1367             fake_bpy_type(class_value, _BPY_PROP_COLLECTION_FAKE, "built-in class used for all collections.", use_subclasses=False)
1368
1369     # operators
1370     def write_ops():
1371         API_BASEURL = "http://svn.blender.org/svnroot/bf-blender/trunk/blender/release/scripts"
1372         API_BASEURL_ADDON = "http://svn.blender.org/svnroot/bf-extensions/trunk/py/scripts"
1373         API_BASEURL_ADDON_CONTRIB = "http://svn.blender.org/svnroot/bf-extensions/contrib/py/scripts"
1374
1375         op_modules = {}
1376         for op in ops.values():
1377             op_modules.setdefault(op.module_name, []).append(op)
1378         del op
1379
1380         for op_module_name, ops_mod in op_modules.items():
1381             filepath = os.path.join(basepath, "bpy.ops.%s.rst" % op_module_name)
1382             file = open(filepath, "w", encoding="utf-8")
1383             fw = file.write
1384
1385             title = "%s Operators" % op_module_name.replace("_", " ").title()
1386
1387             fw(title_string(title, "="))
1388
1389             fw(".. module:: bpy.ops.%s\n\n" % op_module_name)
1390
1391             ops_mod.sort(key=lambda op: op.func_name)
1392
1393             for op in ops_mod:
1394                 args_str = ", ".join(prop.get_arg_default(force=True) for prop in op.args)
1395                 fw(".. function:: %s(%s)\n\n" % (op.func_name, args_str))
1396
1397                 # if the description isn't valid, we output the standard warning
1398                 # with a link to the wiki so that people can help
1399                 if not op.description or op.description == "(undocumented operator)":
1400                     operator_description = undocumented_message('bpy.ops', op.module_name, op.func_name)
1401                 else:
1402                     operator_description = op.description
1403
1404                 fw("   %s\n\n" % operator_description)
1405                 for prop in op.args:
1406                     write_param("   ", fw, prop)
1407                 if op.args:
1408                     fw("\n")
1409
1410                 location = op.get_location()
1411                 if location != (None, None):
1412                     if location[0].startswith("addons_contrib" + os.sep):
1413                         url_base = API_BASEURL_ADDON_CONTRIB
1414                     elif location[0].startswith("addons" + os.sep):
1415                         url_base = API_BASEURL_ADDON
1416                     else:
1417                         url_base = API_BASEURL
1418
1419                     fw("   :file: `%s <%s/%s>`_:%d\n\n" % (location[0],
1420                                                            url_base,
1421                                                            location[0],
1422                                                            location[1]))
1423
1424             file.close()
1425
1426     if "bpy.ops" not in EXCLUDE_MODULES:
1427         write_ops()
1428
1429
1430 def write_sphinx_conf_py(basepath):
1431     '''
1432     Write sphinx's conf.py
1433     '''
1434     filepath = os.path.join(basepath, "conf.py")
1435     file = open(filepath, "w", encoding="utf-8")
1436     fw = file.write
1437
1438     fw("project = 'Blender'\n")
1439     # fw("master_doc = 'index'\n")
1440     fw("copyright = u'Blender Foundation'\n")
1441     fw("version = '%s - API'\n" % BLENDER_VERSION_DOTS)
1442     fw("release = '%s - API'\n" % BLENDER_VERSION_DOTS)
1443
1444     if ARGS.sphinx_theme != 'default':
1445         fw("html_theme = '%s'\n" % ARGS.sphinx_theme)
1446
1447     if ARGS.sphinx_theme in SPHINX_THEMES['bf']:
1448         fw("html_theme_path = ['../']\n")
1449         # copied with the theme, exclude else we get an error [#28873]
1450         fw("html_favicon = 'favicon.ico'\n")    # in <theme>/static/
1451
1452     # not helpful since the source is generated, adds to upload size.
1453     fw("html_copy_source = False\n")
1454     fw("\n")
1455
1456     # needed for latex, pdf gen
1457     fw("latex_documents = [ ('contents', 'contents.tex', 'Blender Index', 'Blender Foundation', 'manual'), ]\n")
1458     fw("latex_paper_size = 'a4paper'\n")
1459     file.close()
1460
1461
1462 def write_rst_contents(basepath):
1463     '''
1464     Write the rst file of the main page, needed for sphinx (index.html)
1465     '''
1466     filepath = os.path.join(basepath, "contents.rst")
1467     file = open(filepath, "w", encoding="utf-8")
1468     fw = file.write
1469
1470     fw(title_string("Blender Documentation Contents", "%", double=True))
1471     fw("\n")
1472     fw("Welcome, this document is an API reference for Blender %s, built %s.\n" % (BLENDER_VERSION_DOTS, BLENDER_DATE))
1473     fw("\n")
1474
1475     # fw("`A PDF version of this document is also available <%s>`_\n" % BLENDER_PDF_FILENAME)
1476     fw("`A compressed ZIP file of this site is available <%s>`_\n" % BLENDER_ZIP_FILENAME)
1477
1478     fw("\n")
1479
1480     if not EXCLUDE_INFO_DOCS:
1481         fw(title_string("Blender/Python Documentation", "=", double=True))
1482
1483         fw(".. toctree::\n")
1484         fw("   :maxdepth: 1\n\n")
1485         for info, info_desc in INFO_DOCS:
1486             fw("   %s <%s>\n\n" % (info_desc, info))
1487         fw("\n")
1488
1489     fw(title_string("Application Modules", "=", double=True))
1490     fw(".. toctree::\n")
1491     fw("   :maxdepth: 1\n\n")
1492
1493     app_modules = (
1494         "bpy.context",  # note: not actually a module
1495         "bpy.data",     # note: not actually a module
1496         "bpy.ops",
1497         "bpy.types",
1498
1499         # py modules
1500         "bpy.utils",
1501         "bpy.path",
1502         "bpy.app",
1503         "bpy.app.handlers",
1504
1505         # C modules
1506         "bpy.props",
1507         )
1508
1509     for mod in app_modules:
1510         if mod not in EXCLUDE_MODULES:
1511             fw("   %s\n\n" % mod)
1512
1513     fw(title_string("Standalone Modules", "=", double=True))
1514     fw(".. toctree::\n")
1515     fw("   :maxdepth: 1\n\n")
1516
1517     standalone_modules = (
1518         # mathutils
1519         "mathutils", "mathutils.geometry", "mathutils.noise",
1520         # misc
1521         "bgl", "blf", "gpu", "aud", "bpy_extras",
1522         # bmesh
1523         "bmesh", "bmesh.types", "bmesh.utils",
1524         )
1525
1526     for mod in standalone_modules:
1527         if mod not in EXCLUDE_MODULES:
1528             fw("   %s\n\n" % mod)
1529
1530     # game engine
1531     if "bge" not in EXCLUDE_MODULES:
1532         fw(title_string("Game Engine Modules", "=", double=True))
1533         fw(".. toctree::\n")
1534         fw("   :maxdepth: 1\n\n")
1535         fw("   bge.types.rst\n\n")
1536         fw("   bge.logic.rst\n\n")
1537         fw("   bge.render.rst\n\n")
1538         fw("   bge.texture.rst\n\n")
1539         fw("   bge.events.rst\n\n")
1540         fw("   bge.constraints.rst\n\n")
1541
1542     # rna generated change log
1543     fw(title_string("API Info", "=", double=True))
1544     fw(".. toctree::\n")
1545     fw("   :maxdepth: 1\n\n")
1546     fw("   change_log.rst\n\n")
1547
1548     fw("\n")
1549     fw("\n")
1550     fw(".. note:: The Blender Python API has areas which are still in development.\n")
1551     fw("   \n")
1552     fw("   The following areas are subject to change.\n")
1553     fw("      * operator behavior, names and arguments\n")
1554     fw("      * mesh creation and editing functions\n")
1555     fw("   \n")
1556     fw("   These parts of the API are relatively stable and are unlikely to change significantly\n")
1557     fw("      * data API, access to attributes of blender data such as mesh verts, material color, timeline frames and scene objects\n")
1558     fw("      * user interface functions for defining buttons, creation of menus, headers, panels\n")
1559     fw("      * render engine integration\n")
1560     fw("      * modules: bgl, mathutils & game engine.\n")
1561     fw("\n")
1562
1563     file.close()
1564
1565
1566 def write_rst_bpy(basepath):
1567     '''
1568     Write rst file of bpy module (disabled by default)
1569     '''
1570     if ARGS.bpy:
1571         filepath = os.path.join(basepath, "bpy.rst")
1572         file = open(filepath, "w", encoding="utf-8")
1573         fw = file.write
1574
1575         fw("\n")
1576
1577         title = ":mod:`bpy` --- Blender Python Module"
1578
1579         fw(title_string(title, "="))
1580
1581         fw(".. module:: bpy.types\n\n")
1582         file.close()
1583
1584
1585 def write_rst_types_index(basepath):
1586     '''
1587     Write the rst file of bpy.types module (index)
1588     '''
1589     if "bpy.types" not in EXCLUDE_MODULES:
1590         filepath = os.path.join(basepath, "bpy.types.rst")
1591         file = open(filepath, "w", encoding="utf-8")
1592         fw = file.write
1593         fw(title_string("Types (bpy.types)", "="))
1594         fw(".. toctree::\n")
1595         fw("   :glob:\n\n")
1596         fw("   bpy.types.*\n\n")
1597         file.close()
1598
1599
1600 def write_rst_ops_index(basepath):
1601     '''
1602     Write the rst file of bpy.ops module (index)
1603     '''
1604     if "bpy.ops" not in EXCLUDE_MODULES:
1605         filepath = os.path.join(basepath, "bpy.ops.rst")
1606         file = open(filepath, "w", encoding="utf-8")
1607         fw = file.write
1608         fw(title_string("Operators (bpy.ops)", "="))
1609         write_example_ref("", fw, "bpy.ops")
1610         fw(".. toctree::\n")
1611         fw("   :glob:\n\n")
1612         fw("   bpy.ops.*\n\n")
1613         file.close()
1614
1615
1616 def write_rst_data(basepath):
1617     '''
1618     Write the rst file of bpy.data module
1619     '''
1620     if "bpy.data" not in EXCLUDE_MODULES:
1621         # not actually a module, only write this file so we
1622         # can reference in the TOC
1623         filepath = os.path.join(basepath, "bpy.data.rst")
1624         file = open(filepath, "w", encoding="utf-8")
1625         fw = file.write
1626         fw(title_string("Data Access (bpy.data)", "="))
1627         fw(".. module:: bpy\n")
1628         fw("\n")
1629         fw("This module is used for all blender/python access.\n")
1630         fw("\n")
1631         fw(".. data:: data\n")
1632         fw("\n")
1633         fw("   Access to blenders internal data\n")
1634         fw("\n")
1635         fw("   :type: :class:`bpy.types.BlendData`\n")
1636         fw("\n")
1637         fw(".. literalinclude:: ../examples/bpy.data.py\n")
1638         file.close()
1639
1640         EXAMPLE_SET_USED.add("bpy.data")
1641
1642
1643 def write_rst_importable_modules(basepath):
1644     '''
1645     Write the rst files of importable modules
1646     '''
1647     importable_modules = {
1648         # python_modules
1649         "bpy.path"          : "Path Utilities",
1650         "bpy.utils"         : "Utilities",
1651         "bpy_extras"        : "Extra Utilities",
1652
1653         # C_modules
1654         "aud"               : "Audio System",
1655         "blf"               : "Font Drawing",
1656         "bmesh"             : "BMesh Module",
1657         "bmesh.types"       : "BMesh Types",
1658         "bmesh.utils"       : "BMesh Utilities",
1659         "bpy.app"           : "Application Data",
1660         "bpy.app.handlers"  : "Application Handlers",
1661         "bpy.props"         : "Property Definitions",
1662         "mathutils"         : "Math Types & Utilities",
1663         "mathutils.geometry": "Geometry Utilities",
1664         "mathutils.noise"   : "Noise Utilities",
1665     }
1666     for mod_name, mod_descr in importable_modules.items():
1667         if mod_name not in EXCLUDE_MODULES:
1668             module = __import__(mod_name,
1669                                 fromlist=[mod_name.rsplit(".", 1)[-1]])
1670             pymodule2sphinx(basepath, mod_name, module, mod_descr)
1671
1672
1673 def copy_handwritten_rsts(basepath):
1674
1675     # info docs
1676     if not EXCLUDE_INFO_DOCS:
1677         for info, info_desc in INFO_DOCS:
1678             shutil.copy2(os.path.join(RST_DIR, info), basepath)
1679
1680     # TODO put this docs in blender's code and use import as per modules above
1681     handwritten_modules = [
1682         "bge.types",
1683         "bge.logic",
1684         "bge.render",
1685         "bge.texture",
1686         "bge.events",
1687         "bge.constraints",
1688         "bgl",  # "Blender OpenGl wrapper"
1689         "gpu",  # "GPU Shader Module"
1690
1691         # includes...
1692         "include__bmesh",
1693     ]
1694     for mod_name in handwritten_modules:
1695         if mod_name not in EXCLUDE_MODULES:
1696             # copy2 keeps time/date stamps
1697             shutil.copy2(os.path.join(RST_DIR, "%s.rst" % mod_name), basepath)
1698
1699     # changelog
1700     shutil.copy2(os.path.join(RST_DIR, "change_log.rst"), basepath)
1701
1702
1703 def rna2sphinx(basepath):
1704
1705     try:
1706         os.mkdir(basepath)
1707     except:
1708         pass
1709
1710     # sphinx setup
1711     write_sphinx_conf_py(basepath)
1712
1713     # main page
1714     write_rst_contents(basepath)
1715
1716     # context
1717     if "bpy.context" not in EXCLUDE_MODULES:
1718         # one of a kind, context doc (uses ctypes to extract info!)
1719         # doesn't work on mac
1720         if PLATFORM != "darwin":
1721             pycontext2sphinx(basepath)
1722
1723     # internal modules
1724     write_rst_bpy(basepath)                 # bpy, disabled by default
1725     write_rst_types_index(basepath)         # bpy.types
1726     write_rst_ops_index(basepath)           # bpy.ops
1727     pyrna2sphinx(basepath)                  # bpy.types.* and bpy.ops.*
1728     write_rst_data(basepath)                # bpy.data
1729     write_rst_importable_modules(basepath)
1730
1731     # copy the other rsts
1732     copy_handwritten_rsts(basepath)
1733
1734
1735 def align_sphinx_in_to_sphinx_in_tmp():
1736     '''
1737     Move changed files from SPHINX_IN_TMP to SPHINX_IN
1738     '''
1739     import filecmp
1740
1741     sphinx_in_files = set(os.listdir(SPHINX_IN))
1742     sphinx_in_tmp_files = set(os.listdir(SPHINX_IN_TMP))
1743
1744     # remove deprecated files that have been removed
1745     for f in sorted(sphinx_in_files):
1746         if f not in sphinx_in_tmp_files:
1747             BPY_LOGGER.debug("\tdeprecated: %s" % f)
1748             os.remove(os.path.join(SPHINX_IN, f))
1749
1750     # freshen with new files.
1751     for f in sorted(sphinx_in_tmp_files):
1752         f_from = os.path.join(SPHINX_IN_TMP, f)
1753         f_to = os.path.join(SPHINX_IN, f)
1754
1755         do_copy = True
1756         if f in sphinx_in_files:
1757             if filecmp.cmp(f_from, f_to):
1758                 do_copy = False
1759
1760         if do_copy:
1761             BPY_LOGGER.debug("\tupdating: %s" % f)
1762             shutil.copy(f_from, f_to)
1763
1764
1765 def refactor_sphinx_log(sphinx_logfile):
1766     refactored_log = []
1767     with open(sphinx_logfile, "r", encoding="utf-8") as original_logfile:
1768         lines = set(original_logfile.readlines())
1769         for line in lines:
1770             if 'warning' in line.lower() or 'error' in line.lower():
1771                 line = line.strip().split(None, 2)
1772                 if len(line) == 3:
1773                     location, kind, msg = line
1774                     location = os.path.relpath(location, start=SPHINX_IN)
1775                     refactored_log.append((kind, location, msg))
1776     with open(sphinx_logfile, "w", encoding="utf-8") as refactored_logfile:
1777         for log in sorted(refactored_log):
1778             refactored_logfile.write("%-12s %s\n             %s\n" % log)
1779
1780
1781 def main():
1782
1783     # eventually, create the dirs
1784     for dir_path in [ARGS.output_dir, SPHINX_IN]:
1785         if not os.path.exists(dir_path):
1786             os.mkdir(dir_path)
1787
1788     # eventually, log in files
1789     if ARGS.log:
1790         bpy_logfile = os.path.join(ARGS.output_dir, ".bpy.log")
1791         bpy_logfilehandler = logging.FileHandler(bpy_logfile, mode="w")
1792         bpy_logfilehandler.setLevel(logging.DEBUG)
1793         BPY_LOGGER.addHandler(bpy_logfilehandler)
1794
1795         # using a FileHandler seems to disable the stdout, so we add a StreamHandler
1796         bpy_log_stdout_handler = logging.StreamHandler(stream=sys.stdout)
1797         bpy_log_stdout_handler.setLevel(logging.DEBUG)
1798         BPY_LOGGER.addHandler(bpy_log_stdout_handler)
1799
1800     # in case of out-of-source build, copy the needed dirs
1801     if ARGS.output_dir != SCRIPT_DIR:
1802         # examples dir
1803         examples_dir_copy = os.path.join(ARGS.output_dir, "examples")
1804         if os.path.exists(examples_dir_copy):
1805             shutil.rmtree(examples_dir_copy, True)
1806         shutil.copytree(EXAMPLES_DIR,
1807                         examples_dir_copy,
1808                         ignore=shutil.ignore_patterns(*(".svn",)),
1809                         copy_function=shutil.copy)
1810
1811         # eventually, copy the theme dir
1812         if ARGS.sphinx_theme in SPHINX_THEMES['bf']:
1813             if os.path.exists(SPHINX_THEME_DIR):
1814                 shutil.rmtree(SPHINX_THEME_DIR, True)
1815             shutil.copytree(SPHINX_THEME_SVN_DIR,
1816                             SPHINX_THEME_DIR,
1817                             ignore=shutil.ignore_patterns(*(".svn",)),
1818                             copy_function=shutil.copy)
1819
1820     # dump the api in rst files
1821     if os.path.exists(SPHINX_IN_TMP):
1822         shutil.rmtree(SPHINX_IN_TMP, True)
1823
1824     rna2sphinx(SPHINX_IN_TMP)
1825
1826     if ARGS.full_rebuild:
1827         # only for full updates
1828         shutil.rmtree(SPHINX_IN, True)
1829         shutil.copytree(SPHINX_IN_TMP,
1830                         SPHINX_IN,
1831                         copy_function=shutil.copy)
1832         if ARGS.sphinx_build and os.path.exists(SPHINX_OUT):
1833             shutil.rmtree(SPHINX_OUT, True)
1834         if ARGS.sphinx_build_pdf and os.path.exists(SPHINX_OUT_PDF):
1835             shutil.rmtree(SPHINX_OUT_PDF, True)
1836     else:
1837         # move changed files in SPHINX_IN
1838         align_sphinx_in_to_sphinx_in_tmp()
1839
1840     # report which example files weren't used
1841     EXAMPLE_SET_UNUSED = EXAMPLE_SET - EXAMPLE_SET_USED
1842     if EXAMPLE_SET_UNUSED:
1843         BPY_LOGGER.debug("\nUnused examples found in '%s'..." % EXAMPLES_DIR)
1844         for f in sorted(EXAMPLE_SET_UNUSED):
1845             BPY_LOGGER.debug("    %s.py" % f)
1846         BPY_LOGGER.debug("  %d total\n" % len(EXAMPLE_SET_UNUSED))
1847
1848     # eventually, build the html docs
1849     if ARGS.sphinx_build:
1850         import subprocess
1851         subprocess.call(SPHINX_BUILD)
1852
1853         # sphinx-build log cleanup+sort
1854         if ARGS.log:
1855             if os.stat(SPHINX_BUILD_LOG).st_size:
1856                 refactor_sphinx_log(SPHINX_BUILD_LOG)
1857
1858     # eventually, build the pdf docs
1859     if ARGS.sphinx_build_pdf:
1860         import subprocess
1861         subprocess.call(SPHINX_BUILD_PDF)
1862         subprocess.call(SPHINX_MAKE_PDF, stdout=SPHINX_MAKE_PDF_STDOUT)
1863
1864         # sphinx-build log cleanup+sort
1865         if ARGS.log:
1866             if os.stat(SPHINX_BUILD_PDF_LOG).st_size:
1867                 refactor_sphinx_log(SPHINX_BUILD_PDF_LOG)
1868
1869     # eventually, prepare the dir to be deployed online (REFERENCE_PATH)
1870     if ARGS.pack_reference:
1871
1872         if ARGS.sphinx_build:
1873             # delete REFERENCE_PATH
1874             if os.path.exists(REFERENCE_PATH):
1875                 shutil.rmtree(REFERENCE_PATH, True)
1876
1877             # copy SPHINX_OUT to the REFERENCE_PATH
1878             ignores = ('.doctrees', 'objects.inv', '.buildinfo')
1879             shutil.copytree(SPHINX_OUT,
1880                             REFERENCE_PATH,
1881                             ignore=shutil.ignore_patterns(*ignores))
1882             shutil.copy(os.path.join(REFERENCE_PATH, "contents.html"),
1883                         os.path.join(REFERENCE_PATH, "index.html"))
1884
1885             # zip REFERENCE_PATH
1886             basename = os.path.join(ARGS.output_dir, REFERENCE_NAME)
1887             tmp_path = shutil.make_archive(basename, 'zip',
1888                                            root_dir=ARGS.output_dir,
1889                                            base_dir=REFERENCE_NAME)
1890             final_path = os.path.join(REFERENCE_PATH, BLENDER_ZIP_FILENAME)
1891             os.rename(tmp_path, final_path)
1892
1893         if ARGS.sphinx_build_pdf:
1894             # copy the pdf to REFERENCE_PATH
1895             shutil.copy(os.path.join(SPHINX_OUT_PDF, "contents.pdf"),
1896                         os.path.join(REFERENCE_PATH, BLENDER_PDF_FILENAME))
1897
1898     sys.exit()
1899
1900
1901 if __name__ == '__main__':
1902     main()