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