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