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