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