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