api changelog for 2.64 and 2.63 (which I missed last release)
[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         "clip_context_dir",
938         "sequencer_context_dir",
939     )
940
941     # Changes in blender will force errors here
942     type_map = {
943         "active_base": ("ObjectBase", False),
944         "active_bone": ("Bone", False),
945         "active_object": ("Object", False),
946         "active_operator": ("Operator", False),
947         "active_pose_bone": ("PoseBone", False),
948         "active_node": ("Node", False),
949         "armature": ("Armature", False),
950         "bone": ("Bone", False),
951         "brush": ("Brush", False),
952         "camera": ("Camera", False),
953         "cloth": ("ClothModifier", False),
954         "collision": ("CollisionModifier", False),
955         "curve": ("Curve", False),
956         "dynamic_paint": ("DynamicPaintModifier", False),
957         "edit_bone": ("EditBone", False),
958         "edit_image": ("Image", False),
959         "edit_mask": ("Mask", False),
960         "edit_movieclip": ("MovieClip", False),
961         "edit_object": ("Object", False),
962         "edit_text": ("Text", False),
963         "editable_bones": ("EditBone", True),
964         "fluid": ("FluidSimulationModifier", False),
965         "image_paint_object": ("Object", False),
966         "lamp": ("Lamp", False),
967         "lattice": ("Lattice", False),
968         "material": ("Material", False),
969         "material_slot": ("MaterialSlot", False),
970         "mesh": ("Mesh", False),
971         "meta_ball": ("MetaBall", False),
972         "object": ("Object", False),
973         "particle_edit_object": ("Object", False),
974         "particle_settings": ("ParticleSettings", False),
975         "particle_system": ("ParticleSystem", False),
976         "particle_system_editable": ("ParticleSystem", False),
977         "pose_bone": ("PoseBone", False),
978         "scene": ("Scene", False),
979         "sculpt_object": ("Object", False),
980         "selectable_bases": ("ObjectBase", True),
981         "selectable_objects": ("Object", True),
982         "selected_bases": ("ObjectBase", True),
983         "selected_bones": ("Bone", True),
984         "selected_editable_bases": ("ObjectBase", True),
985         "selected_editable_bones": ("Bone", True),
986         "selected_editable_objects": ("Object", True),
987         "selected_editable_sequences": ("Sequence", True),
988         "selected_nodes": ("Node", True),
989         "selected_objects": ("Object", True),
990         "selected_pose_bones": ("PoseBone", True),
991         "selected_sequences": ("Sequence", True),
992         "sequences": ("Sequence", True),
993         "smoke": ("SmokeModifier", False),
994         "soft_body": ("SoftBodyModifier", False),
995         "speaker": ("Speaker", False),
996         "texture": ("Texture", False),
997         "texture_slot": ("MaterialTextureSlot", False),
998         "texture_user": ("ID", False),
999         "vertex_paint_object": ("Object", False),
1000         "visible_bases": ("ObjectBase", True),
1001         "visible_bones": ("Object", True),
1002         "visible_objects": ("Object", True),
1003         "visible_pose_bones": ("PoseBone", True),
1004         "weight_paint_object": ("Object", False),
1005         "world": ("World", False),
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 = 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(type_map) > len(unique):
1030         raise Exception("Some types are not used: %s" % str([member for member in 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             for ref in struct.references:
1318                 ref_split = ref.split(".")
1319                 if len(ref_split) > 2:
1320                     ref = ref_split[-2] + "." + ref_split[-1]
1321                 fw("   * :class:`%s`\n" % ref)
1322             fw("\n")
1323
1324         # docs last?, disable for now
1325         # write_example_ref("", fw, "bpy.types.%s" % struct_id)
1326         file.close()
1327
1328     if "bpy.types" not in EXCLUDE_MODULES:
1329         for struct in structs.values():
1330             # TODO, rna_info should filter these out!
1331             if "_OT_" in struct.identifier:
1332                 continue
1333             write_struct(struct)
1334
1335         def fake_bpy_type(class_value, class_name, descr_str, use_subclasses=True):
1336             filepath = os.path.join(basepath, "bpy.types.%s.rst" % class_name)
1337             file = open(filepath, "w", encoding="utf-8")
1338             fw = file.write
1339
1340             fw(title_string(class_name, "="))
1341
1342             fw(".. module:: bpy.types\n")
1343             fw("\n")
1344
1345             if use_subclasses:
1346                 subclass_ids = [s.identifier for s in structs.values() if s.base is None if not rna_info.rna_id_ignore(s.identifier)]
1347                 if subclass_ids:
1348                     fw("subclasses --- \n" + ", ".join((":class:`%s`" % s) for s in sorted(subclass_ids)) + "\n\n")
1349
1350             fw(".. class:: %s\n\n" % class_name)
1351             fw("   %s\n\n" % descr_str)
1352             fw("   .. note::\n\n")
1353             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)
1354
1355             descr_items = [(key, descr) for key, descr in sorted(class_value.__dict__.items()) if not key.startswith("__")]
1356
1357             for key, descr in descr_items:
1358                 if type(descr) == MethodDescriptorType:  # GetSetDescriptorType, GetSetDescriptorType's are not documented yet
1359                     py_descr2sphinx("   ", fw, descr, "bpy.types", class_name, key)
1360
1361             for key, descr in descr_items:
1362                 if type(descr) == GetSetDescriptorType:
1363                     py_descr2sphinx("   ", fw, descr, "bpy.types", class_name, key)
1364             file.close()
1365
1366         # write fake classes
1367         if _BPY_STRUCT_FAKE:
1368             class_value = bpy.types.Struct.__bases__[0]
1369             fake_bpy_type(class_value, _BPY_STRUCT_FAKE, "built-in base class for all classes in bpy.types.", use_subclasses=True)
1370
1371         if _BPY_PROP_COLLECTION_FAKE:
1372             class_value = bpy.data.objects.__class__
1373             fake_bpy_type(class_value, _BPY_PROP_COLLECTION_FAKE, "built-in class used for all collections.", use_subclasses=False)
1374
1375     # operators
1376     def write_ops():
1377         API_BASEURL = "http://svn.blender.org/svnroot/bf-blender/trunk/blender/release/scripts"
1378         API_BASEURL_ADDON = "http://svn.blender.org/svnroot/bf-extensions/trunk/py/scripts"
1379         API_BASEURL_ADDON_CONTRIB = "http://svn.blender.org/svnroot/bf-extensions/contrib/py/scripts"
1380
1381         op_modules = {}
1382         for op in ops.values():
1383             op_modules.setdefault(op.module_name, []).append(op)
1384         del op
1385
1386         for op_module_name, ops_mod in op_modules.items():
1387             filepath = os.path.join(basepath, "bpy.ops.%s.rst" % op_module_name)
1388             file = open(filepath, "w", encoding="utf-8")
1389             fw = file.write
1390
1391             title = "%s Operators" % op_module_name.replace("_", " ").title()
1392
1393             fw(title_string(title, "="))
1394
1395             fw(".. module:: bpy.ops.%s\n\n" % op_module_name)
1396
1397             ops_mod.sort(key=lambda op: op.func_name)
1398
1399             for op in ops_mod:
1400                 args_str = ", ".join(prop.get_arg_default(force=True) for prop in op.args)
1401                 fw(".. function:: %s(%s)\n\n" % (op.func_name, args_str))
1402
1403                 # if the description isn't valid, we output the standard warning
1404                 # with a link to the wiki so that people can help
1405                 if not op.description or op.description == "(undocumented operator)":
1406                     operator_description = undocumented_message('bpy.ops', op.module_name, op.func_name)
1407                 else:
1408                     operator_description = op.description
1409
1410                 fw("   %s\n\n" % operator_description)
1411                 for prop in op.args:
1412                     write_param("   ", fw, prop)
1413                 if op.args:
1414                     fw("\n")
1415
1416                 location = op.get_location()
1417                 if location != (None, None):
1418                     if location[0].startswith("addons_contrib" + os.sep):
1419                         url_base = API_BASEURL_ADDON_CONTRIB
1420                     elif location[0].startswith("addons" + os.sep):
1421                         url_base = API_BASEURL_ADDON
1422                     else:
1423                         url_base = API_BASEURL
1424
1425                     fw("   :file: `%s <%s/%s>`_:%d\n\n" % (location[0],
1426                                                            url_base,
1427                                                            location[0],
1428                                                            location[1]))
1429
1430             file.close()
1431
1432     if "bpy.ops" not in EXCLUDE_MODULES:
1433         write_ops()
1434
1435
1436 def write_sphinx_conf_py(basepath):
1437     '''
1438     Write sphinx's conf.py
1439     '''
1440     filepath = os.path.join(basepath, "conf.py")
1441     file = open(filepath, "w", encoding="utf-8")
1442     fw = file.write
1443
1444     fw("project = 'Blender'\n")
1445     # fw("master_doc = 'index'\n")
1446     fw("copyright = u'Blender Foundation'\n")
1447     fw("version = '%s - API'\n" % BLENDER_VERSION_DOTS)
1448     fw("release = '%s - API'\n" % BLENDER_VERSION_DOTS)
1449
1450     if ARGS.sphinx_theme != 'default':
1451         fw("html_theme = '%s'\n" % ARGS.sphinx_theme)
1452
1453     if ARGS.sphinx_theme in SPHINX_THEMES['bf']:
1454         fw("html_theme_path = ['../']\n")
1455         # copied with the theme, exclude else we get an error [#28873]
1456         fw("html_favicon = 'favicon.ico'\n")    # in <theme>/static/
1457
1458     # not helpful since the source is generated, adds to upload size.
1459     fw("html_copy_source = False\n")
1460     fw("\n")
1461
1462     # needed for latex, pdf gen
1463     fw("latex_documents = [ ('contents', 'contents.tex', 'Blender Index', 'Blender Foundation', 'manual'), ]\n")
1464     fw("latex_paper_size = 'a4paper'\n")
1465     file.close()
1466
1467
1468 def write_rst_contents(basepath):
1469     '''
1470     Write the rst file of the main page, needed for sphinx (index.html)
1471     '''
1472     filepath = os.path.join(basepath, "contents.rst")
1473     file = open(filepath, "w", encoding="utf-8")
1474     fw = file.write
1475
1476     fw(title_string("Blender Documentation Contents", "%", double=True))
1477     fw("\n")
1478     fw("Welcome, this document is an API reference for Blender %s, built %s.\n" % (BLENDER_VERSION_DOTS, BLENDER_DATE))
1479     fw("\n")
1480
1481     # fw("`A PDF version of this document is also available <%s>`_\n" % BLENDER_PDF_FILENAME)
1482     fw("`A compressed ZIP file of this site is available <%s>`_\n" % BLENDER_ZIP_FILENAME)
1483
1484     fw("\n")
1485
1486     if not EXCLUDE_INFO_DOCS:
1487         fw(title_string("Blender/Python Documentation", "=", double=True))
1488
1489         fw(".. toctree::\n")
1490         fw("   :maxdepth: 1\n\n")
1491         for info, info_desc in INFO_DOCS:
1492             fw("   %s <%s>\n\n" % (info_desc, info))
1493         fw("\n")
1494
1495     fw(title_string("Application Modules", "=", double=True))
1496     fw(".. toctree::\n")
1497     fw("   :maxdepth: 1\n\n")
1498
1499     app_modules = (
1500         "bpy.context",  # note: not actually a module
1501         "bpy.data",     # note: not actually a module
1502         "bpy.ops",
1503         "bpy.types",
1504
1505         # py modules
1506         "bpy.utils",
1507         "bpy.path",
1508         "bpy.app",
1509         "bpy.app.handlers",
1510
1511         # C modules
1512         "bpy.props",
1513         )
1514
1515     for mod in app_modules:
1516         if mod not in EXCLUDE_MODULES:
1517             fw("   %s\n\n" % mod)
1518
1519     fw(title_string("Standalone Modules", "=", double=True))
1520     fw(".. toctree::\n")
1521     fw("   :maxdepth: 1\n\n")
1522
1523     standalone_modules = (
1524         # mathutils
1525         "mathutils", "mathutils.geometry", "mathutils.noise",
1526         # misc
1527         "bgl", "blf", "gpu", "aud", "bpy_extras",
1528         # bmesh
1529         "bmesh", "bmesh.types", "bmesh.utils",
1530         )
1531
1532     for mod in standalone_modules:
1533         if mod not in EXCLUDE_MODULES:
1534             fw("   %s\n\n" % mod)
1535
1536     # game engine
1537     if "bge" not in EXCLUDE_MODULES:
1538         fw(title_string("Game Engine Modules", "=", double=True))
1539         fw(".. toctree::\n")
1540         fw("   :maxdepth: 1\n\n")
1541         fw("   bge.types.rst\n\n")
1542         fw("   bge.logic.rst\n\n")
1543         fw("   bge.render.rst\n\n")
1544         fw("   bge.texture.rst\n\n")
1545         fw("   bge.events.rst\n\n")
1546         fw("   bge.constraints.rst\n\n")
1547
1548     # rna generated change log
1549     fw(title_string("API Info", "=", double=True))
1550     fw(".. toctree::\n")
1551     fw("   :maxdepth: 1\n\n")
1552     fw("   change_log.rst\n\n")
1553
1554     fw("\n")
1555     fw("\n")
1556     fw(".. note:: The Blender Python API has areas which are still in development.\n")
1557     fw("   \n")
1558     fw("   The following areas are subject to change.\n")
1559     fw("      * operator behavior, names and arguments\n")
1560     fw("      * mesh creation and editing functions\n")
1561     fw("   \n")
1562     fw("   These parts of the API are relatively stable and are unlikely to change significantly\n")
1563     fw("      * data API, access to attributes of blender data such as mesh verts, material color, timeline frames and scene objects\n")
1564     fw("      * user interface functions for defining buttons, creation of menus, headers, panels\n")
1565     fw("      * render engine integration\n")
1566     fw("      * modules: bgl, mathutils & game engine.\n")
1567     fw("\n")
1568
1569     file.close()
1570
1571
1572 def write_rst_bpy(basepath):
1573     '''
1574     Write rst file of bpy module (disabled by default)
1575     '''
1576     if ARGS.bpy:
1577         filepath = os.path.join(basepath, "bpy.rst")
1578         file = open(filepath, "w", encoding="utf-8")
1579         fw = file.write
1580
1581         fw("\n")
1582
1583         title = ":mod:`bpy` --- Blender Python Module"
1584
1585         fw(title_string(title, "="))
1586
1587         fw(".. module:: bpy.types\n\n")
1588         file.close()
1589
1590
1591 def write_rst_types_index(basepath):
1592     '''
1593     Write the rst file of bpy.types module (index)
1594     '''
1595     if "bpy.types" not in EXCLUDE_MODULES:
1596         filepath = os.path.join(basepath, "bpy.types.rst")
1597         file = open(filepath, "w", encoding="utf-8")
1598         fw = file.write
1599         fw(title_string("Types (bpy.types)", "="))
1600         fw(".. toctree::\n")
1601         fw("   :glob:\n\n")
1602         fw("   bpy.types.*\n\n")
1603         file.close()
1604
1605
1606 def write_rst_ops_index(basepath):
1607     '''
1608     Write the rst file of bpy.ops module (index)
1609     '''
1610     if "bpy.ops" not in EXCLUDE_MODULES:
1611         filepath = os.path.join(basepath, "bpy.ops.rst")
1612         file = open(filepath, "w", encoding="utf-8")
1613         fw = file.write
1614         fw(title_string("Operators (bpy.ops)", "="))
1615         write_example_ref("", fw, "bpy.ops")
1616         fw(".. toctree::\n")
1617         fw("   :glob:\n\n")
1618         fw("   bpy.ops.*\n\n")
1619         file.close()
1620
1621
1622 def write_rst_data(basepath):
1623     '''
1624     Write the rst file of bpy.data module
1625     '''
1626     if "bpy.data" not in EXCLUDE_MODULES:
1627         # not actually a module, only write this file so we
1628         # can reference in the TOC
1629         filepath = os.path.join(basepath, "bpy.data.rst")
1630         file = open(filepath, "w", encoding="utf-8")
1631         fw = file.write
1632         fw(title_string("Data Access (bpy.data)", "="))
1633         fw(".. module:: bpy\n")
1634         fw("\n")
1635         fw("This module is used for all blender/python access.\n")
1636         fw("\n")
1637         fw(".. data:: data\n")
1638         fw("\n")
1639         fw("   Access to blenders internal data\n")
1640         fw("\n")
1641         fw("   :type: :class:`bpy.types.BlendData`\n")
1642         fw("\n")
1643         fw(".. literalinclude:: ../examples/bpy.data.py\n")
1644         file.close()
1645
1646         EXAMPLE_SET_USED.add("bpy.data")
1647
1648
1649 def write_rst_importable_modules(basepath):
1650     '''
1651     Write the rst files of importable modules
1652     '''
1653     importable_modules = {
1654         # python_modules
1655         "bpy.path"          : "Path Utilities",
1656         "bpy.utils"         : "Utilities",
1657         "bpy_extras"        : "Extra Utilities",
1658
1659         # C_modules
1660         "aud"               : "Audio System",
1661         "blf"               : "Font Drawing",
1662         "bmesh"             : "BMesh Module",
1663         "bmesh.types"       : "BMesh Types",
1664         "bmesh.utils"       : "BMesh Utilities",
1665         "bpy.app"           : "Application Data",
1666         "bpy.app.handlers"  : "Application Handlers",
1667         "bpy.props"         : "Property Definitions",
1668         "mathutils"         : "Math Types & Utilities",
1669         "mathutils.geometry": "Geometry Utilities",
1670         "mathutils.noise"   : "Noise Utilities",
1671     }
1672     for mod_name, mod_descr in importable_modules.items():
1673         if mod_name not in EXCLUDE_MODULES:
1674             module = __import__(mod_name,
1675                                 fromlist=[mod_name.rsplit(".", 1)[-1]])
1676             pymodule2sphinx(basepath, mod_name, module, mod_descr)
1677
1678
1679 def copy_handwritten_rsts(basepath):
1680
1681     # info docs
1682     if not EXCLUDE_INFO_DOCS:
1683         for info, info_desc in INFO_DOCS:
1684             shutil.copy2(os.path.join(RST_DIR, info), basepath)
1685
1686     # TODO put this docs in blender's code and use import as per modules above
1687     handwritten_modules = [
1688         "bge.types",
1689         "bge.logic",
1690         "bge.render",
1691         "bge.texture",
1692         "bge.events",
1693         "bge.constraints",
1694         "bgl",  # "Blender OpenGl wrapper"
1695         "gpu",  # "GPU Shader Module"
1696
1697         # includes...
1698         "include__bmesh",
1699     ]
1700     for mod_name in handwritten_modules:
1701         if mod_name not in EXCLUDE_MODULES:
1702             # copy2 keeps time/date stamps
1703             shutil.copy2(os.path.join(RST_DIR, "%s.rst" % mod_name), basepath)
1704
1705     # changelog
1706     shutil.copy2(os.path.join(RST_DIR, "change_log.rst"), basepath)
1707
1708
1709 def rna2sphinx(basepath):
1710
1711     try:
1712         os.mkdir(basepath)
1713     except:
1714         pass
1715
1716     # sphinx setup
1717     write_sphinx_conf_py(basepath)
1718
1719     # main page
1720     write_rst_contents(basepath)
1721
1722     # context
1723     if "bpy.context" not in EXCLUDE_MODULES:
1724         # one of a kind, context doc (uses ctypes to extract info!)
1725         # doesn't work on mac
1726         if PLATFORM != "darwin":
1727             pycontext2sphinx(basepath)
1728
1729     # internal modules
1730     write_rst_bpy(basepath)                 # bpy, disabled by default
1731     write_rst_types_index(basepath)         # bpy.types
1732     write_rst_ops_index(basepath)           # bpy.ops
1733     pyrna2sphinx(basepath)                  # bpy.types.* and bpy.ops.*
1734     write_rst_data(basepath)                # bpy.data
1735     write_rst_importable_modules(basepath)
1736
1737     # copy the other rsts
1738     copy_handwritten_rsts(basepath)
1739
1740
1741 def align_sphinx_in_to_sphinx_in_tmp():
1742     '''
1743     Move changed files from SPHINX_IN_TMP to SPHINX_IN
1744     '''
1745     import filecmp
1746
1747     sphinx_in_files = set(os.listdir(SPHINX_IN))
1748     sphinx_in_tmp_files = set(os.listdir(SPHINX_IN_TMP))
1749
1750     # remove deprecated files that have been removed
1751     for f in sorted(sphinx_in_files):
1752         if f not in sphinx_in_tmp_files:
1753             BPY_LOGGER.debug("\tdeprecated: %s" % f)
1754             os.remove(os.path.join(SPHINX_IN, f))
1755
1756     # freshen with new files.
1757     for f in sorted(sphinx_in_tmp_files):
1758         f_from = os.path.join(SPHINX_IN_TMP, f)
1759         f_to = os.path.join(SPHINX_IN, f)
1760
1761         do_copy = True
1762         if f in sphinx_in_files:
1763             if filecmp.cmp(f_from, f_to):
1764                 do_copy = False
1765
1766         if do_copy:
1767             BPY_LOGGER.debug("\tupdating: %s" % f)
1768             shutil.copy(f_from, f_to)
1769
1770
1771 def refactor_sphinx_log(sphinx_logfile):
1772     refactored_log = []
1773     with open(sphinx_logfile, "r", encoding="utf-8") as original_logfile:
1774         lines = set(original_logfile.readlines())
1775         for line in lines:
1776             if 'warning' in line.lower() or 'error' in line.lower():
1777                 line = line.strip().split(None, 2)
1778                 if len(line) == 3:
1779                     location, kind, msg = line
1780                     location = os.path.relpath(location, start=SPHINX_IN)
1781                     refactored_log.append((kind, location, msg))
1782     with open(sphinx_logfile, "w", encoding="utf-8") as refactored_logfile:
1783         for log in sorted(refactored_log):
1784             refactored_logfile.write("%-12s %s\n             %s\n" % log)
1785
1786
1787 def main():
1788
1789     # eventually, create the dirs
1790     for dir_path in [ARGS.output_dir, SPHINX_IN]:
1791         if not os.path.exists(dir_path):
1792             os.mkdir(dir_path)
1793
1794     # eventually, log in files
1795     if ARGS.log:
1796         bpy_logfile = os.path.join(ARGS.output_dir, ".bpy.log")
1797         bpy_logfilehandler = logging.FileHandler(bpy_logfile, mode="w")
1798         bpy_logfilehandler.setLevel(logging.DEBUG)
1799         BPY_LOGGER.addHandler(bpy_logfilehandler)
1800
1801         # using a FileHandler seems to disable the stdout, so we add a StreamHandler
1802         bpy_log_stdout_handler = logging.StreamHandler(stream=sys.stdout)
1803         bpy_log_stdout_handler.setLevel(logging.DEBUG)
1804         BPY_LOGGER.addHandler(bpy_log_stdout_handler)
1805
1806     # in case of out-of-source build, copy the needed dirs
1807     if ARGS.output_dir != SCRIPT_DIR:
1808         # examples dir
1809         examples_dir_copy = os.path.join(ARGS.output_dir, "examples")
1810         if os.path.exists(examples_dir_copy):
1811             shutil.rmtree(examples_dir_copy, True)
1812         shutil.copytree(EXAMPLES_DIR,
1813                         examples_dir_copy,
1814                         ignore=shutil.ignore_patterns(*(".svn",)),
1815                         copy_function=shutil.copy)
1816
1817         # eventually, copy the theme dir
1818         if ARGS.sphinx_theme in SPHINX_THEMES['bf']:
1819             if os.path.exists(SPHINX_THEME_DIR):
1820                 shutil.rmtree(SPHINX_THEME_DIR, True)
1821             shutil.copytree(SPHINX_THEME_SVN_DIR,
1822                             SPHINX_THEME_DIR,
1823                             ignore=shutil.ignore_patterns(*(".svn",)),
1824                             copy_function=shutil.copy)
1825
1826     # dump the api in rst files
1827     if os.path.exists(SPHINX_IN_TMP):
1828         shutil.rmtree(SPHINX_IN_TMP, True)
1829
1830     rna2sphinx(SPHINX_IN_TMP)
1831
1832     if ARGS.full_rebuild:
1833         # only for full updates
1834         shutil.rmtree(SPHINX_IN, True)
1835         shutil.copytree(SPHINX_IN_TMP,
1836                         SPHINX_IN,
1837                         copy_function=shutil.copy)
1838         if ARGS.sphinx_build and os.path.exists(SPHINX_OUT):
1839             shutil.rmtree(SPHINX_OUT, True)
1840         if ARGS.sphinx_build_pdf and os.path.exists(SPHINX_OUT_PDF):
1841             shutil.rmtree(SPHINX_OUT_PDF, True)
1842     else:
1843         # move changed files in SPHINX_IN
1844         align_sphinx_in_to_sphinx_in_tmp()
1845
1846     # report which example files weren't used
1847     EXAMPLE_SET_UNUSED = EXAMPLE_SET - EXAMPLE_SET_USED
1848     if EXAMPLE_SET_UNUSED:
1849         BPY_LOGGER.debug("\nUnused examples found in '%s'..." % EXAMPLES_DIR)
1850         for f in sorted(EXAMPLE_SET_UNUSED):
1851             BPY_LOGGER.debug("    %s.py" % f)
1852         BPY_LOGGER.debug("  %d total\n" % len(EXAMPLE_SET_UNUSED))
1853
1854     # eventually, build the html docs
1855     if ARGS.sphinx_build:
1856         import subprocess
1857         subprocess.call(SPHINX_BUILD)
1858
1859         # sphinx-build log cleanup+sort
1860         if ARGS.log:
1861             if os.stat(SPHINX_BUILD_LOG).st_size:
1862                 refactor_sphinx_log(SPHINX_BUILD_LOG)
1863
1864     # eventually, build the pdf docs
1865     if ARGS.sphinx_build_pdf:
1866         import subprocess
1867         subprocess.call(SPHINX_BUILD_PDF)
1868         subprocess.call(SPHINX_MAKE_PDF, stdout=SPHINX_MAKE_PDF_STDOUT)
1869
1870         # sphinx-build log cleanup+sort
1871         if ARGS.log:
1872             if os.stat(SPHINX_BUILD_PDF_LOG).st_size:
1873                 refactor_sphinx_log(SPHINX_BUILD_PDF_LOG)
1874
1875     # eventually, prepare the dir to be deployed online (REFERENCE_PATH)
1876     if ARGS.pack_reference:
1877
1878         if ARGS.sphinx_build:
1879             # delete REFERENCE_PATH
1880             if os.path.exists(REFERENCE_PATH):
1881                 shutil.rmtree(REFERENCE_PATH, True)
1882
1883             # copy SPHINX_OUT to the REFERENCE_PATH
1884             ignores = ('.doctrees', 'objects.inv', '.buildinfo')
1885             shutil.copytree(SPHINX_OUT,
1886                             REFERENCE_PATH,
1887                             ignore=shutil.ignore_patterns(*ignores))
1888             shutil.copy(os.path.join(REFERENCE_PATH, "contents.html"),
1889                         os.path.join(REFERENCE_PATH, "index.html"))
1890
1891             # zip REFERENCE_PATH
1892             basename = os.path.join(ARGS.output_dir, REFERENCE_NAME)
1893             tmp_path = shutil.make_archive(basename, 'zip',
1894                                            root_dir=ARGS.output_dir,
1895                                            base_dir=REFERENCE_NAME)
1896             final_path = os.path.join(REFERENCE_PATH, BLENDER_ZIP_FILENAME)
1897             os.rename(tmp_path, final_path)
1898
1899         if ARGS.sphinx_build_pdf:
1900             # copy the pdf to REFERENCE_PATH
1901             shutil.copy(os.path.join(SPHINX_OUT_PDF, "contents.pdf"),
1902                         os.path.join(REFERENCE_PATH, BLENDER_PDF_FILENAME))
1903
1904     sys.exit()
1905
1906
1907 if __name__ == '__main__':
1908     main()