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