blacklist language property for sphinx docs, encoding was messing up PDF generation.
[blender.git] / doc / python_api / sphinx_doc_gen.py
1  # ***** BEGIN GPL LICENSE BLOCK *****
2  #
3  # This program is free software; you can redistribute it and/or
4  # modify it under the terms of the GNU General Public License
5  # as published by the Free Software Foundation; either version 2
6  # of the License, or (at your option) any later version.
7  #
8  # This program is distributed in the hope that it will be useful,
9  # but WITHOUT ANY WARRANTY; without even the implied warranty of
10  # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11  # GNU General Public License for more details.
12  #
13  # You should have received a copy of the GNU General Public License
14  # along with this program; if not, write to the Free Software Foundation,
15  # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16  #
17  # Contributor(s): Campbell Barton
18  #
19  # #**** END GPL LICENSE BLOCK #****
20
21 # <pep8 compliant>
22
23 script_help_msg = '''
24 Usage:
25
26 For HTML generation
27 -------------------
28 - Run this script from blenders root path once you have compiled blender
29
30     ./blender.bin --background -noaudio --python doc/python_api/sphinx_doc_gen.py
31
32   This will generate python files in doc/python_api/sphinx-in/
33   providing ./blender.bin is or links to the blender executable
34
35 - Generate html docs by running...
36
37     cd doc/python_api
38     sphinx-build sphinx-in sphinx-out
39
40   This requires sphinx 1.0.7 to be installed.
41
42 For PDF generation
43 ------------------
44 - After you have built doc/python_api/sphinx-in (see above), run:
45
46     sphinx-build -b latex doc/python_api/sphinx-in doc/python_api/sphinx-out
47     cd doc/python_api/sphinx-out
48     make
49 '''
50
51 # Check we're running in blender
52 if __import__("sys").modules.get("bpy") is None:
53     print("\nError, this script must run from inside blender")
54     print(script_help_msg)
55
56     import sys
57     sys.exit()
58
59
60 # Switch for quick testing
61 if 1:
62     # full build
63     EXCLUDE_INFO_DOCS = False
64     EXCLUDE_MODULES = ()
65     FILTER_BPY_TYPES = None
66     FILTER_BPY_OPS = None
67
68 else:
69     EXCLUDE_INFO_DOCS = False
70     # for testing so doc-builds dont take so long.
71     EXCLUDE_MODULES = (
72         "bpy.context",
73         #"bpy.app",
74         #"bpy.app.handlers",
75         "bpy.path",
76         "bpy.data",
77         "bpy.props",
78         "bpy.utils",
79         "bpy.context",
80         "bpy.types",  # supports filtering
81         "bpy.ops",  # supports filtering
82         "bpy_extras",
83         "bge",
84         "aud",
85         "bgl",
86         "blf",
87         "gpu",
88         "mathutils",
89         "mathutils.geometry",
90     )
91
92     FILTER_BPY_TYPES = ("bpy_struct", "Operator", "ID")  # allow
93     FILTER_BPY_OPS = ("import.scene", )  # allow
94
95     # for quick rebuilds
96     """
97 rm -rf /b/doc/python_api/sphinx-* && \
98 ./blender.bin --background -noaudio --factory-startup --python  doc/python_api/sphinx_doc_gen.py && \
99 sphinx-build doc/python_api/sphinx-in doc/python_api/sphinx-out
100
101     """
102
103 # extra info, not api reference docs
104 # stored in ./rst/info/
105 INFO_DOCS = (
106     ("info_quickstart.rst", "Blender/Python Quickstart: new to blender/scripting and want to get your feet wet?"),
107     ("info_overview.rst", "Blender/Python API Overview: a more complete explanation of python integration"),
108     ("info_best_practice.rst", "Best Practice: Conventions to follow for writing good scripts"),
109     ("info_tips_and_tricks.rst", "Tips and Tricks: Hints to help you while writeing scripts for blender"),
110     ("info_gotcha.rst", "Gotcha's: some of the problems you may come up against when writing scripts"),
111     )
112
113 # only support for properties atm.
114 RNA_BLACKLIST = {
115     # messes up PDF!, really a bug but for now just workaround.
116     "UserPreferencesSystem": {"language", },
117     }
118
119
120 # -----------------------------------------------------------------------------
121 # configure compile time options
122
123 try:
124     __import__("aud")
125 except ImportError:
126     print("Warning: Built without 'aud' module, docs incomplete...")
127     EXCLUDE_MODULES = EXCLUDE_MODULES + ("aud", )
128
129
130 # import rpdb2; rpdb2.start_embedded_debugger('test')
131 import os
132 import inspect
133 import bpy
134 import rna_info
135
136 # lame, python wont give some access
137 ClassMethodDescriptorType = type(dict.__dict__['fromkeys'])
138 MethodDescriptorType = type(dict.get)
139 GetSetDescriptorType = type(int.real)
140 from types import MemberDescriptorType
141
142 EXAMPLE_SET = set()
143 EXAMPLE_SET_USED = set()
144
145 _BPY_STRUCT_FAKE = "bpy_struct"
146 _BPY_PROP_COLLECTION_FAKE = "bpy_prop_collection"
147 _BPY_FULL_REBUILD = False
148
149 if _BPY_PROP_COLLECTION_FAKE:
150     _BPY_PROP_COLLECTION_ID = ":class:`%s`" % _BPY_PROP_COLLECTION_FAKE
151 else:
152     _BPY_PROP_COLLECTION_ID = "collection"
153
154
155 def is_struct_seq(value):
156     return isinstance(value, tuple) and type(tuple) != tuple and hasattr(value, "n_fields")
157
158
159 def undocumented_message(module_name, type_name, identifier):
160     if str(type_name).startswith('<module'):
161         preloadtitle = '%s.%s' % (module_name, identifier)
162     else:
163         preloadtitle = '%s.%s.%s' % (module_name, type_name, identifier)
164     message = "Undocumented (`contribute "\
165         "<http://wiki.blender.org/index.php/Dev:2.5/Py/API/Documentation/Contribute"\
166         "?action=edit&section=new&preload=Dev:2.5/Py/API/Documentation/Contribute/Howto-message"\
167         "&preloadtitle=%s>`_)\n\n" % preloadtitle
168     return message
169
170
171 def range_str(val):
172     '''
173     Converts values to strings for the range directive.
174     (unused function it seems)
175     '''
176     if val < -10000000:
177         return '-inf'
178     elif val > 10000000:
179         return 'inf'
180     elif type(val) == float:
181         return '%g' % val
182     else:
183         return str(val)
184
185
186 def example_extract_docstring(filepath):
187     file = open(filepath, "r", encoding="utf-8")
188     line = file.readline()
189     line_no = 0
190     text = []
191     if line.startswith('"""'):  # assume nothing here
192         line_no += 1
193     else:
194         file.close()
195         return "", 0
196
197     for line in file.readlines():
198         line_no += 1
199         if line.startswith('"""'):
200             break
201         else:
202             text.append(line.rstrip())
203
204     line_no += 1
205     file.close()
206     return "\n".join(text), line_no
207
208
209 def write_title(fw, text, heading_char):
210     fw("%s\n%s\n\n" % (text, len(text) * heading_char))
211
212
213 def write_example_ref(ident, fw, example_id, ext="py"):
214     if example_id in EXAMPLE_SET:
215
216         # extract the comment
217         filepath = "../examples/%s.%s" % (example_id, ext)
218         filepath_full = os.path.join(os.path.dirname(fw.__self__.name), filepath)
219
220         text, line_no = example_extract_docstring(filepath_full)
221
222         for line in text.split("\n"):
223             fw("%s\n" % (ident + line).rstrip())
224         fw("\n")
225
226         fw("%s.. literalinclude:: %s\n" % (ident, filepath))
227         if line_no > 0:
228             fw("%s   :lines: %d-\n" % (ident, line_no))
229         fw("\n")
230         EXAMPLE_SET_USED.add(example_id)
231     else:
232         if bpy.app.debug:
233             print("\tskipping example:", example_id)
234
235     # Support for numbered files bpy.types.Operator -> bpy.types.Operator.1.py
236     i = 1
237     while True:
238         example_id_num = "%s.%d" % (example_id, i)
239         if example_id_num in EXAMPLE_SET:
240             write_example_ref(ident, fw, example_id_num, ext)
241             i += 1
242         else:
243             break
244
245
246 def write_indented_lines(ident, fn, text, strip=True):
247     '''
248     Apply same indentation to all lines in a multilines text.
249     '''
250     if text is None:
251         return
252
253     lines = text.split("\n")
254
255     # strip empty lines from the start/end
256     while lines and not lines[0].strip():
257         del lines[0]
258     while lines and not lines[-1].strip():
259         del lines[-1]
260
261     if strip:
262         ident_strip = 1000
263         for l in lines:
264             if l.strip():
265                 ident_strip = min(ident_strip, len(l) - len(l.lstrip()))
266         for l in lines:
267             fn(ident + l[ident_strip:] + "\n")
268     else:
269         for l in lines:
270             fn(ident + l + "\n")
271
272
273 def pymethod2sphinx(ident, fw, identifier, py_func):
274     '''
275     class method to sphinx
276     '''
277     arg_str = inspect.formatargspec(*inspect.getargspec(py_func))
278     if arg_str.startswith("(self, "):
279         arg_str = "(" + arg_str[7:]
280         func_type = "method"
281     elif arg_str.startswith("(cls, "):
282         arg_str = "(" + arg_str[6:]
283         func_type = "classmethod"
284     else:
285         func_type = "staticmethod"
286
287     fw(ident + ".. %s:: %s%s\n\n" % (func_type, identifier, arg_str))
288     if py_func.__doc__:
289         write_indented_lines(ident + "   ", fw, py_func.__doc__)
290         fw("\n")
291
292
293 def pyfunc2sphinx(ident, fw, identifier, py_func, is_class=True):
294     '''
295     function or class method to sphinx
296     '''
297     arg_str = inspect.formatargspec(*inspect.getargspec(py_func))
298
299     if not is_class:
300         func_type = "function"
301
302         # ther rest are class methods
303     elif arg_str.startswith("(self, "):
304         arg_str = "(" + arg_str[7:]
305         func_type = "method"
306     elif arg_str.startswith("(cls, "):
307         arg_str = "(" + arg_str[6:]
308         func_type = "classmethod"
309     else:
310         func_type = "staticmethod"
311
312     fw(ident + ".. %s:: %s%s\n\n" % (func_type, identifier, arg_str))
313     if py_func.__doc__:
314         write_indented_lines(ident + "   ", fw, py_func.__doc__)
315         fw("\n")
316
317
318 def py_descr2sphinx(ident, fw, descr, module_name, type_name, identifier):
319     if identifier.startswith("_"):
320         return
321
322     doc = descr.__doc__
323     if not doc:
324         doc = undocumented_message(module_name, type_name, identifier)
325
326     if type(descr) == GetSetDescriptorType:
327         fw(ident + ".. attribute:: %s\n\n" % identifier)
328         write_indented_lines(ident + "   ", fw, doc, False)
329         fw("\n")
330     elif type(descr) == MemberDescriptorType:  # same as above but use 'data'
331         fw(ident + ".. data:: %s\n\n" % identifier)
332         write_indented_lines(ident + "   ", fw, doc, False)
333         fw("\n")
334     elif type(descr) in (MethodDescriptorType, ClassMethodDescriptorType):
335         write_indented_lines(ident, fw, doc, False)
336         fw("\n")
337     else:
338         raise TypeError("type was not GetSetDescriptorType, MethodDescriptorType or ClassMethodDescriptorType")
339
340     write_example_ref(ident + "   ", fw, module_name + "." + type_name + "." + identifier)
341     fw("\n")
342
343
344 def py_c_func2sphinx(ident, fw, module_name, type_name, identifier, py_func, is_class=True):
345     '''
346     c defined function to sphinx.
347     '''
348
349     # dump the docstring, assume its formatted correctly
350     if py_func.__doc__:
351         write_indented_lines(ident, fw, py_func.__doc__, False)
352         fw("\n")
353     else:
354         fw(ident + ".. function:: %s()\n\n" % identifier)
355         fw(ident + "   " + undocumented_message(module_name, type_name, identifier))
356
357     if is_class:
358         write_example_ref(ident + "   ", fw, module_name + "." + type_name + "." + identifier)
359     else:
360         write_example_ref(ident + "   ", fw, module_name + "." + identifier)
361
362     fw("\n")
363
364
365 def pyprop2sphinx(ident, fw, identifier, py_prop):
366     '''
367     python property to sphinx
368     '''
369     # readonly properties use "data" directive, variables use "attribute" directive
370     if py_prop.fset is None:
371         fw(ident + ".. data:: %s\n\n" % identifier)
372     else:
373         fw(ident + ".. attribute:: %s\n\n" % identifier)
374     write_indented_lines(ident + "   ", fw, py_prop.__doc__)
375     if py_prop.fset is None:
376         fw(ident + "   (readonly)\n\n")
377
378
379 def pymodule2sphinx(BASEPATH, module_name, module, title):
380     import types
381     attribute_set = set()
382     filepath = os.path.join(BASEPATH, module_name + ".rst")
383
384     module_all = getattr(module, "__all__", None)
385     module_dir = sorted(dir(module))
386
387     if module_all:
388         module_dir = module_all
389
390     file = open(filepath, "w", encoding="utf-8")
391
392     fw = file.write
393
394     write_title(fw, "%s (%s)" % (title, module_name), "=")
395
396     fw(".. module:: %s\n\n" % module_name)
397
398     if module.__doc__:
399         # Note, may contain sphinx syntax, dont mangle!
400         fw(module.__doc__.strip())
401         fw("\n\n")
402
403     write_example_ref("", fw, module_name)
404
405     # write submodules
406     # we could also scan files but this ensures __all__ is used correctly
407     if module_all is not None:
408         submod_name = None
409         submod = None
410         submod_ls = []
411         for submod_name in module_all:
412             ns = {}
413             exec_str = "from %s import %s as submod" % (module.__name__, submod_name)
414             exec(exec_str, ns, ns)
415             submod = ns["submod"]
416             if type(submod) == types.ModuleType:
417                 submod_ls.append((submod_name, submod))
418
419         del submod_name
420         del submod
421
422         if submod_ls:
423             fw(".. toctree::\n")
424             fw("   :maxdepth: 1\n\n")
425
426             for submod_name, submod in submod_ls:
427                 submod_name_full = "%s.%s" % (module_name, submod_name)
428                 fw("   %s.rst\n\n" % submod_name_full)
429
430                 pymodule2sphinx(BASEPATH, submod_name_full, submod, "%s submodule" % module_name)
431         del submod_ls
432     # done writing submodules!
433
434     # write members of the module
435     # only tested with PyStructs which are not exactly modules
436     for key, descr in sorted(type(module).__dict__.items()):
437         if key.startswith("__"):
438             continue
439         # naughty, we also add getset's into PyStructs, this is not typical py but also not incorrect.
440
441         # type_name is only used for examples and messages
442         type_name = str(type(module)).strip("<>").split(" ", 1)[-1][1:-1]  # "<class 'bpy.app.handlers'>" --> bpy.app.handlers
443         if type(descr) == types.GetSetDescriptorType:
444             py_descr2sphinx("", fw, descr, module_name, type_name, key)
445             attribute_set.add(key)
446     descr_sorted = []
447     for key, descr in sorted(type(module).__dict__.items()):
448         if key.startswith("__"):
449             continue
450
451         if type(descr) == MemberDescriptorType:
452             if descr.__doc__:
453                 value = getattr(module, key, None)
454
455                 value_type = type(value)
456                 descr_sorted.append((key, descr, value, type(value)))
457     # sort by the valye type
458     descr_sorted.sort(key=lambda descr_data: str(descr_data[3]))
459     for key, descr, value, value_type in descr_sorted:
460
461         # must be documented as a submodule
462         if is_struct_seq(value):
463             continue
464
465         type_name = value_type.__name__
466         py_descr2sphinx("", fw, descr, module_name, type_name, key)
467
468         attribute_set.add(key)
469
470     del key, descr, descr_sorted
471
472     classes = []
473     submodules = []
474
475     # use this list so we can sort by type
476     module_dir_value_type = []
477
478     for attribute in module_dir:
479         if attribute.startswith("_"):
480             continue
481
482         if attribute in attribute_set:
483             continue
484
485         if attribute.startswith("n_"):  # annoying exception, needed for bpy.app
486             continue
487
488         # workaround for bpy.app documenting .index() and .count()
489         if isinstance(module, tuple) and hasattr(tuple, attribute):
490             continue
491
492         value = getattr(module, attribute)
493
494         module_dir_value_type.append((attribute, value, type(value)))
495
496     # sort by str of each type
497     # this way lists, functions etc are grouped.
498     module_dir_value_type.sort(key=lambda triple: str(triple[2]))
499
500     for attribute, value, value_type in module_dir_value_type:
501         if value_type == types.FunctionType:
502             pyfunc2sphinx("", fw, attribute, value, is_class=False)
503         elif value_type in (types.BuiltinMethodType, types.BuiltinFunctionType):  # both the same at the moment but to be future proof
504             # note: can't get args from these, so dump the string as is
505             # this means any module used like this must have fully formatted docstrings.
506             py_c_func2sphinx("", fw, module_name, None, attribute, value, is_class=False)
507         elif value_type == type:
508             classes.append((attribute, value))
509         elif issubclass(value_type, types.ModuleType):
510             submodules.append((attribute, value))
511         elif value_type in (bool, int, float, str, tuple):
512             # constant, not much fun we can do here except to list it.
513             # TODO, figure out some way to document these!
514             #fw(".. data:: %s\n\n" % attribute)
515             write_indented_lines("   ", fw, "constant value %s" % repr(value), False)
516             fw("\n")
517         else:
518             print("\tnot documenting %s.%s of %r type" % (module_name, attribute, value_type.__name__))
519             continue
520
521         attribute_set.add(attribute)
522         # TODO, more types...
523     del module_dir_value_type
524
525     # TODO, bpy_extras does this already, mathutils not.
526     """
527     if submodules:
528         fw("\n"
529            "**********\n"
530            "Submodules\n"
531            "**********\n"
532            "\n"
533            )
534         for attribute, submod in submodules:
535             fw("* :mod:`%s.%s`\n" % (module_name, attribute))
536         fw("\n")
537     """
538
539     # write collected classes now
540     for (type_name, value) in classes:
541         # May need to be its own function
542         fw(".. class:: %s\n\n" % type_name)
543         if value.__doc__:
544             write_indented_lines("   ", fw, value.__doc__, False)
545             fw("\n")
546         write_example_ref("   ", fw, module_name + "." + type_name)
547
548         descr_items = [(key, descr) for key, descr in sorted(value.__dict__.items()) if not key.startswith("__")]
549
550         for key, descr in descr_items:
551             if type(descr) == ClassMethodDescriptorType:
552                 py_descr2sphinx("   ", fw, descr, module_name, type_name, key)
553
554         for key, descr in descr_items:
555             if type(descr) == MethodDescriptorType:
556                 py_descr2sphinx("   ", fw, descr, module_name, type_name, key)
557
558         for key, descr in descr_items:
559             if type(descr) == GetSetDescriptorType:
560                 py_descr2sphinx("   ", fw, descr, module_name, type_name, key)
561
562         fw("\n\n")
563
564     file.close()
565
566
567 def pycontext2sphinx(BASEPATH):
568     # Only use once. very irregular
569
570     filepath = os.path.join(BASEPATH, "bpy.context.rst")
571     file = open(filepath, "w", encoding="utf-8")
572     fw = file.write
573     fw("Context Access (bpy.context)\n")
574     fw("============================\n\n")
575     fw(".. module:: bpy.context\n")
576     fw("\n")
577     fw("The context members available depend on the area of blender which is currently being accessed.\n")
578     fw("\n")
579     fw("Note that all context values are readonly, but may be modified through the data api or by running operators\n\n")
580
581     # nasty, get strings directly from blender because there is no other way to get it
582     import ctypes
583
584     context_strings = (
585         "screen_context_dir",
586         "view3d_context_dir",
587         "buttons_context_dir",
588         "image_context_dir",
589         "node_context_dir",
590         "text_context_dir",
591     )
592
593     # Changes in blender will force errors here
594     type_map = {
595         "active_base": ("ObjectBase", False),
596         "active_bone": ("Bone", False),
597         "active_object": ("Object", False),
598         "active_operator": ("Operator", False),
599         "active_pose_bone": ("PoseBone", False),
600         "armature": ("Armature", False),
601         "bone": ("Bone", False),
602         "brush": ("Brush", False),
603         "camera": ("Camera", False),
604         "cloth": ("ClothModifier", False),
605         "collision": ("CollisionModifier", False),
606         "curve": ("Curve", False),
607         "dynamic_paint": ("DynamicPaintModifier", False),
608         "edit_bone": ("EditBone", False),
609         "edit_image": ("Image", False),
610         "edit_object": ("Object", False),
611         "edit_text": ("Text", False),
612         "editable_bones": ("EditBone", True),
613         "fluid": ("FluidSimulationModifier", False),
614         "image_paint_object": ("Object", False),
615         "lamp": ("Lamp", False),
616         "lattice": ("Lattice", False),
617         "material": ("Material", False),
618         "material_slot": ("MaterialSlot", False),
619         "mesh": ("Mesh", False),
620         "meta_ball": ("MetaBall", False),
621         "object": ("Object", False),
622         "particle_edit_object": ("Object", False),
623         "particle_system": ("ParticleSystem", False),
624         "particle_system_editable": ("ParticleSystem", False),
625         "pose_bone": ("PoseBone", False),
626         "scene": ("Scene", False),
627         "sculpt_object": ("Object", False),
628         "selectable_bases": ("ObjectBase", True),
629         "selectable_objects": ("Object", True),
630         "selected_bases": ("ObjectBase", True),
631         "selected_bones": ("Bone", True),
632         "selected_editable_bases": ("ObjectBase", True),
633         "selected_editable_bones": ("Bone", True),
634         "selected_editable_objects": ("Object", True),
635         "selected_editable_sequences": ("Sequence", True),
636         "selected_nodes": ("Node", True),
637         "selected_objects": ("Object", True),
638         "selected_pose_bones": ("PoseBone", True),
639         "selected_sequences": ("Sequence", True),
640         "sequences": ("Sequence", True),
641         "smoke": ("SmokeModifier", False),
642         "soft_body": ("SoftBodyModifier", False),
643         "speaker": ("Speaker", False),
644         "texture": ("Texture", False),
645         "texture_slot": ("MaterialTextureSlot", False),
646         "texture_user": ("ID", False),
647         "vertex_paint_object": ("Object", False),
648         "visible_bases": ("ObjectBase", True),
649         "visible_bones": ("Object", True),
650         "visible_objects": ("Object", True),
651         "visible_pose_bones": ("PoseBone", True),
652         "weight_paint_object": ("Object", False),
653         "world": ("World", False),
654     }
655
656     unique = set()
657     blend_cdll = ctypes.CDLL("")
658     for ctx_str in context_strings:
659         subsection = "%s Context" % ctx_str.split("_")[0].title()
660         fw("\n%s\n%s\n\n" % (subsection, (len(subsection) * '-')))
661
662         attr = ctypes.addressof(getattr(blend_cdll, ctx_str))
663         c_char_p_p = ctypes.POINTER(ctypes.c_char_p)
664         char_array = c_char_p_p.from_address(attr)
665         i = 0
666         while char_array[i] is not None:
667             member = ctypes.string_at(char_array[i]).decode(encoding="ascii")
668             fw(".. data:: %s\n\n" % member)
669             member_type, is_seq = type_map[member]
670             fw("   :type: %s :class:`bpy.types.%s`\n\n" % ("sequence of " if is_seq else "", member_type))
671             unique.add(member)
672             i += 1
673
674     # generate typemap...
675     # for member in sorted(unique):
676     #     print('        "%s": ("", False),' % member)
677     if len(type_map) > len(unique):
678         raise Exception("Some types are not used: %s" % str([member for member in type_map if member not in unique]))
679     else:
680         pass  # will have raised an error above
681
682     file.close()
683
684
685 def pyrna_enum2sphinx(prop, use_empty_descriptions=False):
686     """ write a bullet point list of enum + descrptons
687     """
688
689     if use_empty_descriptions:
690         ok = True
691     else:
692         ok = False
693         for identifier, name, description in prop.enum_items:
694             if description:
695                 ok = True
696                 break
697
698     if ok:
699         return "".join(["* ``%s`` %s.\n" %
700                         (identifier,
701                          ", ".join(val for val in (name, description) if val),
702                          )
703                         for identifier, name, description in prop.enum_items
704                         ])
705     else:
706         return ""
707
708
709 def pyrna2sphinx(BASEPATH):
710     """ bpy.types and bpy.ops
711     """
712     structs, funcs, ops, props = rna_info.BuildRNAInfo()
713     if FILTER_BPY_TYPES is not None:
714         structs = {k: v for k, v in structs.items() if k[1] in FILTER_BPY_TYPES}
715
716     if FILTER_BPY_OPS is not None:
717         ops = {k: v for k, v in ops.items() if v.module_name in FILTER_BPY_OPS}
718
719     def write_param(ident, fw, prop, is_return=False):
720         if is_return:
721             id_name = "return"
722             id_type = "rtype"
723             kwargs = {"as_ret": True}
724             identifier = ""
725         else:
726             id_name = "arg"
727             id_type = "type"
728             kwargs = {"as_arg": True}
729             identifier = " %s" % prop.identifier
730
731         kwargs["class_fmt"] = ":class:`%s`"
732
733         kwargs["collection_id"] = _BPY_PROP_COLLECTION_ID
734
735         type_descr = prop.get_type_description(**kwargs)
736
737         enum_text = pyrna_enum2sphinx(prop)
738
739         if prop.name or prop.description or enum_text:
740             fw(ident + ":%s%s:\n\n" % (id_name, identifier))
741
742             if prop.name or prop.description:
743                 fw(ident + "   " + ", ".join(val for val in (prop.name, prop.description) if val) + "\n\n")
744
745             # special exception, cant use genric code here for enums
746             if enum_text:
747                 write_indented_lines(ident + "   ", fw, enum_text)
748                 fw("\n")
749             del enum_text
750             # end enum exception
751
752         fw(ident + ":%s%s: %s\n" % (id_type, identifier, type_descr))
753
754     def write_struct(struct):
755         #if not struct.identifier.startswith("Sc") and not struct.identifier.startswith("I"):
756         #    return
757
758         #if not struct.identifier == "Object":
759         #    return
760
761         filepath = os.path.join(BASEPATH, "bpy.types.%s.rst" % struct.identifier)
762         file = open(filepath, "w", encoding="utf-8")
763         fw = file.write
764
765         base_id = getattr(struct.base, "identifier", "")
766         struct_id = struct.identifier
767
768         if _BPY_STRUCT_FAKE:
769             if not base_id:
770                 base_id = _BPY_STRUCT_FAKE
771
772         if base_id:
773             title = "%s(%s)" % (struct_id, base_id)
774         else:
775             title = struct_id
776
777         write_title(fw, title, "=")
778
779         fw(".. module:: bpy.types\n\n")
780
781         # docs first?, ok
782         write_example_ref("", fw, "bpy.types.%s" % struct_id)
783
784         base_ids = [base.identifier for base in struct.get_bases()]
785
786         if _BPY_STRUCT_FAKE:
787             base_ids.append(_BPY_STRUCT_FAKE)
788
789         base_ids.reverse()
790
791         if base_ids:
792             if len(base_ids) > 1:
793                 fw("base classes --- ")
794             else:
795                 fw("base class --- ")
796
797             fw(", ".join((":class:`%s`" % base_id) for base_id in base_ids))
798             fw("\n\n")
799
800         subclass_ids = [s.identifier for s in structs.values() if s.base is struct if not rna_info.rna_id_ignore(s.identifier)]
801         if subclass_ids:
802             fw("subclasses --- \n" + ", ".join((":class:`%s`" % s) for s in subclass_ids) + "\n\n")
803
804         base_id = getattr(struct.base, "identifier", "")
805
806         if _BPY_STRUCT_FAKE:
807             if not base_id:
808                 base_id = _BPY_STRUCT_FAKE
809
810         if base_id:
811             fw(".. class:: %s(%s)\n\n" % (struct_id, base_id))
812         else:
813             fw(".. class:: %s\n\n" % struct_id)
814
815         fw("   %s\n\n" % struct.description)
816
817         # properties sorted in alphabetical order
818         sorted_struct_properties = struct.properties[:]
819         sorted_struct_properties.sort(key=lambda prop: prop.identifier)
820
821         # support blacklisting props
822         struct_blacklist = RNA_BLACKLIST.get(struct_id, ())
823
824         for prop in sorted_struct_properties:
825
826             # support blacklisting props
827             if prop.identifier in struct_blacklist:
828                 continue
829
830             type_descr = prop.get_type_description(class_fmt=":class:`%s`", collection_id=_BPY_PROP_COLLECTION_ID)
831             # readonly properties use "data" directive, variables properties use "attribute" directive
832             if 'readonly' in type_descr:
833                 fw("   .. data:: %s\n\n" % prop.identifier)
834             else:
835                 fw("   .. attribute:: %s\n\n" % prop.identifier)
836             if prop.description:
837                 fw("      %s\n\n" % prop.description)
838
839             # special exception, cant use genric code here for enums
840             if prop.type == "enum":
841                 enum_text = pyrna_enum2sphinx(prop)
842                 if enum_text:
843                     write_indented_lines("      ", fw, enum_text)
844                     fw("\n")
845                 del enum_text
846             # end enum exception
847
848             fw("      :type: %s\n\n" % type_descr)
849
850         # python attributes
851         py_properties = struct.get_py_properties()
852         py_prop = None
853         for identifier, py_prop in py_properties:
854             pyprop2sphinx("   ", fw, identifier, py_prop)
855         del py_properties, py_prop
856
857         for func in struct.functions:
858             args_str = ", ".join(prop.get_arg_default(force=False) for prop in func.args)
859
860             fw("   .. %s:: %s(%s)\n\n" % ("classmethod" if func.is_classmethod else "method", func.identifier, args_str))
861             fw("      %s\n\n" % func.description)
862
863             for prop in func.args:
864                 write_param("      ", fw, prop)
865
866             if len(func.return_values) == 1:
867                 write_param("      ", fw, func.return_values[0], is_return=True)
868             elif func.return_values:  # multiple return values
869                 fw("      :return (%s):\n" % ", ".join(prop.identifier for prop in func.return_values))
870                 for prop in func.return_values:
871                     # TODO, pyrna_enum2sphinx for multiple return values... actually dont think we even use this but still!!!
872                     type_descr = prop.get_type_description(as_ret=True, class_fmt=":class:`%s`", collection_id=_BPY_PROP_COLLECTION_ID)
873                     descr = prop.description
874                     if not descr:
875                         descr = prop.name
876                     fw("         `%s`, %s, %s\n\n" % (prop.identifier, descr, type_descr))
877
878             write_example_ref("      ", fw, "bpy.types." + struct_id + "." + func.identifier)
879
880             fw("\n")
881
882         # python methods
883         py_funcs = struct.get_py_functions()
884         py_func = None
885
886         for identifier, py_func in py_funcs:
887             pyfunc2sphinx("   ", fw, identifier, py_func, is_class=True)
888         del py_funcs, py_func
889
890         py_funcs = struct.get_py_c_functions()
891         py_func = None
892
893         for identifier, py_func in py_funcs:
894             py_c_func2sphinx("   ", fw, "bpy.types", struct_id, identifier, py_func, is_class=True)
895
896         lines = []
897
898         if struct.base or _BPY_STRUCT_FAKE:
899             bases = list(reversed(struct.get_bases()))
900
901             # props
902             lines[:] = []
903
904             if _BPY_STRUCT_FAKE:
905                 descr_items = [(key, descr) for key, descr in sorted(bpy.types.Struct.__bases__[0].__dict__.items()) if not key.startswith("__")]
906
907             if _BPY_STRUCT_FAKE:
908                 for key, descr in descr_items:
909                     if type(descr) == GetSetDescriptorType:
910                         lines.append("   * :class:`%s.%s`\n" % (_BPY_STRUCT_FAKE, key))
911
912             for base in bases:
913                 for prop in base.properties:
914                     lines.append("   * :class:`%s.%s`\n" % (base.identifier, prop.identifier))
915
916                 for identifier, py_prop in base.get_py_properties():
917                     lines.append("   * :class:`%s.%s`\n" % (base.identifier, identifier))
918
919                 for identifier, py_prop in base.get_py_properties():
920                     lines.append("   * :class:`%s.%s`\n" % (base.identifier, identifier))
921
922             if lines:
923                 fw(".. rubric:: Inherited Properties\n\n")
924
925                 fw(".. hlist::\n")
926                 fw("   :columns: 2\n\n")
927
928                 for line in lines:
929                     fw(line)
930                 fw("\n")
931
932             # funcs
933             lines[:] = []
934
935             if _BPY_STRUCT_FAKE:
936                 for key, descr in descr_items:
937                     if type(descr) == MethodDescriptorType:
938                         lines.append("   * :class:`%s.%s`\n" % (_BPY_STRUCT_FAKE, key))
939
940             for base in bases:
941                 for func in base.functions:
942                     lines.append("   * :class:`%s.%s`\n" % (base.identifier, func.identifier))
943                 for identifier, py_func in base.get_py_functions():
944                     lines.append("   * :class:`%s.%s`\n" % (base.identifier, identifier))
945
946             if lines:
947                 fw(".. rubric:: Inherited Functions\n\n")
948
949                 fw(".. hlist::\n")
950                 fw("   :columns: 2\n\n")
951
952                 for line in lines:
953                     fw(line)
954                 fw("\n")
955
956             lines[:] = []
957
958         if struct.references:
959             # use this otherwise it gets in the index for a normal heading.
960             fw(".. rubric:: References\n\n")
961
962             fw(".. hlist::\n")
963             fw("   :columns: 2\n\n")
964
965             for ref in struct.references:
966                 ref_split = ref.split(".")
967                 if len(ref_split) > 2:
968                     ref = ref_split[-2] + "." + ref_split[-1]
969                 fw("   * :class:`%s`\n" % ref)
970             fw("\n")
971
972         # docs last?, disable for now
973         # write_example_ref("", fw, "bpy.types.%s" % struct_id)
974         file.close()
975
976     if "bpy.types" not in EXCLUDE_MODULES:
977         for struct in structs.values():
978             # TODO, rna_info should filter these out!
979             if "_OT_" in struct.identifier:
980                 continue
981             write_struct(struct)
982
983         def fake_bpy_type(class_value, class_name, descr_str, use_subclasses=True):
984             filepath = os.path.join(BASEPATH, "bpy.types.%s.rst" % class_name)
985             file = open(filepath, "w", encoding="utf-8")
986             fw = file.write
987
988             write_title(fw, class_name, "=")
989
990             fw(".. module:: bpy.types\n")
991             fw("\n")
992
993             if use_subclasses:
994                 subclass_ids = [s.identifier for s in structs.values() if s.base is None if not rna_info.rna_id_ignore(s.identifier)]
995                 if subclass_ids:
996                     fw("subclasses --- \n" + ", ".join((":class:`%s`" % s) for s in sorted(subclass_ids)) + "\n\n")
997
998             fw(".. class:: %s\n\n" % class_name)
999             fw("   %s\n\n" % descr_str)
1000             fw("   .. note::\n\n")
1001             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)
1002
1003             descr_items = [(key, descr) for key, descr in sorted(class_value.__dict__.items()) if not key.startswith("__")]
1004
1005             for key, descr in descr_items:
1006                 if type(descr) == MethodDescriptorType:  # GetSetDescriptorType, GetSetDescriptorType's are not documented yet
1007                     py_descr2sphinx("   ", fw, descr, "bpy.types", class_name, key)
1008
1009             for key, descr in descr_items:
1010                 if type(descr) == GetSetDescriptorType:
1011                     py_descr2sphinx("   ", fw, descr, "bpy.types", class_name, key)
1012             file.close()
1013
1014         # write fake classes
1015         if _BPY_STRUCT_FAKE:
1016             class_value = bpy.types.Struct.__bases__[0]
1017             fake_bpy_type(class_value, _BPY_STRUCT_FAKE, "built-in base class for all classes in bpy.types.", use_subclasses=True)
1018
1019         if _BPY_PROP_COLLECTION_FAKE:
1020             class_value = bpy.data.objects.__class__
1021             fake_bpy_type(class_value, _BPY_PROP_COLLECTION_FAKE, "built-in class used for all collections.", use_subclasses=False)
1022
1023     # operators
1024     def write_ops():
1025         API_BASEURL = "http://svn.blender.org/svnroot/bf-blender/trunk/blender/release/scripts"
1026         API_BASEURL_ADDON = "http://svn.blender.org/svnroot/bf-extensions/trunk/py/scripts"
1027         API_BASEURL_ADDON_CONTRIB = "http://svn.blender.org/svnroot/bf-extensions/contrib/py/scripts"
1028
1029         op_modules = {}
1030         for op in ops.values():
1031             op_modules.setdefault(op.module_name, []).append(op)
1032         del op
1033
1034         for op_module_name, ops_mod in op_modules.items():
1035             filepath = os.path.join(BASEPATH, "bpy.ops.%s.rst" % op_module_name)
1036             file = open(filepath, "w", encoding="utf-8")
1037             fw = file.write
1038
1039             title = "%s Operators" % op_module_name.replace("_", " ").title()
1040
1041             write_title(fw, title, "=")
1042
1043             fw(".. module:: bpy.ops.%s\n\n" % op_module_name)
1044
1045             ops_mod.sort(key=lambda op: op.func_name)
1046
1047             for op in ops_mod:
1048                 args_str = ", ".join(prop.get_arg_default(force=True) for prop in op.args)
1049                 fw(".. function:: %s(%s)\n\n" % (op.func_name, args_str))
1050
1051                 # if the description isn't valid, we output the standard warning
1052                 # with a link to the wiki so that people can help
1053                 if not op.description or op.description == "(undocumented operator)":
1054                     operator_description = undocumented_message('bpy.ops', op.module_name, op.func_name)
1055                 else:
1056                     operator_description = op.description
1057
1058                 fw("   %s\n\n" % operator_description)
1059                 for prop in op.args:
1060                     write_param("   ", fw, prop)
1061                 if op.args:
1062                     fw("\n")
1063
1064                 location = op.get_location()
1065                 if location != (None, None):
1066                     if location[0].startswith("addons_contrib" + os.sep):
1067                         url_base = API_BASEURL_ADDON_CONTRIB
1068                     elif location[0].startswith("addons" + os.sep):
1069                         url_base = API_BASEURL_ADDON
1070                     else:
1071                         url_base = API_BASEURL
1072
1073                     fw("   :file: `%s <%s/%s>`_:%d\n\n" % (location[0], url_base, location[0], location[1]))
1074
1075             file.close()
1076
1077     if "bpy.ops" not in EXCLUDE_MODULES:
1078         write_ops()
1079
1080
1081 def rna2sphinx(BASEPATH):
1082
1083     try:
1084         os.mkdir(BASEPATH)
1085     except:
1086         pass
1087
1088     # conf.py - empty for now
1089     filepath = os.path.join(BASEPATH, "conf.py")
1090     file = open(filepath, "w", encoding="utf-8")
1091     fw = file.write
1092
1093     version_string = ".".join(str(v) for v in bpy.app.version)
1094     if bpy.app.build_revision != "Unknown":
1095         version_string = version_string + " r" + bpy.app.build_revision
1096
1097     version_string_fp = "_".join(str(v) for v in bpy.app.version)
1098
1099     if bpy.app.version_cycle == "release":
1100         version_string_pdf = "%s%s_release" % ("_".join(str(v) for v in bpy.app.version[:2]), bpy.app.version_char)
1101     else:
1102         version_string_pdf = version_string_fp
1103
1104     fw("project = 'Blender'\n")
1105     # fw("master_doc = 'index'\n")
1106     fw("copyright = u'Blender Foundation'\n")
1107     fw("version = '%s - API'\n" % version_string)
1108     fw("release = '%s - API'\n" % version_string)
1109
1110     # until we get a theme for 'Naiad'
1111     if 0:
1112         fw("html_theme = 'blender-org'\n")
1113         fw("html_theme_path = ['../']\n")
1114
1115         # copied with the theme, exclude else we get an error [#28873]
1116         fw("html_favicon = 'favicon.ico'\n")
1117
1118     # not helpful since the source us generated, adds to upload size.
1119     fw("html_copy_source = False\n")
1120     fw("\n")
1121     # needed for latex, pdf gen
1122     fw("latex_documents = [ ('contents', 'contents.tex', 'Blender Index', 'Blender Foundation', 'manual'), ]\n")
1123     fw("latex_paper_size = 'a4paper'\n")
1124     file.close()
1125
1126     # main page needed for sphinx (index.html)
1127     filepath = os.path.join(BASEPATH, "contents.rst")
1128     file = open(filepath, "w", encoding="utf-8")
1129     fw = file.write
1130
1131     fw("%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n")
1132     fw(" Blender Documentation contents\n")
1133     fw("%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n")
1134     fw("\n")
1135     fw("Welcome, this document is an API reference for Blender %s. built %s.\n" % (version_string, bpy.app.build_date))
1136     fw("\n")
1137     fw("`A PDF version of this document is also available <blender_python_reference_%s.pdf>`_\n" % version_string_pdf)
1138
1139     fw("\n")
1140
1141     if not EXCLUDE_INFO_DOCS:
1142         fw("============================\n")
1143         fw("Blender/Python Documentation\n")
1144         fw("============================\n")
1145         fw("\n")
1146         fw("\n")
1147         fw(".. toctree::\n")
1148         fw("   :maxdepth: 1\n\n")
1149         for info, info_desc in INFO_DOCS:
1150             fw("   %s <%s>\n\n" % (info_desc, info))
1151         fw("\n")
1152
1153     fw("===================\n")
1154     fw("Application Modules\n")
1155     fw("===================\n")
1156     fw("\n")
1157     fw(".. toctree::\n")
1158     fw("   :maxdepth: 1\n\n")
1159     if "bpy.context" not in EXCLUDE_MODULES:
1160         fw("   bpy.context.rst\n\n")  # note: not actually a module
1161     if "bpy.data" not in EXCLUDE_MODULES:
1162         fw("   bpy.data.rst\n\n")  # note: not actually a module
1163     if "bpy.ops" not in EXCLUDE_MODULES:
1164         fw("   bpy.ops.rst\n\n")
1165     if "bpy.types" not in EXCLUDE_MODULES:
1166         fw("   bpy.types.rst\n\n")
1167
1168     # py modules
1169     if "bpy.utils" not in EXCLUDE_MODULES:
1170         fw("   bpy.utils.rst\n\n")
1171     if "bpy.path" not in EXCLUDE_MODULES:
1172         fw("   bpy.path.rst\n\n")
1173     if "bpy.app" not in EXCLUDE_MODULES:
1174         fw("   bpy.app.rst\n\n")
1175     if "bpy.app.handlers" not in EXCLUDE_MODULES:
1176         fw("   bpy.app.handlers.rst\n\n")
1177
1178     # C modules
1179     if "bpy.props" not in EXCLUDE_MODULES:
1180         fw("   bpy.props.rst\n\n")
1181
1182     fw("==================\n")
1183     fw("Standalone Modules\n")
1184     fw("==================\n")
1185     fw("\n")
1186     fw(".. toctree::\n")
1187     fw("   :maxdepth: 1\n\n")
1188
1189     if "mathutils" not in EXCLUDE_MODULES:
1190         fw("   mathutils.rst\n\n")
1191     if "mathutils.geometry" not in EXCLUDE_MODULES:
1192         fw("   mathutils.geometry.rst\n\n")
1193     if "bgl" not in EXCLUDE_MODULES:
1194         fw("   bgl.rst\n\n")
1195     if "blf" not in EXCLUDE_MODULES:
1196         fw("   blf.rst\n\n")
1197     if "gpu" not in EXCLUDE_MODULES:
1198         fw("   gpu.rst\n\n")
1199     if "aud" not in EXCLUDE_MODULES:
1200         fw("   aud.rst\n\n")
1201     if "bpy_extras" not in EXCLUDE_MODULES:
1202         fw("   bpy_extras.rst\n\n")
1203
1204     # game engine
1205     if "bge" not in EXCLUDE_MODULES:
1206         fw("===================\n")
1207         fw("Game Engine Modules\n")
1208         fw("===================\n")
1209         fw("\n")
1210         fw(".. toctree::\n")
1211         fw("   :maxdepth: 1\n\n")
1212         fw("   bge.types.rst\n\n")
1213         fw("   bge.logic.rst\n\n")
1214         fw("   bge.render.rst\n\n")
1215         fw("   bge.texture.rst\n\n")
1216         fw("   bge.events.rst\n\n")
1217         fw("   bge.constraints.rst\n\n")
1218
1219     # rna generated change log
1220     fw("========\n")
1221     fw("API Info\n")
1222     fw("========\n")
1223     fw("\n")
1224     fw(".. toctree::\n")
1225     fw("   :maxdepth: 1\n\n")
1226     fw("   change_log.rst\n\n")
1227
1228     fw("\n")
1229     fw("\n")
1230     fw(".. note:: The Blender Python API has areas which are still in development.\n")
1231     fw("   \n")
1232     fw("   The following areas are subject to change.\n")
1233     fw("      * operator behavior, names and arguments\n")
1234     fw("      * mesh creation and editing functions\n")
1235     fw("   \n")
1236     fw("   These parts of the API are relatively stable and are unlikely to change significantly\n")
1237     fw("      * data API, access to attributes of blender data such as mesh verts, material color, timeline frames and scene objects\n")
1238     fw("      * user interface functions for defining buttons, creation of menus, headers, panels\n")
1239     fw("      * render engine integration\n")
1240     fw("      * modules: bgl, mathutils & game engine.\n")
1241     fw("\n")
1242
1243     file.close()
1244
1245     # internal modules
1246     if "bpy.ops" not in EXCLUDE_MODULES:
1247         filepath = os.path.join(BASEPATH, "bpy.ops.rst")
1248         file = open(filepath, "w", encoding="utf-8")
1249         fw = file.write
1250         fw("Operators (bpy.ops)\n")
1251         fw("===================\n\n")
1252         write_example_ref("", fw, "bpy.ops")
1253         fw(".. toctree::\n")
1254         fw("   :glob:\n\n")
1255         fw("   bpy.ops.*\n\n")
1256         file.close()
1257
1258     if "bpy.types" not in EXCLUDE_MODULES:
1259         filepath = os.path.join(BASEPATH, "bpy.types.rst")
1260         file = open(filepath, "w", encoding="utf-8")
1261         fw = file.write
1262         fw("Types (bpy.types)\n")
1263         fw("=================\n\n")
1264         fw(".. toctree::\n")
1265         fw("   :glob:\n\n")
1266         fw("   bpy.types.*\n\n")
1267         file.close()
1268
1269     if "bpy.data" not in EXCLUDE_MODULES:
1270         # not actually a module, only write this file so we
1271         # can reference in the TOC
1272         filepath = os.path.join(BASEPATH, "bpy.data.rst")
1273         file = open(filepath, "w", encoding="utf-8")
1274         fw = file.write
1275         fw("Data Access (bpy.data)\n")
1276         fw("======================\n\n")
1277         fw(".. module:: bpy\n")
1278         fw("\n")
1279         fw("This module is used for all blender/python access.\n")
1280         fw("\n")
1281         fw(".. data:: data\n")
1282         fw("\n")
1283         fw("   Access to blenders internal data\n")
1284         fw("\n")
1285         fw("   :type: :class:`bpy.types.BlendData`\n")
1286         fw("\n")
1287         fw(".. literalinclude:: ../examples/bpy.data.py\n")
1288         file.close()
1289
1290     EXAMPLE_SET_USED.add("bpy.data")
1291
1292     module = None
1293
1294     if "bpy.context" not in EXCLUDE_MODULES:
1295         # one of a kind, context doc (uses ctypes to extract info!)
1296         pycontext2sphinx(BASEPATH)
1297
1298     # python modules
1299     if "bpy.utils" not in EXCLUDE_MODULES:
1300         from bpy import utils as module
1301         pymodule2sphinx(BASEPATH, "bpy.utils", module, "Utilities")
1302
1303     if "bpy.path" not in EXCLUDE_MODULES:
1304         from bpy import path as module
1305         pymodule2sphinx(BASEPATH, "bpy.path", module, "Path Utilities")
1306
1307     if "bpy_extras" not in EXCLUDE_MODULES:
1308         import bpy_extras as module
1309         pymodule2sphinx(BASEPATH, "bpy_extras", module, "Extra Utilities")
1310
1311     # C modules
1312     if "bpy.app" not in EXCLUDE_MODULES:
1313         from bpy import app as module
1314         pymodule2sphinx(BASEPATH, "bpy.app", module, "Application Data")
1315
1316     if "bpy.app.handlers" not in EXCLUDE_MODULES:
1317         from bpy.app import handlers as module
1318         pymodule2sphinx(BASEPATH, "bpy.app.handlers", module, "Application Handlers")
1319
1320     if "bpy.props" not in EXCLUDE_MODULES:
1321         from bpy import props as module
1322         pymodule2sphinx(BASEPATH, "bpy.props", module, "Property Definitions")
1323
1324     if "mathutils" not in EXCLUDE_MODULES:
1325         import mathutils as module
1326         pymodule2sphinx(BASEPATH, "mathutils", module, "Math Types & Utilities")
1327
1328     if "mathutils.geometry" not in EXCLUDE_MODULES:
1329         import mathutils.geometry as module
1330         pymodule2sphinx(BASEPATH, "mathutils.geometry", module, "Geometry Utilities")
1331
1332     if "blf" not in EXCLUDE_MODULES:
1333         import blf as module
1334         pymodule2sphinx(BASEPATH, "blf", module, "Font Drawing")
1335
1336     if "bgl" not in EXCLUDE_MODULES:
1337         #import bgl as module
1338         #pymodule2sphinx(BASEPATH, "bgl", module, "Blender OpenGl wrapper")
1339         #del module
1340         import shutil
1341         shutil.copy2(os.path.join(BASEPATH, "..", "rst", "bgl.rst"), BASEPATH)
1342
1343     if "gpu" not in EXCLUDE_MODULES:
1344         #import gpu as module
1345         #pymodule2sphinx(BASEPATH, "gpu", module, "GPU Shader Module")
1346         #del module
1347         import shutil
1348         shutil.copy2(os.path.join(BASEPATH, "..", "rst", "gpu.rst"), BASEPATH)
1349
1350     if "aud" not in EXCLUDE_MODULES:
1351         import aud as module
1352         pymodule2sphinx(BASEPATH, "aud", module, "Audio System")
1353     del module
1354
1355     ## game engine
1356     import shutil
1357     # copy2 keeps time/date stamps
1358     if "bge" not in EXCLUDE_MODULES:
1359         shutil.copy2(os.path.join(BASEPATH, "..", "rst", "bge.types.rst"), BASEPATH)
1360         shutil.copy2(os.path.join(BASEPATH, "..", "rst", "bge.logic.rst"), BASEPATH)
1361         shutil.copy2(os.path.join(BASEPATH, "..", "rst", "bge.render.rst"), BASEPATH)
1362         shutil.copy2(os.path.join(BASEPATH, "..", "rst", "bge.texture.rst"), BASEPATH)
1363         shutil.copy2(os.path.join(BASEPATH, "..", "rst", "bge.events.rst"), BASEPATH)
1364         shutil.copy2(os.path.join(BASEPATH, "..", "rst", "bge.constraints.rst"), BASEPATH)
1365
1366     shutil.copy2(os.path.join(BASEPATH, "..", "rst", "change_log.rst"), BASEPATH)
1367
1368     if not EXCLUDE_INFO_DOCS:
1369         for info, info_desc in INFO_DOCS:
1370             shutil.copy2(os.path.join(BASEPATH, "..", "rst", info), BASEPATH)
1371
1372     if 0:
1373         filepath = os.path.join(BASEPATH, "bpy.rst")
1374         file = open(filepath, "w", encoding="utf-8")
1375         fw = file.write
1376
1377         fw("\n")
1378
1379         title = ":mod:`bpy` --- Blender Python Module"
1380
1381         write_title(fw, title, "=")
1382
1383         fw(".. module:: bpy.types\n\n")
1384         file.close()
1385
1386     # bpy.types and bpy.ops
1387     pyrna2sphinx(BASEPATH)
1388
1389     file.close()
1390
1391
1392 def main():
1393     import shutil
1394
1395     script_dir = os.path.dirname(__file__)
1396     path_in = os.path.join(script_dir, "sphinx-in")
1397     path_out = os.path.join(script_dir, "sphinx-out")
1398     path_examples = os.path.join(script_dir, "examples")
1399     # only for partial updates
1400     path_in_tmp = path_in + "-tmp"
1401
1402     if not os.path.exists(path_in):
1403         os.mkdir(path_in)
1404
1405     for f in os.listdir(path_examples):
1406         if f.endswith(".py"):
1407             EXAMPLE_SET.add(os.path.splitext(f)[0])
1408
1409     # only for full updates
1410     if _BPY_FULL_REBUILD:
1411         shutil.rmtree(path_in, True)
1412         shutil.rmtree(path_out, True)
1413     else:
1414         # write here, then move
1415         shutil.rmtree(path_in_tmp, True)
1416
1417     rna2sphinx(path_in_tmp)
1418
1419     if not _BPY_FULL_REBUILD:
1420         import filecmp
1421
1422         # now move changed files from 'path_in_tmp' --> 'path_in'
1423         file_list_path_in = set(os.listdir(path_in))
1424         file_list_path_in_tmp = set(os.listdir(path_in_tmp))
1425
1426         # remove deprecated files that have been removed.
1427         for f in sorted(file_list_path_in):
1428             if f not in file_list_path_in_tmp:
1429                 print("\tdeprecated: %s" % f)
1430                 os.remove(os.path.join(path_in, f))
1431
1432         # freshen with new files.
1433         for f in sorted(file_list_path_in_tmp):
1434             f_from = os.path.join(path_in_tmp, f)
1435             f_to = os.path.join(path_in, f)
1436
1437             do_copy = True
1438             if f in file_list_path_in:
1439                 if filecmp.cmp(f_from, f_to):
1440                     do_copy = False
1441
1442             if do_copy:
1443                 print("\tupdating: %s" % f)
1444                 shutil.copy(f_from, f_to)
1445             '''else:
1446                 print("\tkeeping: %s" % f) # eh, not that useful'''
1447
1448     EXAMPLE_SET_UNUSED = EXAMPLE_SET - EXAMPLE_SET_USED
1449     if EXAMPLE_SET_UNUSED:
1450         print("\nUnused examples found in '%s'..." % path_examples)
1451         for f in EXAMPLE_SET_UNUSED:
1452             print("    %s.py" % f)
1453         print("  %d total\n" % len(EXAMPLE_SET_UNUSED))
1454
1455     import sys
1456     sys.exit()
1457
1458 if __name__ == '__main__':
1459     main()