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