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