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