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