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