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