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