Merged changes in the trunk up to revision 31190.
[blender.git] / source / blender / python / doc / 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 script_help_msg = '''
22 Usage,
23 run this script from blenders root path once you have compiled blender
24     ./blender.bin -b -P /b/source/blender/python/doc/sphinx_doc_gen.py
25
26 This will generate python files in "./source/blender/python/doc/sphinx-in"
27 Generate html docs by running...
28     
29     sphinx-build source/blender/python/doc/sphinx-in source/blender/python/doc/sphinx-out
30
31
32 For PDF generation
33
34     sphinx-build -b latex source/blender/python/doc/sphinx-in source/blender/python/doc/sphinx-out
35     cd source/blender/python/doc/sphinx-out
36     make
37 '''
38
39 # import rpdb2; rpdb2.start_embedded_debugger('test')
40
41 import os
42 import inspect
43 import bpy
44 import rna_info
45 reload(rna_info)
46
47 # lame, python wont give some access
48 MethodDescriptorType = type(dict.get)
49 GetSetDescriptorType = type(int.real)
50 StaticMethodType = type(staticmethod(lambda: None))
51
52 EXAMPLE_SET = set()
53 EXAMPLE_SET_USED = set()
54
55 _BPY_STRUCT_FAKE = "bpy_struct"
56 _BPY_FULL_REBUILD = False
57
58 def undocumented_message(module_name, type_name, identifier):
59     message = "Undocumented (`contribute " \
60         "<http://wiki.blender.org/index.php/Dev:2.5/Py/API/Documentation/Contribute" \
61         "?action=edit&section=new&preload=Dev:2.5/Py/API/Documentation/Contribute/Howto-message" \
62         "&preloadtitle=%s.%s.%s>`_)\n\n" % (module_name, type_name, identifier)
63     return message
64
65
66 def range_str(val):
67     '''
68     Converts values to strings for the range directive.
69     (unused function it seems)
70     '''
71     if val < -10000000: return '-inf'
72     if val >  10000000: return 'inf'
73     if type(val)==float:
74         return '%g'  % val
75     else:
76         return str(val)
77
78
79 def write_example_ref(ident, fw, example_id, ext="py"):
80     if example_id in EXAMPLE_SET:
81         fw("%s.. literalinclude:: ../examples/%s.%s\n\n" % (ident, example_id, ext))
82         EXAMPLE_SET_USED.add(example_id)
83     else:
84         if bpy.app.debug:
85             print("\tskipping example:", example_id)
86
87
88 def write_indented_lines(ident, fn, text, strip=True):
89     '''
90     Apply same indentation to all lines in a multilines text.
91     '''
92     if text is None:
93         return
94     for l in text.split("\n"):
95         if strip:
96             fn(ident + l.strip() + "\n")
97         else:
98             fn(ident + l + "\n")
99
100
101 def pymethod2sphinx(ident, fw, identifier, py_func):
102     '''
103     class method to sphinx
104     '''
105     arg_str = inspect.formatargspec(*inspect.getargspec(py_func))
106     if arg_str.startswith("(self, "):
107         arg_str = "(" + arg_str[7:]
108         func_type = "method"
109     elif arg_str.startswith("(cls, "):
110         arg_str = "(" + arg_str[6:]
111         func_type = "classmethod"
112     else:
113         func_type = "staticmethod"
114
115     fw(ident + ".. %s:: %s%s\n\n" % (func_type, identifier, arg_str))
116     if py_func.__doc__:
117         write_indented_lines(ident + "   ", fw, py_func.__doc__)
118         fw("\n")
119
120
121 def pyfunc2sphinx(ident, fw, identifier, py_func, is_class=True):
122     '''
123     function or class method to sphinx
124     '''
125     arg_str = inspect.formatargspec(*inspect.getargspec(py_func))
126
127     if not is_class:
128         func_type = "function"
129         
130         # ther rest are class methods
131     elif arg_str.startswith("(self, "):
132         arg_str = "(" + arg_str[7:]
133         func_type = "method"
134     elif arg_str.startswith("(cls, "):
135         arg_str = "(" + arg_str[6:]
136         func_type = "classmethod"
137     else:
138         func_type = "staticmethod"
139
140     fw(ident + ".. %s:: %s%s\n\n" % (func_type, identifier, arg_str))
141     if py_func.__doc__:
142         write_indented_lines(ident + "   ", fw, py_func.__doc__.strip())
143         fw("\n")
144
145
146 def py_descr2sphinx(ident, fw, descr, module_name, type_name, identifier):    
147
148     doc = descr.__doc__
149     if not doc:
150         doc = undocumented_message(module_name, type_name, identifier)
151     
152     if type(descr) == GetSetDescriptorType:
153         fw(ident + ".. attribute:: %s\n\n" % identifier)
154         write_indented_lines(ident + "   ", fw, doc, False)
155     elif type(descr) == MethodDescriptorType: # GetSetDescriptorType's are not documented yet
156         write_indented_lines(ident, fw, doc, False)
157     else:
158         raise TypeError("type was not GetSetDescriptorType or MethodDescriptorType")
159
160     write_example_ref(ident, fw, module_name + "." + type_name + "." + identifier)
161     fw("\n")
162
163
164 def py_c_func2sphinx(ident, fw, module_name, type_name, identifier, py_func, is_class=True):
165     '''
166     c defined function to sphinx.
167     '''
168     
169     # dump the docstring, assume its formatted correctly
170     if py_func.__doc__:
171         write_indented_lines(ident, fw, py_func.__doc__, False)
172         fw("\n")
173     else:
174         fw(ident + ".. function:: %s()\n\n" % identifier)
175         fw(ident + "   " + undocumented_message(module_name, type_name, identifier))
176
177
178 def pyprop2sphinx(ident, fw, identifier, py_prop):
179     '''
180     python property to sphinx
181     '''
182     # readonly properties use "data" directive, variables use "attribute" directive
183     if py_prop.fset is None:
184         fw(ident + ".. data:: %s\n\n" % identifier)
185     else:
186         fw(ident + ".. attribute:: %s\n\n" % identifier)
187     write_indented_lines(ident + "   ", fw, py_prop.__doc__)
188     if py_prop.fset is None:
189         fw(ident + "   (readonly)\n\n")
190
191
192 def pymodule2sphinx(BASEPATH, module_name, module, title):
193     import types
194     attribute_set = set()
195     filepath = os.path.join(BASEPATH, module_name + ".rst")
196     
197     file = open(filepath, "w")
198
199     fw = file.write
200     
201     fw(title + "\n")
202     fw(("=" * len(title)) + "\n\n")
203     
204     fw(".. module:: %s\n\n" % module_name)
205     
206     if module.__doc__:
207         # Note, may contain sphinx syntax, dont mangle!
208         fw(module.__doc__.strip())
209         fw("\n\n")
210         
211     write_example_ref("", fw, module_name)
212     
213     # write members of the module
214     # only tested with PyStructs which are not exactly modules
215     for key, descr in sorted(type(module).__dict__.items()):
216         if type(descr) == types.MemberDescriptorType:
217             if descr.__doc__:
218                 fw(".. data:: %s\n\n" % key)
219                 write_indented_lines("   ", fw, descr.__doc__, False)
220                 attribute_set.add(key)
221                 fw("\n")
222     del key, descr
223     
224     classes = []
225
226     for attribute in sorted(dir(module)):
227         if not attribute.startswith("_"):
228
229             if attribute in attribute_set:
230                 continue
231
232             if attribute.startswith("n_"): # annoying exception, needed for bpy.app
233                 continue
234             
235             value = getattr(module, attribute)
236
237             value_type = type(value)
238
239             if value_type == types.FunctionType:
240                 pyfunc2sphinx("", fw, attribute, value, is_class=False)
241             elif value_type in (types.BuiltinMethodType, types.BuiltinFunctionType): # both the same at the moment but to be future proof
242                 # note: can't get args from these, so dump the string as is
243                 # this means any module used like this must have fully formatted docstrings.
244                 py_c_func2sphinx("", fw, module_name, module, attribute, value, is_class=False)
245             elif value_type == type:
246                 classes.append((attribute, value))
247             elif value_type in (bool, int, float, str, tuple):
248                 # constant, not much fun we can do here except to list it.
249                 # TODO, figure out some way to document these!
250                 fw(".. data:: %s\n\n" % attribute)
251                 write_indented_lines("   ", fw, "constant value %s" % repr(value), False)
252                 fw("\n")
253             else:
254                 print("\tnot documenting %s.%s" % (module_name, attribute))
255                 continue
256
257             attribute_set.add(attribute)
258             # TODO, more types...
259
260     # write collected classes now
261     for (type_name, value) in classes:
262         # May need to be its own function
263         fw(".. class:: %s\n\n" % type_name)
264         if value.__doc__:
265             write_indented_lines("   ", fw, value.__doc__, False)
266             fw("\n")
267         write_example_ref("   ", fw, module_name + "." + type_name)
268
269         descr_items = [(key, descr) for key, descr in sorted(value.__dict__.items()) if not key.startswith("__")]
270
271         for key, descr in descr_items:
272             if type(descr) == MethodDescriptorType: # GetSetDescriptorType's are not documented yet
273                 py_descr2sphinx("   ", fw, descr, module_name, type_name, key)
274
275         for key, descr in descr_items:
276             if type(descr) == GetSetDescriptorType:
277                 py_descr2sphinx("   ", fw, descr, module_name, type_name, key)
278
279         for key, descr in descr_items:
280             if type(descr) == StaticMethodType:
281                 descr = getattr(value, key)
282                 write_indented_lines("   ", fw, descr.__doc__ or "Undocumented", False)
283                 fw("\n")
284
285         fw("\n\n")
286
287     file.close()
288
289
290
291 def rna2sphinx(BASEPATH):
292
293     structs, funcs, ops, props = rna_info.BuildRNAInfo()
294
295     try:
296         os.mkdir(BASEPATH)
297     except:
298         pass
299
300     # conf.py - empty for now
301     filepath = os.path.join(BASEPATH, "conf.py")
302     file = open(filepath, "w")
303     fw = file.write
304
305
306     version_string = bpy.app.version_string.split("(")[0]
307     if bpy.app.build_revision != "Unknown":
308         version_string = version_string + " r" + bpy.app.build_revision
309     
310     fw("project = 'Blender'\n")
311     # fw("master_doc = 'index'\n")
312     fw("copyright = u'Blender Foundation'\n")
313     fw("version = '%s - UNSTABLE API'\n" % version_string)
314     fw("release = '%s - UNSTABLE API'\n" % version_string)
315     fw("html_theme = 'blender-org'\n")
316     fw("html_theme_path = ['../']\n")
317     fw("html_favicon = 'favicon.ico'\n")
318     # not helpful since the source us generated, adds to upload size.
319     fw("html_copy_source = False\n")
320     fw("\n")
321     # needed for latex, pdf gen
322     fw("latex_documents = [ ('contents', 'contents.tex', 'Blender Index', 'Blender Foundation', 'manual'), ]\n")
323     fw("latex_paper_size = 'a4paper'\n")
324     file.close()
325
326
327     filepath = os.path.join(BASEPATH, "contents.rst")
328     file = open(filepath, "w")
329     fw = file.write
330     
331     fw("%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n")
332     fw(" Blender Documentation contents\n")
333     fw("%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n")
334     fw("\n")
335     fw("This document is an API reference for Blender %s. built %s.\n" % (version_string, bpy.app.build_date))
336     fw("\n")
337     fw("An introduction to Blender and Python can be found at <http://wiki.blender.org/index.php/Dev:2.5/Py/API/Intro>\n")
338     fw("\n")
339     fw("`A PDF version of this document is also available <blender_python_reference_250.pdf>`__\n")
340     fw("\n")
341     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")
342     fw("   \n")
343     fw("   The following areas are subject to change.\n")
344     fw("      * operator names and arguments\n")
345     fw("      * render api\n")
346     fw("      * function calls with the data api (any function calls with values accessed from bpy.data), including functions for importing and exporting meshes\n")
347     fw("      * class registration (Operator, Panels, Menus, Headers)\n")
348     fw("      * modules: bpy.props, blf)\n")
349     fw("      * members in the bpy.context have to be reviewed\n")
350     fw("      * python defined modal operators, especially drawing callbacks are highly experemental\n")
351     fw("   \n")
352     fw("   These parts of the API are relatively stable and are unlikely to change significantly\n")
353     fw("      * data API, access to attributes of blender data such as mesh verts, material color, timeline frames and scene objects\n")
354     fw("      * user interface functions for defining buttons, creation of menus, headers, panels\n")
355     fw("      * modules: bgl, mathutils and geometry\n")
356     fw("\n")
357
358     fw("===================\n")
359     fw("Application Modules\n")
360     fw("===================\n")
361     fw("\n")
362     fw(".. toctree::\n")
363     fw("   :maxdepth: 1\n\n")
364     fw("   bpy.data.rst\n\n") # note: not actually a module
365     fw("   bpy.ops.rst\n\n")
366     fw("   bpy.types.rst\n\n")
367     
368     # py modules
369     fw("   bpy.utils.rst\n\n")
370     fw("   bpy.path.rst\n\n")
371     fw("   bpy.app.rst\n\n")
372     
373     # C modules
374     fw("   bpy.props.rst\n\n")
375
376     fw("==================\n")
377     fw("Standalone Modules\n")
378     fw("==================\n")
379     fw("\n")
380     fw(".. toctree::\n")
381     fw("   :maxdepth: 1\n\n")
382
383
384     fw("   mathutils.rst\n\n")
385     fw("   Freestyle.rst\n\n")
386     fw("   blf.rst\n\n")
387     
388     # game engine
389     fw("===================\n")
390     fw("Game Engine Modules\n")
391     fw("===================\n")
392     fw("\n")
393     fw(".. toctree::\n")
394     fw("   :maxdepth: 1\n\n")
395     fw("   bge.types.rst\n\n")
396     fw("   bge.logic.rst\n\n")
397     fw("   bge.render.rst\n\n")
398     fw("   bge.events.rst\n\n")
399
400     file.close()
401
402
403     # internal modules
404     filepath = os.path.join(BASEPATH, "bpy.ops.rst")
405     file = open(filepath, "w")
406     fw = file.write
407     fw("Operators (bpy.ops)\n")
408     fw("===================\n\n")
409     fw(".. toctree::\n")
410     fw("   :glob:\n\n")
411     fw("   bpy.ops.*\n\n")
412     file.close()
413
414     filepath = os.path.join(BASEPATH, "bpy.types.rst")
415     file = open(filepath, "w")
416     fw = file.write
417     fw("Types (bpy.types)\n")
418     fw("=================\n\n")
419     fw(".. toctree::\n")
420     fw("   :glob:\n\n")
421     fw("   bpy.types.*\n\n")
422     file.close()
423
424
425     # not actually a module, only write this file so we
426     # can reference in the TOC
427     filepath = os.path.join(BASEPATH, "bpy.data.rst")
428     file = open(filepath, "w")
429     fw = file.write
430     fw("Data Access (bpy.data)\n")
431     fw("======================\n\n")
432     fw(".. module:: bpy\n")
433     fw("\n")
434     fw("This module is used for all blender/python access.\n")
435     fw("\n")
436     fw(".. literalinclude:: ../examples/bpy.data.py\n")
437     fw("\n")
438     fw(".. data:: data\n")
439     fw("\n")
440     fw("   Access to blenders internal data\n")
441     fw("\n")
442     fw("   :type: :class:`bpy.types.Main`\n")
443     file.close()
444
445     EXAMPLE_SET_USED.add("bpy.data")
446
447
448     # python modules
449     from bpy import utils as module
450     pymodule2sphinx(BASEPATH, "bpy.utils", module, "Utilities (bpy.utils)")
451
452     from bpy import path as module
453     pymodule2sphinx(BASEPATH, "bpy.path", module, "Path Utilities (bpy.path)")
454
455     # C modules
456     from bpy import app as module
457     pymodule2sphinx(BASEPATH, "bpy.app", module, "Application Data (bpy.app)")
458
459     from bpy import props as module
460     pymodule2sphinx(BASEPATH, "bpy.props", module, "Property Definitions (bpy.props)")
461     
462     import mathutils as module
463     pymodule2sphinx(BASEPATH, "mathutils", module, "Math Types & Utilities (mathutils)")
464     del module
465
466     import Freestyle as module
467     pymodule2sphinx(BASEPATH, "Freestyle", module, "Freestyle Operators & Rules (Freestyle)")
468     del module
469
470     import blf as module
471     pymodule2sphinx(BASEPATH, "blf", module, "Font Drawing (blf)")
472     del module
473
474     # game engine
475     import shutil
476     # copy2 keeps time/date stamps
477     shutil.copy2(os.path.join(BASEPATH, "../../../../gameengine/PyDoc/bge.types.rst"), BASEPATH)
478     shutil.copy2(os.path.join(BASEPATH, "../../../../gameengine/PyDoc/bge.logic.rst"), BASEPATH)
479     shutil.copy2(os.path.join(BASEPATH, "../../../../gameengine/PyDoc/bge.render.rst"), BASEPATH)
480     shutil.copy2(os.path.join(BASEPATH, "../../../../gameengine/PyDoc/bge.events.rst"), BASEPATH)
481
482
483     if 0:
484         filepath = os.path.join(BASEPATH, "bpy.rst")
485         file = open(filepath, "w")
486         fw = file.write
487         
488         fw("\n")
489
490         title = ":mod:`bpy` --- Blender Python Module"
491         fw("%s\n%s\n\n" % (title, "=" * len(title)))
492         fw(".. module:: bpy.types\n\n")
493         file.close()
494
495     def write_param(ident, fw, prop, is_return=False):
496         if is_return:
497             id_name = "return"
498             id_type = "rtype"
499             kwargs = {"as_ret": True, "class_fmt": ":class:`%s`"}
500             identifier = ""
501         else:
502             id_name = "arg"
503             id_type = "type"
504             kwargs = {"as_arg": True, "class_fmt": ":class:`%s`"}
505             identifier = " %s" % prop.identifier
506
507         type_descr = prop.get_type_description(**kwargs)
508         if prop.name or prop.description:
509             fw(ident + ":%s%s: %s\n" % (id_name, identifier, ", ".join([val for val in (prop.name, prop.description) if val])))
510         fw(ident + ":%s%s: %s\n" % (id_type, identifier, type_descr))
511
512     def write_struct(struct):
513         #if not struct.identifier.startswith("Sc") and not struct.identifier.startswith("I"):
514         #    return
515
516         #if not struct.identifier == "Object":
517         #    return
518
519         filepath = os.path.join(BASEPATH, "bpy.types.%s.rst" % struct.identifier)
520         file = open(filepath, "w")
521         fw = file.write
522         
523         base_id = getattr(struct.base, "identifier", "")
524
525         if _BPY_STRUCT_FAKE:
526             if not base_id:
527                 base_id = _BPY_STRUCT_FAKE
528
529         if base_id:
530             title = "%s(%s)" % (struct.identifier, base_id)
531         else:
532             title = struct.identifier
533
534         fw("%s\n%s\n\n" % (title, "=" * len(title)))
535         
536         fw(".. module:: bpy.types\n\n")
537         
538         base_ids = [base.identifier for base in struct.get_bases()]
539
540         if _BPY_STRUCT_FAKE:
541             base_ids.append(_BPY_STRUCT_FAKE)
542
543         base_ids.reverse()
544             
545         if base_ids:
546             if len(base_ids) > 1:
547                 fw("base classes --- ")
548             else:
549                 fw("base class --- ")
550
551             fw(", ".join([(":class:`%s`" % base_id) for base_id in base_ids]))
552             fw("\n\n")
553         
554         subclass_ids = [s.identifier for s in structs.values() if s.base is struct if not rna_info.rna_id_ignore(s.identifier)]
555         if subclass_ids:
556             fw("subclasses --- \n" + ", ".join([(":class:`%s`" % s) for s in subclass_ids]) + "\n\n")
557         
558         base_id = getattr(struct.base, "identifier", "")
559         
560         if _BPY_STRUCT_FAKE:
561             if not base_id:
562                 base_id = _BPY_STRUCT_FAKE
563
564         if base_id:
565             fw(".. class:: %s(%s)\n\n" % (struct.identifier, base_id))
566         else:
567             fw(".. class:: %s\n\n" % struct.identifier)
568
569         fw("   %s\n\n" % struct.description)
570         
571         # properties sorted in alphabetical order
572         sorted_struct_properties = struct.properties[:]
573         sorted_struct_properties.sort(key=lambda prop: prop.identifier)
574
575         for prop in sorted_struct_properties:
576             type_descr = prop.get_type_description(class_fmt=":class:`%s`")
577             # readonly properties use "data" directive, variables properties use "attribute" directive
578             if 'readonly' in type_descr:
579                 fw("   .. data:: %s\n\n" % prop.identifier)
580             else:
581                 fw("   .. attribute:: %s\n\n" % prop.identifier)
582             if prop.description:
583                 fw("      %s\n\n" % prop.description)
584             fw("      :type: %s\n\n" % type_descr)
585         
586         # python attributes
587         py_properties = struct.get_py_properties()
588         py_prop = None
589         for identifier, py_prop in py_properties:
590             pyprop2sphinx("   ", fw, identifier, py_prop)
591         del py_properties, py_prop
592
593         for func in struct.functions:
594             args_str = ", ".join([prop.get_arg_default(force=False) for prop in func.args])
595
596             fw("   .. method:: %s(%s)\n\n" % (func.identifier, args_str))
597             fw("      %s\n\n" % func.description)
598             
599             for prop in func.args:
600                 write_param("      ", fw, prop)
601
602             if len(func.return_values) == 1:
603                 write_param("      ", fw, func.return_values[0], is_return=True)
604             elif func.return_values: # multiple return values
605                 fw("      :return (%s):\n" % ", ".join([prop.identifier for prop in func.return_values]))
606                 for prop in func.return_values:
607                     type_descr = prop.get_type_description(as_ret=True, class_fmt=":class:`%s`")
608                     descr = prop.description
609                     if not descr:
610                         descr = prop.name
611                     fw("         `%s`, %s, %s\n\n" % (prop.identifier, descr, type_descr))
612
613             fw("\n")
614
615
616         # python methods
617         py_funcs = struct.get_py_functions()
618         py_func = None
619         
620         for identifier, py_func in py_funcs:
621             pyfunc2sphinx("   ", fw, identifier, py_func, is_class=True)
622         del py_funcs, py_func
623
624         lines = []
625
626         if struct.base or _BPY_STRUCT_FAKE:
627             bases = list(reversed(struct.get_bases()))
628
629             # props
630             lines[:] = []
631             
632             if _BPY_STRUCT_FAKE:
633                 descr_items = [(key, descr) for key, descr in sorted(bpy.types.Struct.__bases__[0].__dict__.items()) if not key.startswith("__")]
634             
635             if _BPY_STRUCT_FAKE:
636                 for key, descr in descr_items:
637                     if type(descr) == GetSetDescriptorType:
638                         lines.append("* :class:`%s.%s`\n" % (_BPY_STRUCT_FAKE, key))
639
640             for base in bases:
641                 for prop in base.properties:
642                     lines.append("* :class:`%s.%s`\n" % (base.identifier, prop.identifier))
643
644                 for identifier, py_prop in base.get_py_properties():
645                     lines.append("* :class:`%s.%s`\n" % (base.identifier, identifier))
646                     
647                 for identifier, py_prop in base.get_py_properties():
648                     lines.append("* :class:`%s.%s`\n" % (base.identifier, identifier))
649             
650             if lines:
651                 fw(".. rubric:: Inherited Properties\n\n")
652                 for line in lines:
653                     fw(line)
654                 fw("\n")
655
656
657             # funcs
658             lines[:] = []
659
660             if _BPY_STRUCT_FAKE:
661                 for key, descr in descr_items:
662                     if type(descr) == MethodDescriptorType:
663                         lines.append("* :class:`%s.%s`\n" % (_BPY_STRUCT_FAKE, key))
664
665             for base in bases:
666                 for func in base.functions:
667                     lines.append("* :class:`%s.%s`\n" % (base.identifier, func.identifier))
668                 for identifier, py_func in base.get_py_functions():
669                     lines.append("* :class:`%s.%s`\n" % (base.identifier, identifier))
670
671             if lines:
672                 fw(".. rubric:: Inherited Functions\n\n")
673                 for line in lines:
674                     fw(line)
675                 fw("\n")
676             
677             lines[:] = []
678
679
680         if struct.references:
681             # use this otherwise it gets in the index for a normal heading.
682             fw(".. rubric:: References\n\n")
683
684             for ref in struct.references:
685                 ref_split = ref.split(".")
686                 if len(ref_split) > 2:
687                     ref = ref_split[-2] + "." + ref_split[-1]
688                 fw("* :class:`%s`\n" % ref)
689             fw("\n")
690
691
692     for struct in structs.values():
693         # TODO, rna_info should filter these out!
694         if "_OT_" in struct.identifier:
695             continue
696         write_struct(struct)
697         
698     # special case, bpy_struct
699     if _BPY_STRUCT_FAKE:
700         filepath = os.path.join(BASEPATH, "bpy.types.%s.rst" % _BPY_STRUCT_FAKE)
701         file = open(filepath, "w")
702         fw = file.write
703
704         fw("%s\n" % _BPY_STRUCT_FAKE)
705         fw("=" * len(_BPY_STRUCT_FAKE) + "\n")
706         fw("\n")
707         fw(".. module:: bpy.types\n")
708         fw("\n")
709
710         subclass_ids = [s.identifier for s in structs.values() if s.base is None if not rna_info.rna_id_ignore(s.identifier)]
711         if subclass_ids:
712             fw("subclasses --- \n" + ", ".join([(":class:`%s`" % s) for s in sorted(subclass_ids)]) + "\n\n")
713
714         fw(".. class:: %s\n\n" % _BPY_STRUCT_FAKE)
715         fw("   built-in base class for all classes in bpy.types.\n\n")
716         fw("   .. note::\n\n")
717         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)
718
719         descr_items = [(key, descr) for key, descr in sorted(bpy.types.Struct.__bases__[0].__dict__.items()) if not key.startswith("__")]
720
721         for key, descr in descr_items:
722             if type(descr) == MethodDescriptorType: # GetSetDescriptorType, GetSetDescriptorType's are not documented yet
723                 py_descr2sphinx("   ", fw, descr, "bpy.types", _BPY_STRUCT_FAKE, key)
724
725         for key, descr in descr_items:
726             if type(descr) == GetSetDescriptorType:
727                 py_descr2sphinx("   ", fw, descr, "bpy.types", _BPY_STRUCT_FAKE, key)
728
729
730     # operators
731     def write_ops():
732         API_BASEURL='https://svn.blender.org/svnroot/bf-blender/trunk/blender/release/scripts'
733         fw = None
734         last_mod = ''
735         
736         for op_key in sorted(ops.keys()):
737             op = ops[op_key]
738             
739             if last_mod != op.module_name:
740                 filepath = os.path.join(BASEPATH, "bpy.ops.%s.rst" % op.module_name)
741                 file = open(filepath, "w")
742                 fw = file.write
743                 
744                 title = "%s Operators"  % (op.module_name[0].upper() + op.module_name[1:])
745                 fw("%s\n%s\n\n" % (title, "=" * len(title)))
746                 
747                 fw(".. module:: bpy.ops.%s\n\n" % op.module_name)
748                 last_mod = op.module_name
749
750             args_str = ", ".join([prop.get_arg_default(force=True) for prop in op.args])
751             fw(".. function:: %s(%s)\n\n" % (op.func_name, args_str))
752
753             # if the description isn't valid, we output the standard warning 
754             # with a link to the wiki so that people can help
755             if not op.description or op.description == "(undocumented operator)":
756                 operator_description = undocumented_message('bpy.ops',op.module_name,op.func_name)
757             else:
758                 operator_description = op.description
759
760             fw("   %s\n\n" % operator_description)
761             for prop in op.args:
762                 write_param("   ", fw, prop)
763             if op.args:
764                 fw("\n")
765
766             location = op.get_location()
767             if location != (None, None):
768                 fw("   :file: `%s <%s/%s>`_:%d\n\n" % (location[0],API_BASEURL,location[0],location[1]))
769     
770     write_ops()
771
772     file.close()
773
774 if __name__ == '__main__':
775     if 'bpy' not in dir():
776         print("\nError, this script must run from inside blender2.5")
777         print(script_help_msg)
778     else:
779         import shutil
780
781         path_in = 'source/blender/python/doc/sphinx-in'
782         path_out = 'source/blender/python/doc/sphinx-out'
783         path_examples = 'source/blender/python/doc/examples'
784         # only for partial updates
785         path_in_tmp = path_in + "-tmp"
786
787         if not os.path.exists(path_in):
788             os.mkdir(path_in)
789
790         for f in os.listdir(path_examples):
791             if f.endswith(".py"):
792                 EXAMPLE_SET.add(os.path.splitext(f)[0])
793
794
795         # only for full updates
796         if _BPY_FULL_REBUILD:
797             shutil.rmtree(path_in, True)
798             shutil.rmtree(path_out, True)
799         else:
800             # write here, then move
801             shutil.rmtree(path_in_tmp, True)
802
803         rna2sphinx(path_in_tmp)
804
805         if not _BPY_FULL_REBUILD:
806             import filecmp
807
808             # now move changed files from 'path_in_tmp' --> 'path_in'
809             file_list_path_in = set(os.listdir(path_in))
810             file_list_path_in_tmp = set(os.listdir(path_in_tmp))
811             
812             # remove deprecated files that have been removed.
813             for f in sorted(file_list_path_in):
814                 if f not in file_list_path_in_tmp:
815                     print("\tdeprecated: %s" % f)
816                     os.remove(os.path.join(path_in, f))
817
818             # freshen with new files.
819             for f in sorted(file_list_path_in_tmp):
820                 f_from = os.path.join(path_in_tmp, f)
821                 f_to = os.path.join(path_in, f)
822
823                 do_copy = True
824                 if f in file_list_path_in:
825                     if filecmp.cmp(f_from, f_to):
826                         do_copy = False
827                 
828                 if do_copy:
829                     print("\tupdating: %s" % f)
830                     shutil.copy(f_from, f_to)
831                 '''else:
832                     print("\tkeeping: %s" % f) # eh, not that useful'''
833
834
835         EXAMPLE_SET_UNUSED = EXAMPLE_SET - EXAMPLE_SET_USED
836         if EXAMPLE_SET_UNUSED:
837             print("\nUnused examples found in '%s'..." % path_examples)
838             for f in EXAMPLE_SET_UNUSED:
839                 print("    %s.py" % f)
840             print("  %d total\n" % len(EXAMPLE_SET_UNUSED))
841
842     import sys
843     sys.exit()