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