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