Sphinx RNA API changelog generator.
[blender-staging.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 = "https://svn.blender.org/svnroot/bf-blender/trunk/blender/release/scripts"
814
815         op_modules = {}
816         for op in ops.values():
817             op_modules.setdefault(op.module_name, []).append(op)
818         del op
819
820         for op_module_name, ops_mod in op_modules.items():
821             filepath = os.path.join(BASEPATH, "bpy.ops.%s.rst" % op_module_name)
822             file = open(filepath, "w")
823             fw = file.write
824
825             title = "%s Operators" % op_module_name.replace("_", " ").title()
826
827             write_title(fw, title, "=")
828
829             fw(".. module:: bpy.ops.%s\n\n" % op_module_name)
830
831             ops_mod.sort(key=lambda op: op.func_name)
832
833             for op in ops_mod:
834                 args_str = ", ".join(prop.get_arg_default(force=True) for prop in op.args)
835                 fw(".. function:: %s(%s)\n\n" % (op.func_name, args_str))
836
837                 # if the description isn't valid, we output the standard warning
838                 # with a link to the wiki so that people can help
839                 if not op.description or op.description == "(undocumented operator)":
840                     operator_description = undocumented_message('bpy.ops', op.module_name, op.func_name)
841                 else:
842                     operator_description = op.description
843
844                 fw("   %s\n\n" % operator_description)
845                 for prop in op.args:
846                     write_param("   ", fw, prop)
847                 if op.args:
848                     fw("\n")
849
850                 location = op.get_location()
851                 if location != (None, None):
852                     fw("   :file: `%s <%s/%s>`_:%d\n\n" % (location[0], API_BASEURL, location[0], location[1]))
853
854             file.close()
855
856     if "bpy.ops" not in EXCLUDE_MODULES:
857         write_ops()
858
859
860 def rna2sphinx(BASEPATH):
861
862     try:
863         os.mkdir(BASEPATH)
864     except:
865         pass
866
867     # conf.py - empty for now
868     filepath = os.path.join(BASEPATH, "conf.py")
869     file = open(filepath, "w")
870     fw = file.write
871
872     version_string = ".".join(str(v) for v in bpy.app.version)
873     if bpy.app.build_revision != "Unknown":
874         version_string = version_string + " r" + bpy.app.build_revision
875
876     # for use with files
877     version_string_fp = "_".join(str(v) for v in bpy.app.version)
878
879     fw("project = 'Blender'\n")
880     # fw("master_doc = 'index'\n")
881     fw("copyright = u'Blender Foundation'\n")
882     fw("version = '%s - UNSTABLE API'\n" % version_string)
883     fw("release = '%s - UNSTABLE API'\n" % version_string)
884     fw("html_theme = 'blender-org'\n")
885     fw("html_theme_path = ['../']\n")
886     fw("html_favicon = 'favicon.ico'\n")
887     # not helpful since the source us generated, adds to upload size.
888     fw("html_copy_source = False\n")
889     fw("\n")
890     # needed for latex, pdf gen
891     fw("latex_documents = [ ('contents', 'contents.tex', 'Blender Index', 'Blender Foundation', 'manual'), ]\n")
892     fw("latex_paper_size = 'a4paper'\n")
893     file.close()
894
895     # main page needed for sphinx (index.html)
896     filepath = os.path.join(BASEPATH, "contents.rst")
897     file = open(filepath, "w")
898     fw = file.write
899
900     fw("%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n")
901     fw(" Blender Documentation contents\n")
902     fw("%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n")
903     fw("\n")
904     fw("This document is an API reference for Blender %s. built %s.\n" % (version_string, bpy.app.build_date))
905     fw("\n")
906     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")
907     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")
908     fw("\n")
909     fw("`A PDF version of this document is also available <blender_python_reference_%s.pdf>`_\n" % version_string_fp)
910     fw("\n")
911     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")
912     fw("   \n")
913     fw("   The following areas are subject to change.\n")
914     fw("      * operator names and arguments\n")
915     fw("      * render api\n")
916     fw("      * function calls with the data api (any function calls with values accessed from bpy.data), including functions for importing and exporting meshes\n")
917     fw("      * class registration (Operator, Panels, Menus, Headers)\n")
918     fw("      * modules: bpy.props, blf)\n")
919     fw("      * members in the bpy.context have to be reviewed\n")
920     fw("      * python defined modal operators, especially drawing callbacks are highly experemental\n")
921     fw("   \n")
922     fw("   These parts of the API are relatively stable and are unlikely to change significantly\n")
923     fw("      * data API, access to attributes of blender data such as mesh verts, material color, timeline frames and scene objects\n")
924     fw("      * user interface functions for defining buttons, creation of menus, headers, panels\n")
925     fw("      * modules: bgl and mathutils\n")
926     fw("      * game engine modules\n")
927     fw("\n")
928
929     fw("===================\n")
930     fw("Application Modules\n")
931     fw("===================\n")
932     fw("\n")
933     fw(".. toctree::\n")
934     fw("   :maxdepth: 1\n\n")
935     if "bpy.context" not in EXCLUDE_MODULES:
936         fw("   bpy.context.rst\n\n")  # note: not actually a module
937     if "bpy.data" not in EXCLUDE_MODULES:
938         fw("   bpy.data.rst\n\n")  # note: not actually a module
939     if "bpy.ops" not in EXCLUDE_MODULES:
940         fw("   bpy.ops.rst\n\n")
941     if "bpy.types" not in EXCLUDE_MODULES:
942         fw("   bpy.types.rst\n\n")
943
944     # py modules
945     if "bpy.utils" not in EXCLUDE_MODULES:
946         fw("   bpy.utils.rst\n\n")
947     if "bpy.path" not in EXCLUDE_MODULES:
948         fw("   bpy.path.rst\n\n")
949     if "bpy.app" not in EXCLUDE_MODULES:
950         fw("   bpy.app.rst\n\n")
951
952     # C modules
953     if "bpy.props" not in EXCLUDE_MODULES:
954         fw("   bpy.props.rst\n\n")
955
956     fw("==================\n")
957     fw("Standalone Modules\n")
958     fw("==================\n")
959     fw("\n")
960     fw(".. toctree::\n")
961     fw("   :maxdepth: 1\n\n")
962
963     if "mathutils" not in EXCLUDE_MODULES:
964         fw("   mathutils.rst\n\n")
965     if "mathutils.geometry" not in EXCLUDE_MODULES:
966         fw("   mathutils.geometry.rst\n\n")
967     # XXX TODO
968     #fw("   bgl.rst\n\n")
969     if "blf" not in EXCLUDE_MODULES:
970         fw("   blf.rst\n\n")
971     if "aud" not in EXCLUDE_MODULES:
972         fw("   aud.rst\n\n")
973
974     # game engine
975     if "bge" not in EXCLUDE_MODULES:
976         fw("===================\n")
977         fw("Game Engine Modules\n")
978         fw("===================\n")
979         fw("\n")
980         fw(".. toctree::\n")
981         fw("   :maxdepth: 1\n\n")
982         fw("   bge.types.rst\n\n")
983         fw("   bge.logic.rst\n\n")
984         fw("   bge.render.rst\n\n")
985         fw("   bge.events.rst\n\n")
986
987     # rna generated change log
988     fw("========\n")
989     fw("API Info\n")
990     fw("========\n")
991     fw("\n")
992     fw(".. toctree::\n")
993     fw("   :maxdepth: 1\n\n")
994     fw("   change_log.rst\n\n")
995
996     file.close()
997
998     # internal modules
999     if "bpy.ops" not in EXCLUDE_MODULES:
1000         filepath = os.path.join(BASEPATH, "bpy.ops.rst")
1001         file = open(filepath, "w")
1002         fw = file.write
1003         fw("Operators (bpy.ops)\n")
1004         fw("===================\n\n")
1005         write_example_ref("", fw, "bpy.ops")
1006         fw(".. toctree::\n")
1007         fw("   :glob:\n\n")
1008         fw("   bpy.ops.*\n\n")
1009         file.close()
1010
1011     if "bpy.types" not in EXCLUDE_MODULES:
1012         filepath = os.path.join(BASEPATH, "bpy.types.rst")
1013         file = open(filepath, "w")
1014         fw = file.write
1015         fw("Types (bpy.types)\n")
1016         fw("=================\n\n")
1017         fw(".. toctree::\n")
1018         fw("   :glob:\n\n")
1019         fw("   bpy.types.*\n\n")
1020         file.close()
1021
1022     if "bpy.data" not in EXCLUDE_MODULES:
1023         # not actually a module, only write this file so we
1024         # can reference in the TOC
1025         filepath = os.path.join(BASEPATH, "bpy.data.rst")
1026         file = open(filepath, "w")
1027         fw = file.write
1028         fw("Data Access (bpy.data)\n")
1029         fw("======================\n\n")
1030         fw(".. module:: bpy\n")
1031         fw("\n")
1032         fw("This module is used for all blender/python access.\n")
1033         fw("\n")
1034         fw(".. data:: data\n")
1035         fw("\n")
1036         fw("   Access to blenders internal data\n")
1037         fw("\n")
1038         fw("   :type: :class:`bpy.types.BlendData`\n")
1039         fw("\n")
1040         fw(".. literalinclude:: ../examples/bpy.data.py\n")
1041         file.close()
1042
1043     EXAMPLE_SET_USED.add("bpy.data")
1044
1045     module = None
1046
1047     if "bpy.context" not in EXCLUDE_MODULES:
1048         # one of a kind, context doc (uses ctypes to extract info!)
1049         pycontext2sphinx(BASEPATH)
1050
1051     # python modules
1052     if "bpy.utils" not in EXCLUDE_MODULES:
1053         from bpy import utils as module
1054         pymodule2sphinx(BASEPATH, "bpy.utils", module, "Utilities (bpy.utils)")
1055
1056     if "bpy.path" not in EXCLUDE_MODULES:
1057         from bpy import path as module
1058         pymodule2sphinx(BASEPATH, "bpy.path", module, "Path Utilities (bpy.path)")
1059
1060     # C modules
1061     if "bpy.app" not in EXCLUDE_MODULES:
1062         from bpy import app as module
1063         pymodule2sphinx(BASEPATH, "bpy.app", module, "Application Data (bpy.app)")
1064
1065     if "bpy.props" not in EXCLUDE_MODULES:
1066         from bpy import props as module
1067         pymodule2sphinx(BASEPATH, "bpy.props", module, "Property Definitions (bpy.props)")
1068
1069     if "mathutils" not in EXCLUDE_MODULES:
1070         import mathutils as module
1071         pymodule2sphinx(BASEPATH, "mathutils", module, "Math Types & Utilities (mathutils)")
1072
1073     if "mathutils.geometry" not in EXCLUDE_MODULES:
1074         import mathutils.geometry as module
1075         pymodule2sphinx(BASEPATH, "mathutils.geometry", module, "Geometry Utilities (mathutils.geometry)")
1076
1077     if "mathutils.geometry" not in EXCLUDE_MODULES:
1078         import blf as module
1079         pymodule2sphinx(BASEPATH, "blf", module, "Font Drawing (blf)")
1080
1081     # XXX TODO
1082     #import bgl as module
1083     #pymodule2sphinx(BASEPATH, "bgl", module, "Blender OpenGl wrapper (bgl)")
1084     #del module
1085
1086     if "aud" not in EXCLUDE_MODULES:
1087         import aud as module
1088         pymodule2sphinx(BASEPATH, "aud", module, "Audio System (aud)")
1089     del module
1090
1091     ## game engine
1092     import shutil
1093     # copy2 keeps time/date stamps
1094     if "bge" not in EXCLUDE_MODULES:
1095         shutil.copy2(os.path.join(BASEPATH, "..", "rst", "bge.types.rst"), BASEPATH)
1096         shutil.copy2(os.path.join(BASEPATH, "..", "rst", "bge.logic.rst"), BASEPATH)
1097         shutil.copy2(os.path.join(BASEPATH, "..", "rst", "bge.render.rst"), BASEPATH)
1098         shutil.copy2(os.path.join(BASEPATH, "..", "rst", "bge.events.rst"), BASEPATH)
1099
1100     shutil.copy2(os.path.join(BASEPATH, "..", "rst", "change_log.rst"), BASEPATH)
1101
1102     if 0:
1103         filepath = os.path.join(BASEPATH, "bpy.rst")
1104         file = open(filepath, "w")
1105         fw = file.write
1106
1107         fw("\n")
1108
1109         title = ":mod:`bpy` --- Blender Python Module"
1110
1111         write_title(fw, title, "=")
1112
1113         fw(".. module:: bpy.types\n\n")
1114         file.close()
1115
1116     # bpy.types and bpy.ops
1117     pyrna2sphinx(BASEPATH)
1118
1119     file.close()
1120
1121
1122 def main():
1123     import bpy
1124     if 'bpy' not in dir():
1125         print("\nError, this script must run from inside blender2.5")
1126         print(script_help_msg)
1127     else:
1128         import shutil
1129
1130         script_dir = os.path.dirname(__file__)
1131         path_in = os.path.join(script_dir, "sphinx-in")
1132         path_out = os.path.join(script_dir, "sphinx-out")
1133         path_examples = os.path.join(script_dir, "examples")
1134         # only for partial updates
1135         path_in_tmp = path_in + "-tmp"
1136
1137         if not os.path.exists(path_in):
1138             os.mkdir(path_in)
1139
1140         for f in os.listdir(path_examples):
1141             if f.endswith(".py"):
1142                 EXAMPLE_SET.add(os.path.splitext(f)[0])
1143
1144         # only for full updates
1145         if _BPY_FULL_REBUILD:
1146             shutil.rmtree(path_in, True)
1147             shutil.rmtree(path_out, True)
1148         else:
1149             # write here, then move
1150             shutil.rmtree(path_in_tmp, True)
1151
1152         rna2sphinx(path_in_tmp)
1153
1154         if not _BPY_FULL_REBUILD:
1155             import filecmp
1156
1157             # now move changed files from 'path_in_tmp' --> 'path_in'
1158             file_list_path_in = set(os.listdir(path_in))
1159             file_list_path_in_tmp = set(os.listdir(path_in_tmp))
1160
1161             # remove deprecated files that have been removed.
1162             for f in sorted(file_list_path_in):
1163                 if f not in file_list_path_in_tmp:
1164                     print("\tdeprecated: %s" % f)
1165                     os.remove(os.path.join(path_in, f))
1166
1167             # freshen with new files.
1168             for f in sorted(file_list_path_in_tmp):
1169                 f_from = os.path.join(path_in_tmp, f)
1170                 f_to = os.path.join(path_in, f)
1171
1172                 do_copy = True
1173                 if f in file_list_path_in:
1174                     if filecmp.cmp(f_from, f_to):
1175                         do_copy = False
1176
1177                 if do_copy:
1178                     print("\tupdating: %s" % f)
1179                     shutil.copy(f_from, f_to)
1180                 '''else:
1181                     print("\tkeeping: %s" % f) # eh, not that useful'''
1182
1183         EXAMPLE_SET_UNUSED = EXAMPLE_SET - EXAMPLE_SET_USED
1184         if EXAMPLE_SET_UNUSED:
1185             print("\nUnused examples found in '%s'..." % path_examples)
1186             for f in EXAMPLE_SET_UNUSED:
1187                 print("    %s.py" % f)
1188             print("  %d total\n" % len(EXAMPLE_SET_UNUSED))
1189
1190     import sys
1191     sys.exit()
1192
1193 if __name__ == '__main__':
1194     main()