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