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