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