Part I of the Freestyle branch merger: new 'freestyle' folders.
[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     file_handle = open(filepath)
1487     exec(compile(file_handle.read(), filepath, 'exec'), global_namespace)
1488     file_handle.close()
1489
1490
1491 def write_rst_contents(basepath):
1492     '''
1493     Write the rst file of the main page, needed for sphinx (index.html)
1494     '''
1495     filepath = os.path.join(basepath, "contents.rst")
1496     file = open(filepath, "w", encoding="utf-8")
1497     fw = file.write
1498
1499     fw(title_string("Blender Documentation Contents", "%", double=True))
1500     fw("\n")
1501     fw("Welcome, this document is an API reference for Blender %s, built %s.\n" % (BLENDER_VERSION_DOTS, BLENDER_DATE))
1502     fw("\n")
1503
1504     # fw("`A PDF version of this document is also available <%s>`_\n" % BLENDER_PDF_FILENAME)
1505     fw("`A compressed ZIP file of this site is available <%s>`_\n" % BLENDER_ZIP_FILENAME)
1506
1507     fw("\n")
1508
1509     if not EXCLUDE_INFO_DOCS:
1510         fw(title_string("Blender/Python Documentation", "=", double=True))
1511
1512         fw(".. toctree::\n")
1513         fw("   :maxdepth: 1\n\n")
1514         for info, info_desc in INFO_DOCS:
1515             fw("   %s <%s>\n\n" % (info_desc, info))
1516         fw("\n")
1517
1518     fw(title_string("Application Modules", "=", double=True))
1519     fw(".. toctree::\n")
1520     fw("   :maxdepth: 1\n\n")
1521
1522     app_modules = (
1523         "bpy.context",  # note: not actually a module
1524         "bpy.data",     # note: not actually a module
1525         "bpy.ops",
1526         "bpy.types",
1527
1528         # py modules
1529         "bpy.utils",
1530         "bpy.path",
1531         "bpy.app",
1532         "bpy.app.handlers",
1533
1534         # C modules
1535         "bpy.props",
1536         )
1537
1538     for mod in app_modules:
1539         if mod not in EXCLUDE_MODULES:
1540             fw("   %s\n\n" % mod)
1541
1542     fw(title_string("Standalone Modules", "=", double=True))
1543     fw(".. toctree::\n")
1544     fw("   :maxdepth: 1\n\n")
1545
1546     standalone_modules = (
1547         # mathutils
1548         "mathutils", "mathutils.geometry", "mathutils.noise",
1549         # misc
1550         "bgl", "blf", "gpu", "aud", "bpy_extras",
1551         # bmesh, submodules are in own page
1552         "bmesh",
1553         )
1554
1555     for mod in standalone_modules:
1556         if mod not in EXCLUDE_MODULES:
1557             fw("   %s\n\n" % mod)
1558
1559     # special case, this 'bmesh.ops.rst' is extracted from C source
1560     if "bmesh.ops" not in EXCLUDE_MODULES:
1561         execfile(os.path.join(SCRIPT_DIR, "rst_from_bmesh_opdefines.py"))
1562
1563     # game engine
1564     if "bge" not in EXCLUDE_MODULES:
1565         fw(title_string("Game Engine Modules", "=", double=True))
1566         fw(".. toctree::\n")
1567         fw("   :maxdepth: 1\n\n")
1568         fw("   bge.types.rst\n\n")
1569         fw("   bge.logic.rst\n\n")
1570         fw("   bge.render.rst\n\n")
1571         fw("   bge.texture.rst\n\n")
1572         fw("   bge.events.rst\n\n")
1573         fw("   bge.constraints.rst\n\n")
1574
1575     # rna generated change log
1576     fw(title_string("API Info", "=", double=True))
1577     fw(".. toctree::\n")
1578     fw("   :maxdepth: 1\n\n")
1579     fw("   change_log.rst\n\n")
1580
1581     fw("\n")
1582     fw("\n")
1583     fw(".. note:: The Blender Python API has areas which are still in development.\n")
1584     fw("   \n")
1585     fw("   The following areas are subject to change.\n")
1586     fw("      * operator behavior, names and arguments\n")
1587     fw("      * mesh creation and editing functions\n")
1588     fw("   \n")
1589     fw("   These parts of the API are relatively stable and are unlikely to change significantly\n")
1590     fw("      * data API, access to attributes of blender data such as mesh verts, material color, timeline frames and scene objects\n")
1591     fw("      * user interface functions for defining buttons, creation of menus, headers, panels\n")
1592     fw("      * render engine integration\n")
1593     fw("      * modules: bgl, mathutils & game engine.\n")
1594     fw("\n")
1595
1596     file.close()
1597
1598
1599 def write_rst_bpy(basepath):
1600     '''
1601     Write rst file of bpy module (disabled by default)
1602     '''
1603     if ARGS.bpy:
1604         filepath = os.path.join(basepath, "bpy.rst")
1605         file = open(filepath, "w", encoding="utf-8")
1606         fw = file.write
1607
1608         fw("\n")
1609
1610         title = ":mod:`bpy` --- Blender Python Module"
1611
1612         fw(title_string(title, "="))
1613
1614         fw(".. module:: bpy.types\n\n")
1615         file.close()
1616
1617
1618 def write_rst_types_index(basepath):
1619     '''
1620     Write the rst file of bpy.types module (index)
1621     '''
1622     if "bpy.types" not in EXCLUDE_MODULES:
1623         filepath = os.path.join(basepath, "bpy.types.rst")
1624         file = open(filepath, "w", encoding="utf-8")
1625         fw = file.write
1626         fw(title_string("Types (bpy.types)", "="))
1627         fw(".. toctree::\n")
1628         fw("   :glob:\n\n")
1629         fw("   bpy.types.*\n\n")
1630         file.close()
1631
1632
1633 def write_rst_ops_index(basepath):
1634     '''
1635     Write the rst file of bpy.ops module (index)
1636     '''
1637     if "bpy.ops" not in EXCLUDE_MODULES:
1638         filepath = os.path.join(basepath, "bpy.ops.rst")
1639         file = open(filepath, "w", encoding="utf-8")
1640         fw = file.write
1641         fw(title_string("Operators (bpy.ops)", "="))
1642         write_example_ref("", fw, "bpy.ops")
1643         fw(".. toctree::\n")
1644         fw("   :glob:\n\n")
1645         fw("   bpy.ops.*\n\n")
1646         file.close()
1647
1648
1649 def write_rst_data(basepath):
1650     '''
1651     Write the rst file of bpy.data module
1652     '''
1653     if "bpy.data" not in EXCLUDE_MODULES:
1654         # not actually a module, only write this file so we
1655         # can reference in the TOC
1656         filepath = os.path.join(basepath, "bpy.data.rst")
1657         file = open(filepath, "w", encoding="utf-8")
1658         fw = file.write
1659         fw(title_string("Data Access (bpy.data)", "="))
1660         fw(".. module:: bpy\n")
1661         fw("\n")
1662         fw("This module is used for all blender/python access.\n")
1663         fw("\n")
1664         fw(".. data:: data\n")
1665         fw("\n")
1666         fw("   Access to blenders internal data\n")
1667         fw("\n")
1668         fw("   :type: :class:`bpy.types.BlendData`\n")
1669         fw("\n")
1670         fw(".. literalinclude:: ../examples/bpy.data.py\n")
1671         file.close()
1672
1673         EXAMPLE_SET_USED.add("bpy.data")
1674
1675
1676 def write_rst_importable_modules(basepath):
1677     '''
1678     Write the rst files of importable modules
1679     '''
1680     importable_modules = {
1681         # python_modules
1682         "bpy.path"          : "Path Utilities",
1683         "bpy.utils"         : "Utilities",
1684         "bpy_extras"        : "Extra Utilities",
1685
1686         # C_modules
1687         "aud"               : "Audio System",
1688         "blf"               : "Font Drawing",
1689         "bmesh"             : "BMesh Module",
1690         "bmesh.types"       : "BMesh Types",
1691         "bmesh.utils"       : "BMesh Utilities",
1692         "bpy.app"           : "Application Data",
1693         "bpy.app.handlers"  : "Application Handlers",
1694         "bpy.props"         : "Property Definitions",
1695         "mathutils"         : "Math Types & Utilities",
1696         "mathutils.geometry": "Geometry Utilities",
1697         "mathutils.noise"   : "Noise Utilities",
1698     }
1699     for mod_name, mod_descr in importable_modules.items():
1700         if mod_name not in EXCLUDE_MODULES:
1701             module = __import__(mod_name,
1702                                 fromlist=[mod_name.rsplit(".", 1)[-1]])
1703             pymodule2sphinx(basepath, mod_name, module, mod_descr)
1704
1705
1706 def copy_handwritten_rsts(basepath):
1707
1708     # info docs
1709     if not EXCLUDE_INFO_DOCS:
1710         for info, info_desc in INFO_DOCS:
1711             shutil.copy2(os.path.join(RST_DIR, info), basepath)
1712
1713     # TODO put this docs in blender's code and use import as per modules above
1714     handwritten_modules = [
1715         "bge.logic",
1716         "bge.render",
1717         "bge.texture",
1718         "bge.events",
1719         "bge.constraints",
1720         "bgl",  # "Blender OpenGl wrapper"
1721         "gpu",  # "GPU Shader Module"
1722
1723         "bmesh.ops",  # generated by rst_from_bmesh_opdefines.py
1724
1725         # includes...
1726         "include__bmesh",
1727     ]
1728     for mod_name in handwritten_modules:
1729         if mod_name not in EXCLUDE_MODULES:
1730             # copy2 keeps time/date stamps
1731             shutil.copy2(os.path.join(RST_DIR, "%s.rst" % mod_name), basepath)
1732
1733     if "bge.types" not in EXCLUDE_MODULES:
1734         shutil.copy2(os.path.join(RST_DIR, "bge.types.rst"), basepath)
1735
1736         bge_types_dir = os.path.join(RST_DIR, "bge_types")
1737
1738         for i in os.listdir(bge_types_dir):
1739             shutil.copy2(os.path.join(bge_types_dir, i), basepath)
1740
1741     # changelog
1742     shutil.copy2(os.path.join(RST_DIR, "change_log.rst"), basepath)
1743
1744     # copy images, could be smarter but just glob for now.
1745     for f in os.listdir(RST_DIR):
1746         if f.endswith(".png"):
1747             shutil.copy2(os.path.join(RST_DIR, f), basepath)
1748
1749
1750 def rna2sphinx(basepath):
1751
1752     try:
1753         os.mkdir(basepath)
1754     except:
1755         pass
1756
1757     # sphinx setup
1758     write_sphinx_conf_py(basepath)
1759
1760     # main page
1761     write_rst_contents(basepath)
1762
1763     # context
1764     if "bpy.context" not in EXCLUDE_MODULES:
1765         # one of a kind, context doc (uses ctypes to extract info!)
1766         # doesn't work on mac
1767         if PLATFORM != "darwin":
1768             pycontext2sphinx(basepath)
1769
1770     # internal modules
1771     write_rst_bpy(basepath)                 # bpy, disabled by default
1772     write_rst_types_index(basepath)         # bpy.types
1773     write_rst_ops_index(basepath)           # bpy.ops
1774     pyrna2sphinx(basepath)                  # bpy.types.* and bpy.ops.*
1775     write_rst_data(basepath)                # bpy.data
1776     write_rst_importable_modules(basepath)
1777
1778     # copy the other rsts
1779     copy_handwritten_rsts(basepath)
1780
1781
1782 def align_sphinx_in_to_sphinx_in_tmp():
1783     '''
1784     Move changed files from SPHINX_IN_TMP to SPHINX_IN
1785     '''
1786     import filecmp
1787
1788     sphinx_in_files = set(os.listdir(SPHINX_IN))
1789     sphinx_in_tmp_files = set(os.listdir(SPHINX_IN_TMP))
1790
1791     # remove deprecated files that have been removed
1792     for f in sorted(sphinx_in_files):
1793         if f not in sphinx_in_tmp_files:
1794             BPY_LOGGER.debug("\tdeprecated: %s" % f)
1795             os.remove(os.path.join(SPHINX_IN, f))
1796
1797     # freshen with new files.
1798     for f in sorted(sphinx_in_tmp_files):
1799         f_from = os.path.join(SPHINX_IN_TMP, f)
1800         f_to = os.path.join(SPHINX_IN, f)
1801
1802         do_copy = True
1803         if f in sphinx_in_files:
1804             if filecmp.cmp(f_from, f_to):
1805                 do_copy = False
1806
1807         if do_copy:
1808             BPY_LOGGER.debug("\tupdating: %s" % f)
1809             shutil.copy(f_from, f_to)
1810
1811
1812 def refactor_sphinx_log(sphinx_logfile):
1813     refactored_log = []
1814     with open(sphinx_logfile, "r", encoding="utf-8") as original_logfile:
1815         lines = set(original_logfile.readlines())
1816         for line in lines:
1817             if 'warning' in line.lower() or 'error' in line.lower():
1818                 line = line.strip().split(None, 2)
1819                 if len(line) == 3:
1820                     location, kind, msg = line
1821                     location = os.path.relpath(location, start=SPHINX_IN)
1822                     refactored_log.append((kind, location, msg))
1823     with open(sphinx_logfile, "w", encoding="utf-8") as refactored_logfile:
1824         for log in sorted(refactored_log):
1825             refactored_logfile.write("%-12s %s\n             %s\n" % log)
1826
1827
1828 def monkey_patch():
1829     filepath = os.path.join(SCRIPT_DIR, "sphinx_doc_gen_monkeypatch.py")
1830     global_namespace = {"__file__": filepath, "__name__": "__main__"}
1831     file = open(filepath, 'rb')
1832     exec(compile(file.read(), filepath, 'exec'), global_namespace)
1833     file.close()
1834
1835
1836 def main():
1837
1838     # first monkey patch to load in fake members
1839     monkey_patch()
1840
1841     # eventually, create the dirs
1842     for dir_path in [ARGS.output_dir, SPHINX_IN]:
1843         if not os.path.exists(dir_path):
1844             os.mkdir(dir_path)
1845
1846     # eventually, log in files
1847     if ARGS.log:
1848         bpy_logfile = os.path.join(ARGS.output_dir, ".bpy.log")
1849         bpy_logfilehandler = logging.FileHandler(bpy_logfile, mode="w")
1850         bpy_logfilehandler.setLevel(logging.DEBUG)
1851         BPY_LOGGER.addHandler(bpy_logfilehandler)
1852
1853         # using a FileHandler seems to disable the stdout, so we add a StreamHandler
1854         bpy_log_stdout_handler = logging.StreamHandler(stream=sys.stdout)
1855         bpy_log_stdout_handler.setLevel(logging.DEBUG)
1856         BPY_LOGGER.addHandler(bpy_log_stdout_handler)
1857
1858     # in case of out-of-source build, copy the needed dirs
1859     if ARGS.output_dir != SCRIPT_DIR:
1860         # examples dir
1861         examples_dir_copy = os.path.join(ARGS.output_dir, "examples")
1862         if os.path.exists(examples_dir_copy):
1863             shutil.rmtree(examples_dir_copy, True)
1864         shutil.copytree(EXAMPLES_DIR,
1865                         examples_dir_copy,
1866                         ignore=shutil.ignore_patterns(*(".svn",)),
1867                         copy_function=shutil.copy)
1868
1869         # eventually, copy the theme dir
1870         if ARGS.sphinx_theme in SPHINX_THEMES['bf']:
1871             if os.path.exists(SPHINX_THEME_DIR):
1872                 shutil.rmtree(SPHINX_THEME_DIR, True)
1873             shutil.copytree(SPHINX_THEME_SVN_DIR,
1874                             SPHINX_THEME_DIR,
1875                             ignore=shutil.ignore_patterns(*(".svn",)),
1876                             copy_function=shutil.copy)
1877
1878     # dump the api in rst files
1879     if os.path.exists(SPHINX_IN_TMP):
1880         shutil.rmtree(SPHINX_IN_TMP, True)
1881
1882     rna2sphinx(SPHINX_IN_TMP)
1883
1884     if ARGS.full_rebuild:
1885         # only for full updates
1886         shutil.rmtree(SPHINX_IN, True)
1887         shutil.copytree(SPHINX_IN_TMP,
1888                         SPHINX_IN,
1889                         copy_function=shutil.copy)
1890         if ARGS.sphinx_build and os.path.exists(SPHINX_OUT):
1891             shutil.rmtree(SPHINX_OUT, True)
1892         if ARGS.sphinx_build_pdf and os.path.exists(SPHINX_OUT_PDF):
1893             shutil.rmtree(SPHINX_OUT_PDF, True)
1894     else:
1895         # move changed files in SPHINX_IN
1896         align_sphinx_in_to_sphinx_in_tmp()
1897
1898     # report which example files weren't used
1899     EXAMPLE_SET_UNUSED = EXAMPLE_SET - EXAMPLE_SET_USED
1900     if EXAMPLE_SET_UNUSED:
1901         BPY_LOGGER.debug("\nUnused examples found in '%s'..." % EXAMPLES_DIR)
1902         for f in sorted(EXAMPLE_SET_UNUSED):
1903             BPY_LOGGER.debug("    %s.py" % f)
1904         BPY_LOGGER.debug("  %d total\n" % len(EXAMPLE_SET_UNUSED))
1905
1906     # eventually, build the html docs
1907     if ARGS.sphinx_build:
1908         import subprocess
1909         subprocess.call(SPHINX_BUILD)
1910
1911         # sphinx-build log cleanup+sort
1912         if ARGS.log:
1913             if os.stat(SPHINX_BUILD_LOG).st_size:
1914                 refactor_sphinx_log(SPHINX_BUILD_LOG)
1915
1916     # eventually, build the pdf docs
1917     if ARGS.sphinx_build_pdf:
1918         import subprocess
1919         subprocess.call(SPHINX_BUILD_PDF)
1920         subprocess.call(SPHINX_MAKE_PDF, stdout=SPHINX_MAKE_PDF_STDOUT)
1921
1922         # sphinx-build log cleanup+sort
1923         if ARGS.log:
1924             if os.stat(SPHINX_BUILD_PDF_LOG).st_size:
1925                 refactor_sphinx_log(SPHINX_BUILD_PDF_LOG)
1926
1927     # eventually, prepare the dir to be deployed online (REFERENCE_PATH)
1928     if ARGS.pack_reference:
1929
1930         if ARGS.sphinx_build:
1931             # delete REFERENCE_PATH
1932             if os.path.exists(REFERENCE_PATH):
1933                 shutil.rmtree(REFERENCE_PATH, True)
1934
1935             # copy SPHINX_OUT to the REFERENCE_PATH
1936             ignores = ('.doctrees', 'objects.inv', '.buildinfo')
1937             shutil.copytree(SPHINX_OUT,
1938                             REFERENCE_PATH,
1939                             ignore=shutil.ignore_patterns(*ignores))
1940             shutil.copy(os.path.join(REFERENCE_PATH, "contents.html"),
1941                         os.path.join(REFERENCE_PATH, "index.html"))
1942
1943             # zip REFERENCE_PATH
1944             basename = os.path.join(ARGS.output_dir, REFERENCE_NAME)
1945             tmp_path = shutil.make_archive(basename, 'zip',
1946                                            root_dir=ARGS.output_dir,
1947                                            base_dir=REFERENCE_NAME)
1948             final_path = os.path.join(REFERENCE_PATH, BLENDER_ZIP_FILENAME)
1949             os.rename(tmp_path, final_path)
1950
1951         if ARGS.sphinx_build_pdf:
1952             # copy the pdf to REFERENCE_PATH
1953             shutil.copy(os.path.join(SPHINX_OUT_PDF, "contents.pdf"),
1954                         os.path.join(REFERENCE_PATH, BLENDER_PDF_FILENAME))
1955
1956     sys.exit()
1957
1958
1959 if __name__ == '__main__':
1960     main()