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