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