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