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