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