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