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