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