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