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