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