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