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