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