Merged changes in the trunk up to revision 52815.
[blender-staging.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     arg_str = inspect.formatargspec(*inspect.getargspec(py_func))
618
619     if not is_class:
620         func_type = "function"
621
622         # ther rest are class methods
623     elif arg_str.startswith("(self, "):
624         arg_str = "(" + arg_str[7:]
625         func_type = "method"
626     elif arg_str.startswith("(cls, "):
627         arg_str = "(" + arg_str[6:]
628         func_type = "classmethod"
629     else:
630         func_type = "staticmethod"
631
632     fw(ident + ".. %s:: %s%s\n\n" % (func_type, identifier, arg_str))
633     if py_func.__doc__:
634         write_indented_lines(ident + "   ", fw, py_func.__doc__)
635         fw("\n")
636
637
638 def py_descr2sphinx(ident, fw, descr, module_name, type_name, identifier):
639     if identifier.startswith("_"):
640         return
641
642     doc = descr.__doc__
643     if not doc:
644         doc = undocumented_message(module_name, type_name, identifier)
645
646     if type(descr) == GetSetDescriptorType:
647         fw(ident + ".. attribute:: %s\n\n" % identifier)
648         write_indented_lines(ident + "   ", fw, doc, False)
649         fw("\n")
650     elif type(descr) == MemberDescriptorType:  # same as above but use 'data'
651         fw(ident + ".. data:: %s\n\n" % identifier)
652         write_indented_lines(ident + "   ", fw, doc, False)
653         fw("\n")
654     elif type(descr) in (MethodDescriptorType, ClassMethodDescriptorType):
655         write_indented_lines(ident, fw, doc, False)
656         fw("\n")
657     else:
658         raise TypeError("type was not GetSetDescriptorType, MethodDescriptorType or ClassMethodDescriptorType")
659
660     write_example_ref(ident + "   ", fw, module_name + "." + type_name + "." + identifier)
661     fw("\n")
662
663
664 def py_c_func2sphinx(ident, fw, module_name, type_name, identifier, py_func, is_class=True):
665     '''
666     c defined function to sphinx.
667     '''
668
669     # dump the docstring, assume its formatted correctly
670     if py_func.__doc__:
671         write_indented_lines(ident, fw, py_func.__doc__, False)
672         fw("\n")
673     else:
674         fw(ident + ".. function:: %s()\n\n" % identifier)
675         fw(ident + "   " + undocumented_message(module_name, type_name, identifier))
676
677     if is_class:
678         write_example_ref(ident + "   ", fw, module_name + "." + type_name + "." + identifier)
679     else:
680         write_example_ref(ident + "   ", fw, module_name + "." + identifier)
681
682     fw("\n")
683
684
685 def pyprop2sphinx(ident, fw, identifier, py_prop):
686     '''
687     python property to sphinx
688     '''
689     # readonly properties use "data" directive, variables use "attribute" directive
690     if py_prop.fset is None:
691         fw(ident + ".. data:: %s\n\n" % identifier)
692     else:
693         fw(ident + ".. attribute:: %s\n\n" % identifier)
694     write_indented_lines(ident + "   ", fw, py_prop.__doc__)
695     if py_prop.fset is None:
696         fw(ident + "   (readonly)\n\n")
697
698
699 def pymodule2sphinx(basepath, module_name, module, title):
700     import types
701     attribute_set = set()
702     filepath = os.path.join(basepath, module_name + ".rst")
703
704     module_all = getattr(module, "__all__", None)
705     module_dir = sorted(dir(module))
706
707     if module_all:
708         module_dir = module_all
709
710     # TODO - currently only used for classes
711     # grouping support
712     module_grouping = MODULE_GROUPING.get(module_name)
713
714     def module_grouping_index(name):
715         if module_grouping is not None:
716             try:
717                 return module_grouping.index(name)
718             except ValueError:
719                 pass
720         return -1
721
722     def module_grouping_heading(name):
723         if module_grouping is not None:
724             i = module_grouping_index(name) - 1
725             if i >= 0 and type(module_grouping[i]) == tuple:
726                 return module_grouping[i]
727         return None, None
728
729     def module_grouping_sort_key(name):
730         return module_grouping_index(name)
731     # done grouping support
732
733     file = open(filepath, "w", encoding="utf-8")
734
735     fw = file.write
736
737     fw(title_string("%s (%s)" % (title, module_name), "="))
738
739     fw(".. module:: %s\n\n" % module_name)
740
741     if module.__doc__:
742         # Note, may contain sphinx syntax, dont mangle!
743         fw(module.__doc__.strip())
744         fw("\n\n")
745
746     write_example_ref("", fw, module_name)
747
748     # write submodules
749     # we could also scan files but this ensures __all__ is used correctly
750     if module_all is not None:
751         submod_name = None
752         submod = None
753         submod_ls = []
754         for submod_name in module_all:
755             ns = {}
756             exec_str = "from %s import %s as submod" % (module.__name__, submod_name)
757             exec(exec_str, ns, ns)
758             submod = ns["submod"]
759             if type(submod) == types.ModuleType:
760                 submod_ls.append((submod_name, submod))
761
762         del submod_name
763         del submod
764
765         if submod_ls:
766             fw(".. toctree::\n")
767             fw("   :maxdepth: 1\n\n")
768
769             for submod_name, submod in submod_ls:
770                 submod_name_full = "%s.%s" % (module_name, submod_name)
771                 fw("   %s.rst\n\n" % submod_name_full)
772
773                 pymodule2sphinx(basepath, submod_name_full, submod, "%s submodule" % module_name)
774         del submod_ls
775     # done writing submodules!
776
777     # write members of the module
778     # only tested with PyStructs which are not exactly modules
779     for key, descr in sorted(type(module).__dict__.items()):
780         if key.startswith("__"):
781             continue
782         # naughty, we also add getset's into PyStructs, this is not typical py but also not incorrect.
783
784         # type_name is only used for examples and messages
785         type_name = str(type(module)).strip("<>").split(" ", 1)[-1][1:-1]  # "<class 'bpy.app.handlers'>" --> bpy.app.handlers
786         if type(descr) == types.GetSetDescriptorType:
787             py_descr2sphinx("", fw, descr, module_name, type_name, key)
788             attribute_set.add(key)
789     descr_sorted = []
790     for key, descr in sorted(type(module).__dict__.items()):
791         if key.startswith("__"):
792             continue
793
794         if type(descr) == MemberDescriptorType:
795             if descr.__doc__:
796                 value = getattr(module, key, None)
797
798                 value_type = type(value)
799                 descr_sorted.append((key, descr, value, type(value)))
800     # sort by the valye type
801     descr_sorted.sort(key=lambda descr_data: str(descr_data[3]))
802     for key, descr, value, value_type in descr_sorted:
803
804         # must be documented as a submodule
805         if is_struct_seq(value):
806             continue
807
808         type_name = value_type.__name__
809         py_descr2sphinx("", fw, descr, module_name, type_name, key)
810
811         attribute_set.add(key)
812
813     del key, descr, descr_sorted
814
815     classes = []
816     submodules = []
817
818     # use this list so we can sort by type
819     module_dir_value_type = []
820
821     for attribute in module_dir:
822         if attribute.startswith("_"):
823             continue
824
825         if attribute in attribute_set:
826             continue
827
828         if attribute.startswith("n_"):  # annoying exception, needed for bpy.app
829             continue
830
831         # workaround for bpy.app documenting .index() and .count()
832         if isinstance(module, tuple) and hasattr(tuple, attribute):
833             continue
834
835         value = getattr(module, attribute)
836
837         module_dir_value_type.append((attribute, value, type(value)))
838
839     # sort by str of each type
840     # this way lists, functions etc are grouped.
841     module_dir_value_type.sort(key=lambda triple: str(triple[2]))
842
843     for attribute, value, value_type in module_dir_value_type:
844         if value_type == types.FunctionType:
845             pyfunc2sphinx("", fw, attribute, value, is_class=False)
846         elif value_type in (types.BuiltinMethodType, types.BuiltinFunctionType):  # both the same at the moment but to be future proof
847             # note: can't get args from these, so dump the string as is
848             # this means any module used like this must have fully formatted docstrings.
849             py_c_func2sphinx("", fw, module_name, None, attribute, value, is_class=False)
850         elif value_type == type:
851             classes.append((attribute, value))
852         elif issubclass(value_type, types.ModuleType):
853             submodules.append((attribute, value))
854         elif value_type in (bool, int, float, str, tuple):
855             # constant, not much fun we can do here except to list it.
856             # TODO, figure out some way to document these!
857             fw(".. data:: %s\n\n" % attribute)
858             write_indented_lines("   ", fw, "constant value %s" % repr(value), False)
859             fw("\n")
860         else:
861             BPY_LOGGER.debug("\tnot documenting %s.%s of %r type" % (module_name, attribute, value_type.__name__))
862             continue
863
864         attribute_set.add(attribute)
865         # TODO, more types...
866     del module_dir_value_type
867
868     # TODO, bpy_extras does this already, mathutils not.
869     '''
870     if submodules:
871         fw("\n"
872            "**********\n"
873            "Submodules\n"
874            "**********\n"
875            "\n"
876            )
877         for attribute, submod in submodules:
878             fw("* :mod:`%s.%s`\n" % (module_name, attribute))
879         fw("\n")
880     '''
881
882     if module_grouping is not None:
883         classes.sort(key=lambda pair: module_grouping_sort_key(pair[0]))
884
885     # write collected classes now
886     for (type_name, value) in classes:
887
888         if module_grouping is not None:
889             heading, heading_char = module_grouping_heading(type_name)
890             if heading:
891                 fw(title_string(heading, heading_char))
892
893         # May need to be its own function
894         fw(".. class:: %s\n\n" % type_name)
895         if value.__doc__:
896             write_indented_lines("   ", fw, value.__doc__, False)
897             fw("\n")
898         write_example_ref("   ", fw, module_name + "." + type_name)
899
900         descr_items = [(key, descr) for key, descr in sorted(value.__dict__.items()) if not key.startswith("__")]
901
902         for key, descr in descr_items:
903             if type(descr) == ClassMethodDescriptorType:
904                 py_descr2sphinx("   ", fw, descr, module_name, type_name, key)
905
906         for key, descr in descr_items:
907             if type(descr) == MethodDescriptorType:
908                 py_descr2sphinx("   ", fw, descr, module_name, type_name, key)
909
910         for key, descr in descr_items:
911             if type(descr) == GetSetDescriptorType:
912                 py_descr2sphinx("   ", fw, descr, module_name, type_name, key)
913
914         for key, descr in descr_items:
915             if type(descr) == StaticMethodType:
916                 descr = getattr(value, key)
917                 write_indented_lines("   ", fw, descr.__doc__ or "Undocumented", False)
918                 fw("\n")
919
920         fw("\n\n")
921
922     file.close()
923
924 # Changes in blender will force errors here
925 context_type_map = {
926     "active_base": ("ObjectBase", False),
927     "active_bone": ("EditBone", False),
928     "active_object": ("Object", False),
929     "active_operator": ("Operator", False),
930     "active_pose_bone": ("PoseBone", False),
931     "active_node": ("Node", False),
932     "armature": ("Armature", False),
933     "bone": ("Bone", False),
934     "brush": ("Brush", False),
935     "camera": ("Camera", False),
936     "cloth": ("ClothModifier", False),
937     "collision": ("CollisionModifier", False),
938     "curve": ("Curve", False),
939     "dynamic_paint": ("DynamicPaintModifier", False),
940     "edit_bone": ("EditBone", False),
941     "edit_image": ("Image", False),
942     "edit_mask": ("Mask", False),
943     "edit_movieclip": ("MovieClip", False),
944     "edit_object": ("Object", False),
945     "edit_text": ("Text", False),
946     "editable_bones": ("EditBone", True),
947     "fluid": ("FluidSimulationModifier", False),
948     "image_paint_object": ("Object", False),
949     "lamp": ("Lamp", False),
950     "lattice": ("Lattice", False),
951     "material": ("Material", False),
952     "material_slot": ("MaterialSlot", False),
953     "mesh": ("Mesh", False),
954     "meta_ball": ("MetaBall", False),
955     "object": ("Object", False),
956     "particle_edit_object": ("Object", False),
957     "particle_settings": ("ParticleSettings", False),
958     "particle_system": ("ParticleSystem", False),
959     "particle_system_editable": ("ParticleSystem", False),
960     "pose_bone": ("PoseBone", False),
961     "scene": ("Scene", False),
962     "sculpt_object": ("Object", False),
963     "selectable_bases": ("ObjectBase", True),
964     "selectable_objects": ("Object", True),
965     "selected_bases": ("ObjectBase", True),
966     "selected_bones": ("EditBone", True),
967     "selected_editable_bases": ("ObjectBase", True),
968     "selected_editable_bones": ("EditBone", True),
969     "selected_editable_objects": ("Object", True),
970     "selected_editable_sequences": ("Sequence", True),
971     "selected_nodes": ("Node", True),
972     "selected_objects": ("Object", True),
973     "selected_pose_bones": ("PoseBone", True),
974     "selected_sequences": ("Sequence", True),
975     "sequences": ("Sequence", True),
976     "smoke": ("SmokeModifier", False),
977     "soft_body": ("SoftBodyModifier", False),
978     "speaker": ("Speaker", False),
979     "texture": ("Texture", False),
980     "texture_slot": ("MaterialTextureSlot", False),
981     "texture_user": ("ID", False),
982     "vertex_paint_object": ("Object", False),
983     "visible_bases": ("ObjectBase", True),
984     "visible_bones": ("EditBone", True),
985     "visible_objects": ("Object", True),
986     "visible_pose_bones": ("PoseBone", True),
987     "weight_paint_object": ("Object", False),
988     "world": ("World", False),
989 }
990
991 def pycontext2sphinx(basepath):
992     # Only use once. very irregular
993
994     filepath = os.path.join(basepath, "bpy.context.rst")
995     file = open(filepath, "w", encoding="utf-8")
996     fw = file.write
997     fw(title_string("Context Access (bpy.context)", "="))
998     fw(".. module:: bpy.context\n")
999     fw("\n")
1000     fw("The context members available depend on the area of blender which is currently being accessed.\n")
1001     fw("\n")
1002     fw("Note that all context values are readonly, but may be modified through the data api or by running operators\n\n")
1003
1004     # nasty, get strings directly from blender because there is no other way to get it
1005     import ctypes
1006
1007     context_strings = (
1008         "screen_context_dir",
1009         "view3d_context_dir",
1010         "buttons_context_dir",
1011         "image_context_dir",
1012         "node_context_dir",
1013         "text_context_dir",
1014         "clip_context_dir",
1015         "sequencer_context_dir",
1016     )
1017
1018
1019     unique = set()
1020     blend_cdll = ctypes.CDLL("")
1021     for ctx_str in context_strings:
1022         subsection = "%s Context" % ctx_str.split("_")[0].title()
1023         fw("\n%s\n%s\n\n" % (subsection, (len(subsection) * '-')))
1024
1025         attr = ctypes.addressof(getattr(blend_cdll, ctx_str))
1026         c_char_p_p = ctypes.POINTER(ctypes.c_char_p)
1027         char_array = c_char_p_p.from_address(attr)
1028         i = 0
1029         while char_array[i] is not None:
1030             member = ctypes.string_at(char_array[i]).decode(encoding="ascii")
1031             fw(".. data:: %s\n\n" % member)
1032             member_type, is_seq = context_type_map[member]
1033             fw("   :type: %s :class:`bpy.types.%s`\n\n" % ("sequence of " if is_seq else "", member_type))
1034             unique.add(member)
1035             i += 1
1036
1037     # generate typemap...
1038     # for member in sorted(unique):
1039     #     print('        "%s": ("", False),' % member)
1040     if len(context_type_map) > len(unique):
1041         raise Exception("Some types are not used: %s" % str([member for member in context_type_map if member not in unique]))
1042     else:
1043         pass  # will have raised an error above
1044
1045     file.close()
1046
1047
1048 def pyrna_enum2sphinx(prop, use_empty_descriptions=False):
1049     """ write a bullet point list of enum + descrptons
1050     """
1051
1052     if use_empty_descriptions:
1053         ok = True
1054     else:
1055         ok = False
1056         for identifier, name, description in prop.enum_items:
1057             if description:
1058                 ok = True
1059                 break
1060
1061     if ok:
1062         return "".join(["* ``%s`` %s.\n" %
1063                         (identifier,
1064                          ", ".join(val for val in (name, description) if val),
1065                          )
1066                         for identifier, name, description in prop.enum_items
1067                         ])
1068     else:
1069         return ""
1070
1071
1072 def pyrna2sphinx(basepath):
1073     """ bpy.types and bpy.ops
1074     """
1075     structs, funcs, ops, props = rna_info.BuildRNAInfo()
1076     if FILTER_BPY_TYPES is not None:
1077         structs = {k: v for k, v in structs.items() if k[1] in FILTER_BPY_TYPES}
1078
1079     if FILTER_BPY_OPS is not None:
1080         ops = {k: v for k, v in ops.items() if v.module_name in FILTER_BPY_OPS}
1081
1082     def write_param(ident, fw, prop, is_return=False):
1083         if is_return:
1084             id_name = "return"
1085             id_type = "rtype"
1086             kwargs = {"as_ret": True}
1087             identifier = ""
1088         else:
1089             id_name = "arg"
1090             id_type = "type"
1091             kwargs = {"as_arg": True}
1092             identifier = " %s" % prop.identifier
1093
1094         kwargs["class_fmt"] = ":class:`%s`"
1095
1096         kwargs["collection_id"] = _BPY_PROP_COLLECTION_ID
1097
1098         type_descr = prop.get_type_description(**kwargs)
1099
1100         enum_text = pyrna_enum2sphinx(prop)
1101
1102         if prop.name or prop.description or enum_text:
1103             fw(ident + ":%s%s:\n\n" % (id_name, identifier))
1104
1105             if prop.name or prop.description:
1106                 fw(ident + "   " + ", ".join(val for val in (prop.name, prop.description) if val) + "\n\n")
1107
1108             # special exception, cant use genric code here for enums
1109             if enum_text:
1110                 write_indented_lines(ident + "   ", fw, enum_text)
1111                 fw("\n")
1112             del enum_text
1113             # end enum exception
1114
1115         fw(ident + ":%s%s: %s\n" % (id_type, identifier, type_descr))
1116
1117     def write_struct(struct):
1118         #if not struct.identifier.startswith("Sc") and not struct.identifier.startswith("I"):
1119         #    return
1120
1121         #if not struct.identifier == "Object":
1122         #    return
1123
1124         filepath = os.path.join(basepath, "bpy.types.%s.rst" % struct.identifier)
1125         file = open(filepath, "w", encoding="utf-8")
1126         fw = file.write
1127
1128         base_id = getattr(struct.base, "identifier", "")
1129         struct_id = struct.identifier
1130
1131         if _BPY_STRUCT_FAKE:
1132             if not base_id:
1133                 base_id = _BPY_STRUCT_FAKE
1134
1135         if base_id:
1136             title = "%s(%s)" % (struct_id, base_id)
1137         else:
1138             title = struct_id
1139
1140         fw(title_string(title, "="))
1141
1142         fw(".. module:: bpy.types\n\n")
1143
1144         # docs first?, ok
1145         write_example_ref("", fw, "bpy.types.%s" % struct_id)
1146
1147         base_ids = [base.identifier for base in struct.get_bases()]
1148
1149         if _BPY_STRUCT_FAKE:
1150             base_ids.append(_BPY_STRUCT_FAKE)
1151
1152         base_ids.reverse()
1153
1154         if base_ids:
1155             if len(base_ids) > 1:
1156                 fw("base classes --- ")
1157             else:
1158                 fw("base class --- ")
1159
1160             fw(", ".join((":class:`%s`" % base_id) for base_id in base_ids))
1161             fw("\n\n")
1162
1163         subclass_ids = [s.identifier for s in structs.values() if s.base is struct if not rna_info.rna_id_ignore(s.identifier)]
1164         if subclass_ids:
1165             fw("subclasses --- \n" + ", ".join((":class:`%s`" % s) for s in subclass_ids) + "\n\n")
1166
1167         base_id = getattr(struct.base, "identifier", "")
1168
1169         if _BPY_STRUCT_FAKE:
1170             if not base_id:
1171                 base_id = _BPY_STRUCT_FAKE
1172
1173         if base_id:
1174             fw(".. class:: %s(%s)\n\n" % (struct_id, base_id))
1175         else:
1176             fw(".. class:: %s\n\n" % struct_id)
1177
1178         fw("   %s\n\n" % struct.description)
1179
1180         # properties sorted in alphabetical order
1181         sorted_struct_properties = struct.properties[:]
1182         sorted_struct_properties.sort(key=lambda prop: prop.identifier)
1183
1184         # support blacklisting props
1185         struct_blacklist = RNA_BLACKLIST.get(struct_id, ())
1186
1187         for prop in sorted_struct_properties:
1188
1189             # support blacklisting props
1190             if prop.identifier in struct_blacklist:
1191                 continue
1192
1193             type_descr = prop.get_type_description(class_fmt=":class:`%s`", collection_id=_BPY_PROP_COLLECTION_ID)
1194             # readonly properties use "data" directive, variables properties use "attribute" directive
1195             if 'readonly' in type_descr:
1196                 fw("   .. data:: %s\n\n" % prop.identifier)
1197             else:
1198                 fw("   .. attribute:: %s\n\n" % prop.identifier)
1199             if prop.description:
1200                 fw("      %s\n\n" % prop.description)
1201
1202             # special exception, cant use genric code here for enums
1203             if prop.type == "enum":
1204                 enum_text = pyrna_enum2sphinx(prop)
1205                 if enum_text:
1206                     write_indented_lines("      ", fw, enum_text)
1207                     fw("\n")
1208                 del enum_text
1209             # end enum exception
1210
1211             fw("      :type: %s\n\n" % type_descr)
1212
1213         # python attributes
1214         py_properties = struct.get_py_properties()
1215         py_prop = None
1216         for identifier, py_prop in py_properties:
1217             pyprop2sphinx("   ", fw, identifier, py_prop)
1218         del py_properties, py_prop
1219
1220         for func in struct.functions:
1221             args_str = ", ".join(prop.get_arg_default(force=False) for prop in func.args)
1222
1223             fw("   .. %s:: %s(%s)\n\n" % ("classmethod" if func.is_classmethod else "method", func.identifier, args_str))
1224             fw("      %s\n\n" % func.description)
1225
1226             for prop in func.args:
1227                 write_param("      ", fw, prop)
1228
1229             if len(func.return_values) == 1:
1230                 write_param("      ", fw, func.return_values[0], is_return=True)
1231             elif func.return_values:  # multiple return values
1232                 fw("      :return (%s):\n" % ", ".join(prop.identifier for prop in func.return_values))
1233                 for prop in func.return_values:
1234                     # TODO, pyrna_enum2sphinx for multiple return values... actually dont think we even use this but still!!!
1235                     type_descr = prop.get_type_description(as_ret=True, class_fmt=":class:`%s`", collection_id=_BPY_PROP_COLLECTION_ID)
1236                     descr = prop.description
1237                     if not descr:
1238                         descr = prop.name
1239                     fw("         `%s`, %s, %s\n\n" % (prop.identifier, descr, type_descr))
1240
1241             write_example_ref("      ", fw, "bpy.types." + struct_id + "." + func.identifier)
1242
1243             fw("\n")
1244
1245         # python methods
1246         py_funcs = struct.get_py_functions()
1247         py_func = None
1248
1249         for identifier, py_func in py_funcs:
1250             pyfunc2sphinx("   ", fw, identifier, py_func, is_class=True)
1251         del py_funcs, py_func
1252
1253         py_funcs = struct.get_py_c_functions()
1254         py_func = None
1255
1256         for identifier, py_func in py_funcs:
1257             py_c_func2sphinx("   ", fw, "bpy.types", struct_id, identifier, py_func, is_class=True)
1258
1259         lines = []
1260
1261         if struct.base or _BPY_STRUCT_FAKE:
1262             bases = list(reversed(struct.get_bases()))
1263
1264             # props
1265             del lines[:]
1266
1267             if _BPY_STRUCT_FAKE:
1268                 descr_items = [(key, descr) for key, descr in sorted(bpy.types.Struct.__bases__[0].__dict__.items()) if not key.startswith("__")]
1269
1270             if _BPY_STRUCT_FAKE:
1271                 for key, descr in descr_items:
1272                     if type(descr) == GetSetDescriptorType:
1273                         lines.append("   * :class:`%s.%s`\n" % (_BPY_STRUCT_FAKE, key))
1274
1275             for base in bases:
1276                 for prop in base.properties:
1277                     lines.append("   * :class:`%s.%s`\n" % (base.identifier, prop.identifier))
1278
1279                 for identifier, py_prop in base.get_py_properties():
1280                     lines.append("   * :class:`%s.%s`\n" % (base.identifier, identifier))
1281
1282                 for identifier, py_prop in base.get_py_properties():
1283                     lines.append("   * :class:`%s.%s`\n" % (base.identifier, identifier))
1284
1285             if lines:
1286                 fw(".. rubric:: Inherited Properties\n\n")
1287
1288                 fw(".. hlist::\n")
1289                 fw("   :columns: 2\n\n")
1290
1291                 for line in lines:
1292                     fw(line)
1293                 fw("\n")
1294
1295             # funcs
1296             del lines[:]
1297
1298             if _BPY_STRUCT_FAKE:
1299                 for key, descr in descr_items:
1300                     if type(descr) == MethodDescriptorType:
1301                         lines.append("   * :class:`%s.%s`\n" % (_BPY_STRUCT_FAKE, key))
1302
1303             for base in bases:
1304                 for func in base.functions:
1305                     lines.append("   * :class:`%s.%s`\n" % (base.identifier, func.identifier))
1306                 for identifier, py_func in base.get_py_functions():
1307                     lines.append("   * :class:`%s.%s`\n" % (base.identifier, identifier))
1308
1309             if lines:
1310                 fw(".. rubric:: Inherited Functions\n\n")
1311
1312                 fw(".. hlist::\n")
1313                 fw("   :columns: 2\n\n")
1314
1315                 for line in lines:
1316                     fw(line)
1317                 fw("\n")
1318
1319             del lines[:]
1320
1321         if struct.references:
1322             # use this otherwise it gets in the index for a normal heading.
1323             fw(".. rubric:: References\n\n")
1324
1325             fw(".. hlist::\n")
1326             fw("   :columns: 2\n\n")
1327
1328             # context does its own thing
1329             # "active_base": ("ObjectBase", False),
1330             for ref_attr, (ref_type, ref_is_seq) in sorted(context_type_map.items()):
1331                 if ref_type == struct_id:
1332                     fw("   * :mod:`bpy.context.%s`\n" % ref_attr)
1333             del ref_attr, ref_type, ref_is_seq
1334
1335             for ref in struct.references:
1336                 ref_split = ref.split(".")
1337                 if len(ref_split) > 2:
1338                     ref = ref_split[-2] + "." + ref_split[-1]
1339                 fw("   * :class:`%s`\n" % ref)
1340             fw("\n")
1341
1342         # docs last?, disable for now
1343         # write_example_ref("", fw, "bpy.types.%s" % struct_id)
1344         file.close()
1345
1346     if "bpy.types" not in EXCLUDE_MODULES:
1347         for struct in structs.values():
1348             # TODO, rna_info should filter these out!
1349             if "_OT_" in struct.identifier:
1350                 continue
1351             write_struct(struct)
1352
1353         def fake_bpy_type(class_value, class_name, descr_str, use_subclasses=True):
1354             filepath = os.path.join(basepath, "bpy.types.%s.rst" % class_name)
1355             file = open(filepath, "w", encoding="utf-8")
1356             fw = file.write
1357
1358             fw(title_string(class_name, "="))
1359
1360             fw(".. module:: bpy.types\n")
1361             fw("\n")
1362
1363             if use_subclasses:
1364                 subclass_ids = [s.identifier for s in structs.values() if s.base is None if not rna_info.rna_id_ignore(s.identifier)]
1365                 if subclass_ids:
1366                     fw("subclasses --- \n" + ", ".join((":class:`%s`" % s) for s in sorted(subclass_ids)) + "\n\n")
1367
1368             fw(".. class:: %s\n\n" % class_name)
1369             fw("   %s\n\n" % descr_str)
1370             fw("   .. note::\n\n")
1371             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)
1372
1373             descr_items = [(key, descr) for key, descr in sorted(class_value.__dict__.items()) if not key.startswith("__")]
1374
1375             for key, descr in descr_items:
1376                 if type(descr) == MethodDescriptorType:  # GetSetDescriptorType, GetSetDescriptorType's are not documented yet
1377                     py_descr2sphinx("   ", fw, descr, "bpy.types", class_name, key)
1378
1379             for key, descr in descr_items:
1380                 if type(descr) == GetSetDescriptorType:
1381                     py_descr2sphinx("   ", fw, descr, "bpy.types", class_name, key)
1382             file.close()
1383
1384         # write fake classes
1385         if _BPY_STRUCT_FAKE:
1386             class_value = bpy.types.Struct.__bases__[0]
1387             fake_bpy_type(class_value, _BPY_STRUCT_FAKE, "built-in base class for all classes in bpy.types.", use_subclasses=True)
1388
1389         if _BPY_PROP_COLLECTION_FAKE:
1390             class_value = bpy.data.objects.__class__
1391             fake_bpy_type(class_value, _BPY_PROP_COLLECTION_FAKE, "built-in class used for all collections.", use_subclasses=False)
1392
1393     # operators
1394     def write_ops():
1395         API_BASEURL = "http://svn.blender.org/svnroot/bf-blender/trunk/blender/release/scripts"
1396         API_BASEURL_ADDON = "http://svn.blender.org/svnroot/bf-extensions/trunk/py/scripts"
1397         API_BASEURL_ADDON_CONTRIB = "http://svn.blender.org/svnroot/bf-extensions/contrib/py/scripts"
1398
1399         op_modules = {}
1400         for op in ops.values():
1401             op_modules.setdefault(op.module_name, []).append(op)
1402         del op
1403
1404         for op_module_name, ops_mod in op_modules.items():
1405             filepath = os.path.join(basepath, "bpy.ops.%s.rst" % op_module_name)
1406             file = open(filepath, "w", encoding="utf-8")
1407             fw = file.write
1408
1409             title = "%s Operators" % op_module_name.replace("_", " ").title()
1410
1411             fw(title_string(title, "="))
1412
1413             fw(".. module:: bpy.ops.%s\n\n" % op_module_name)
1414
1415             ops_mod.sort(key=lambda op: op.func_name)
1416
1417             for op in ops_mod:
1418                 args_str = ", ".join(prop.get_arg_default(force=True) for prop in op.args)
1419                 fw(".. function:: %s(%s)\n\n" % (op.func_name, args_str))
1420
1421                 # if the description isn't valid, we output the standard warning
1422                 # with a link to the wiki so that people can help
1423                 if not op.description or op.description == "(undocumented operator)":
1424                     operator_description = undocumented_message('bpy.ops', op.module_name, op.func_name)
1425                 else:
1426                     operator_description = op.description
1427
1428                 fw("   %s\n\n" % operator_description)
1429                 for prop in op.args:
1430                     write_param("   ", fw, prop)
1431                 if op.args:
1432                     fw("\n")
1433
1434                 location = op.get_location()
1435                 if location != (None, None):
1436                     if location[0].startswith("addons_contrib" + os.sep):
1437                         url_base = API_BASEURL_ADDON_CONTRIB
1438                     elif location[0].startswith("addons" + os.sep):
1439                         url_base = API_BASEURL_ADDON
1440                     else:
1441                         url_base = API_BASEURL
1442
1443                     fw("   :file: `%s <%s/%s>`_:%d\n\n" % (location[0],
1444                                                            url_base,
1445                                                            location[0],
1446                                                            location[1]))
1447
1448             file.close()
1449
1450     if "bpy.ops" not in EXCLUDE_MODULES:
1451         write_ops()
1452
1453
1454 def write_sphinx_conf_py(basepath):
1455     '''
1456     Write sphinx's conf.py
1457     '''
1458     filepath = os.path.join(basepath, "conf.py")
1459     file = open(filepath, "w", encoding="utf-8")
1460     fw = file.write
1461
1462     fw("project = 'Blender'\n")
1463     # fw("master_doc = 'index'\n")
1464     fw("copyright = u'Blender Foundation'\n")
1465     fw("version = '%s - API'\n" % BLENDER_VERSION_DOTS)
1466     fw("release = '%s - API'\n" % BLENDER_VERSION_DOTS)
1467
1468     if ARGS.sphinx_theme != 'default':
1469         fw("html_theme = '%s'\n" % ARGS.sphinx_theme)
1470
1471     if ARGS.sphinx_theme in SPHINX_THEMES['bf']:
1472         fw("html_theme_path = ['../']\n")
1473         # copied with the theme, exclude else we get an error [#28873]
1474         fw("html_favicon = 'favicon.ico'\n")    # in <theme>/static/
1475
1476     # not helpful since the source is generated, adds to upload size.
1477     fw("html_copy_source = False\n")
1478     fw("\n")
1479
1480     # needed for latex, pdf gen
1481     fw("latex_documents = [ ('contents', 'contents.tex', 'Blender Index', 'Blender Foundation', 'manual'), ]\n")
1482     fw("latex_paper_size = 'a4paper'\n")
1483     file.close()
1484
1485
1486 def execfile(filepath):
1487     global_namespace = {"__file__": filepath, "__name__": "__main__"}
1488     exec(compile(open(filepath).read(), filepath, 'exec'), global_namespace)
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         "Freestyle", "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         "Freestyle"         : "Freestyle Data Types & Operators",
1699     }
1700     for mod_name, mod_descr in importable_modules.items():
1701         if mod_name not in EXCLUDE_MODULES:
1702             module = __import__(mod_name,
1703                                 fromlist=[mod_name.rsplit(".", 1)[-1]])
1704             pymodule2sphinx(basepath, mod_name, module, mod_descr)
1705
1706
1707 def copy_handwritten_rsts(basepath):
1708
1709     # info docs
1710     if not EXCLUDE_INFO_DOCS:
1711         for info, info_desc in INFO_DOCS:
1712             shutil.copy2(os.path.join(RST_DIR, info), basepath)
1713
1714     # TODO put this docs in blender's code and use import as per modules above
1715     handwritten_modules = [
1716         "bge.types",
1717         "bge.logic",
1718         "bge.render",
1719         "bge.texture",
1720         "bge.events",
1721         "bge.constraints",
1722         "bgl",  # "Blender OpenGl wrapper"
1723         "gpu",  # "GPU Shader Module"
1724
1725         "bmesh.ops",  # generated by rst_from_bmesh_opdefines.py
1726
1727         # includes...
1728         "include__bmesh",
1729     ]
1730     for mod_name in handwritten_modules:
1731         if mod_name not in EXCLUDE_MODULES:
1732             # copy2 keeps time/date stamps
1733             shutil.copy2(os.path.join(RST_DIR, "%s.rst" % mod_name), basepath)
1734
1735     # changelog
1736     shutil.copy2(os.path.join(RST_DIR, "change_log.rst"), basepath)
1737
1738     # copy images, could be smarter but just glob for now.
1739     for f in os.listdir(RST_DIR):
1740         if f.endswith(".png"):
1741             shutil.copy2(os.path.join(RST_DIR, f), basepath)
1742
1743
1744 def rna2sphinx(basepath):
1745
1746     try:
1747         os.mkdir(basepath)
1748     except:
1749         pass
1750
1751     # sphinx setup
1752     write_sphinx_conf_py(basepath)
1753
1754     # main page
1755     write_rst_contents(basepath)
1756
1757     # context
1758     if "bpy.context" not in EXCLUDE_MODULES:
1759         # one of a kind, context doc (uses ctypes to extract info!)
1760         # doesn't work on mac
1761         if PLATFORM != "darwin":
1762             pycontext2sphinx(basepath)
1763
1764     # internal modules
1765     write_rst_bpy(basepath)                 # bpy, disabled by default
1766     write_rst_types_index(basepath)         # bpy.types
1767     write_rst_ops_index(basepath)           # bpy.ops
1768     pyrna2sphinx(basepath)                  # bpy.types.* and bpy.ops.*
1769     write_rst_data(basepath)                # bpy.data
1770     write_rst_importable_modules(basepath)
1771
1772     # copy the other rsts
1773     copy_handwritten_rsts(basepath)
1774
1775
1776 def align_sphinx_in_to_sphinx_in_tmp():
1777     '''
1778     Move changed files from SPHINX_IN_TMP to SPHINX_IN
1779     '''
1780     import filecmp
1781
1782     sphinx_in_files = set(os.listdir(SPHINX_IN))
1783     sphinx_in_tmp_files = set(os.listdir(SPHINX_IN_TMP))
1784
1785     # remove deprecated files that have been removed
1786     for f in sorted(sphinx_in_files):
1787         if f not in sphinx_in_tmp_files:
1788             BPY_LOGGER.debug("\tdeprecated: %s" % f)
1789             os.remove(os.path.join(SPHINX_IN, f))
1790
1791     # freshen with new files.
1792     for f in sorted(sphinx_in_tmp_files):
1793         f_from = os.path.join(SPHINX_IN_TMP, f)
1794         f_to = os.path.join(SPHINX_IN, f)
1795
1796         do_copy = True
1797         if f in sphinx_in_files:
1798             if filecmp.cmp(f_from, f_to):
1799                 do_copy = False
1800
1801         if do_copy:
1802             BPY_LOGGER.debug("\tupdating: %s" % f)
1803             shutil.copy(f_from, f_to)
1804
1805
1806 def refactor_sphinx_log(sphinx_logfile):
1807     refactored_log = []
1808     with open(sphinx_logfile, "r", encoding="utf-8") as original_logfile:
1809         lines = set(original_logfile.readlines())
1810         for line in lines:
1811             if 'warning' in line.lower() or 'error' in line.lower():
1812                 line = line.strip().split(None, 2)
1813                 if len(line) == 3:
1814                     location, kind, msg = line
1815                     location = os.path.relpath(location, start=SPHINX_IN)
1816                     refactored_log.append((kind, location, msg))
1817     with open(sphinx_logfile, "w", encoding="utf-8") as refactored_logfile:
1818         for log in sorted(refactored_log):
1819             refactored_logfile.write("%-12s %s\n             %s\n" % log)
1820
1821
1822 def main():
1823
1824     # eventually, create the dirs
1825     for dir_path in [ARGS.output_dir, SPHINX_IN]:
1826         if not os.path.exists(dir_path):
1827             os.mkdir(dir_path)
1828
1829     # eventually, log in files
1830     if ARGS.log:
1831         bpy_logfile = os.path.join(ARGS.output_dir, ".bpy.log")
1832         bpy_logfilehandler = logging.FileHandler(bpy_logfile, mode="w")
1833         bpy_logfilehandler.setLevel(logging.DEBUG)
1834         BPY_LOGGER.addHandler(bpy_logfilehandler)
1835
1836         # using a FileHandler seems to disable the stdout, so we add a StreamHandler
1837         bpy_log_stdout_handler = logging.StreamHandler(stream=sys.stdout)
1838         bpy_log_stdout_handler.setLevel(logging.DEBUG)
1839         BPY_LOGGER.addHandler(bpy_log_stdout_handler)
1840
1841     # in case of out-of-source build, copy the needed dirs
1842     if ARGS.output_dir != SCRIPT_DIR:
1843         # examples dir
1844         examples_dir_copy = os.path.join(ARGS.output_dir, "examples")
1845         if os.path.exists(examples_dir_copy):
1846             shutil.rmtree(examples_dir_copy, True)
1847         shutil.copytree(EXAMPLES_DIR,
1848                         examples_dir_copy,
1849                         ignore=shutil.ignore_patterns(*(".svn",)),
1850                         copy_function=shutil.copy)
1851
1852         # eventually, copy the theme dir
1853         if ARGS.sphinx_theme in SPHINX_THEMES['bf']:
1854             if os.path.exists(SPHINX_THEME_DIR):
1855                 shutil.rmtree(SPHINX_THEME_DIR, True)
1856             shutil.copytree(SPHINX_THEME_SVN_DIR,
1857                             SPHINX_THEME_DIR,
1858                             ignore=shutil.ignore_patterns(*(".svn",)),
1859                             copy_function=shutil.copy)
1860
1861     # dump the api in rst files
1862     if os.path.exists(SPHINX_IN_TMP):
1863         shutil.rmtree(SPHINX_IN_TMP, True)
1864
1865     rna2sphinx(SPHINX_IN_TMP)
1866
1867     if ARGS.full_rebuild:
1868         # only for full updates
1869         shutil.rmtree(SPHINX_IN, True)
1870         shutil.copytree(SPHINX_IN_TMP,
1871                         SPHINX_IN,
1872                         copy_function=shutil.copy)
1873         if ARGS.sphinx_build and os.path.exists(SPHINX_OUT):
1874             shutil.rmtree(SPHINX_OUT, True)
1875         if ARGS.sphinx_build_pdf and os.path.exists(SPHINX_OUT_PDF):
1876             shutil.rmtree(SPHINX_OUT_PDF, True)
1877     else:
1878         # move changed files in SPHINX_IN
1879         align_sphinx_in_to_sphinx_in_tmp()
1880
1881     # report which example files weren't used
1882     EXAMPLE_SET_UNUSED = EXAMPLE_SET - EXAMPLE_SET_USED
1883     if EXAMPLE_SET_UNUSED:
1884         BPY_LOGGER.debug("\nUnused examples found in '%s'..." % EXAMPLES_DIR)
1885         for f in sorted(EXAMPLE_SET_UNUSED):
1886             BPY_LOGGER.debug("    %s.py" % f)
1887         BPY_LOGGER.debug("  %d total\n" % len(EXAMPLE_SET_UNUSED))
1888
1889     # eventually, build the html docs
1890     if ARGS.sphinx_build:
1891         import subprocess
1892         subprocess.call(SPHINX_BUILD)
1893
1894         # sphinx-build log cleanup+sort
1895         if ARGS.log:
1896             if os.stat(SPHINX_BUILD_LOG).st_size:
1897                 refactor_sphinx_log(SPHINX_BUILD_LOG)
1898
1899     # eventually, build the pdf docs
1900     if ARGS.sphinx_build_pdf:
1901         import subprocess
1902         subprocess.call(SPHINX_BUILD_PDF)
1903         subprocess.call(SPHINX_MAKE_PDF, stdout=SPHINX_MAKE_PDF_STDOUT)
1904
1905         # sphinx-build log cleanup+sort
1906         if ARGS.log:
1907             if os.stat(SPHINX_BUILD_PDF_LOG).st_size:
1908                 refactor_sphinx_log(SPHINX_BUILD_PDF_LOG)
1909
1910     # eventually, prepare the dir to be deployed online (REFERENCE_PATH)
1911     if ARGS.pack_reference:
1912
1913         if ARGS.sphinx_build:
1914             # delete REFERENCE_PATH
1915             if os.path.exists(REFERENCE_PATH):
1916                 shutil.rmtree(REFERENCE_PATH, True)
1917
1918             # copy SPHINX_OUT to the REFERENCE_PATH
1919             ignores = ('.doctrees', 'objects.inv', '.buildinfo')
1920             shutil.copytree(SPHINX_OUT,
1921                             REFERENCE_PATH,
1922                             ignore=shutil.ignore_patterns(*ignores))
1923             shutil.copy(os.path.join(REFERENCE_PATH, "contents.html"),
1924                         os.path.join(REFERENCE_PATH, "index.html"))
1925
1926             # zip REFERENCE_PATH
1927             basename = os.path.join(ARGS.output_dir, REFERENCE_NAME)
1928             tmp_path = shutil.make_archive(basename, 'zip',
1929                                            root_dir=ARGS.output_dir,
1930                                            base_dir=REFERENCE_NAME)
1931             final_path = os.path.join(REFERENCE_PATH, BLENDER_ZIP_FILENAME)
1932             os.rename(tmp_path, final_path)
1933
1934         if ARGS.sphinx_build_pdf:
1935             # copy the pdf to REFERENCE_PATH
1936             shutil.copy(os.path.join(SPHINX_OUT_PDF, "contents.pdf"),
1937                         os.path.join(REFERENCE_PATH, BLENDER_PDF_FILENAME))
1938
1939     sys.exit()
1940
1941
1942 if __name__ == '__main__':
1943     main()