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