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