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