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