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