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