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