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