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