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