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