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