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