- added docs and examples for bpy.app.handlers
[blender.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 --background -noaudio --python doc/python_api/sphinx_doc_gen.py
31
32   This will generate python files in doc/python_api/sphinx-in/
33   providing ./blender.bin is or links to the blender executable
34
35 - Generate html docs by running...
36
37     cd doc/python_api
38     sphinx-build sphinx-in sphinx-out
39
40   This requires sphinx 1.0.7 to be installed.
41
42 For PDF generation
43 ------------------
44 - After you have built doc/python_api/sphinx-in (see above), run:
45
46     sphinx-build -b latex doc/python_api/sphinx-in doc/python_api/sphinx-out
47     cd doc/python_api/sphinx-out
48     make
49 '''
50
51 # Check we're running in blender
52 if __import__("sys").modules.get("bpy") is None:
53     print("\nError, this script must run from inside blender")
54     print(script_help_msg)
55
56     import sys
57     sys.exit()
58
59
60 # Switch for quick testing
61 if 1:
62     # full build
63     EXCLUDE_INFO_DOCS = False
64     EXCLUDE_MODULES = ()
65     FILTER_BPY_TYPES = None
66     FILTER_BPY_OPS = None
67
68 else:
69     EXCLUDE_INFO_DOCS = False
70     # for testing so doc-builds dont take so long.
71     EXCLUDE_MODULES = (
72         "bpy.context",
73         #"bpy.app",
74         #"bpy.app.handlers",
75         "bpy.path",
76         "bpy.data",
77         "bpy.props",
78         "bpy.utils",
79         "bpy.context",
80         "bpy.types",  # supports filtering
81         "bpy.ops",  # supports filtering
82         "bpy_extras",
83         "bge",
84         "aud",
85         "bgl",
86         "blf",
87         "gpu",
88         "mathutils",
89         "mathutils.geometry",
90     )
91
92     FILTER_BPY_TYPES = ("bpy_struct", "Operator", "ID")  # allow
93     FILTER_BPY_OPS = ("import.scene", )  # allow
94
95     # for quick rebuilds
96     """
97 rm -rf /b/doc/python_api/sphinx-* && \
98 ./blender.bin --background -noaudio --factory-startup --python  doc/python_api/sphinx_doc_gen.py && \
99 sphinx-build doc/python_api/sphinx-in doc/python_api/sphinx-out
100
101     """
102
103 # extra info, not api reference docs
104 # stored in ./rst/info/
105 INFO_DOCS = (
106     ("info_quickstart.rst", "Blender/Python Quickstart: new to blender/scripting and want to get your feet wet?"),
107     ("info_overview.rst", "Blender/Python API Overview: a more complete explanation of python integration"),
108     ("info_best_practice.rst", "Best Practice: Conventions to follow for writing good scripts"),
109     ("info_tips_and_tricks.rst", "Tips and Tricks: Hints to help you while writeing scripts for blender"),
110     ("info_gotcha.rst", "Gotcha's: some of the problems you may come up against when writing scripts"),
111     )
112
113
114 # -----------------------------------------------------------------------------
115 # configure compile time options
116
117 try:
118     __import__("aud")
119 except ImportError:
120     print("Warning: Built without 'aud' module, docs incomplete...")
121     EXCLUDE_MODULES = EXCLUDE_MODULES + ("aud", )
122
123
124
125 # import rpdb2; rpdb2.start_embedded_debugger('test')
126
127 import os
128 import inspect
129 import bpy
130 import rna_info
131
132 # lame, python wont give some access
133 ClassMethodDescriptorType = type(dict.__dict__['fromkeys'])
134 MethodDescriptorType = type(dict.get)
135 GetSetDescriptorType = type(int.real)
136 from types import MemberDescriptorType
137
138 EXAMPLE_SET = set()
139 EXAMPLE_SET_USED = set()
140
141 _BPY_STRUCT_FAKE = "bpy_struct"
142 _BPY_PROP_COLLECTION_FAKE = "bpy_prop_collection"
143 _BPY_FULL_REBUILD = False
144
145 if _BPY_PROP_COLLECTION_FAKE:
146     _BPY_PROP_COLLECTION_ID = ":class:`%s`" % _BPY_PROP_COLLECTION_FAKE
147 else:
148     _BPY_PROP_COLLECTION_ID = "collection"
149
150
151 def is_struct_seq(value):
152     return isinstance(value, tuple) and type(tuple) != tuple and hasattr(value, "n_fields")
153
154
155 def module_id_as_ref(name):
156     return "mod_" + name.replace(".", "__")
157
158
159 def undocumented_message(module_name, type_name, identifier):
160     if str(type_name).startswith('<module'):
161         preloadtitle = '%s.%s' % (module_name, identifier)
162     else:
163         preloadtitle = '%s.%s.%s' % (module_name, type_name, identifier)
164     message = "Undocumented (`contribute "\
165         "<http://wiki.blender.org/index.php/Dev:2.5/Py/API/Documentation/Contribute"\
166         "?action=edit&section=new&preload=Dev:2.5/Py/API/Documentation/Contribute/Howto-message"\
167         "&preloadtitle=%s>`_)\n\n" % preloadtitle
168     return message
169
170
171 def range_str(val):
172     '''
173     Converts values to strings for the range directive.
174     (unused function it seems)
175     '''
176     if val < -10000000:
177         return '-inf'
178     elif val > 10000000:
179         return 'inf'
180     elif type(val) == float:
181         return '%g' % val
182     else:
183         return str(val)
184
185
186 def example_extract_docstring(filepath):
187     file = open(filepath, "r", encoding="utf-8")
188     line = file.readline()
189     line_no = 0
190     text = []
191     if line.startswith('"""'):  # assume nothing here
192         line_no += 1
193     else:
194         file.close()
195         return "", 0
196
197     for line in file.readlines():
198         line_no += 1
199         if line.startswith('"""'):
200             break
201         else:
202             text.append(line.rstrip())
203
204     line_no += 1
205     file.close()
206     return "\n".join(text), line_no
207
208
209 def write_title(fw, text, heading_char):
210     fw("%s\n%s\n\n" % (text, len(text) * heading_char))
211
212
213 def write_example_ref(ident, fw, example_id, ext="py"):
214     if example_id in EXAMPLE_SET:
215
216         # extract the comment
217         filepath = "../examples/%s.%s" % (example_id, ext)
218         filepath_full = os.path.join(os.path.dirname(fw.__self__.name), filepath)
219
220         text, line_no = example_extract_docstring(filepath_full)
221
222         for line in text.split("\n"):
223             fw("%s\n" % (ident + line).rstrip())
224         fw("\n")
225
226         fw("%s.. literalinclude:: %s\n" % (ident, filepath))
227         if line_no > 0:
228             fw("%s   :lines: %d-\n" % (ident, line_no))
229         fw("\n")
230         EXAMPLE_SET_USED.add(example_id)
231     else:
232         if bpy.app.debug:
233             print("\tskipping example:", example_id)
234
235     # Support for numbered files bpy.types.Operator -> bpy.types.Operator.1.py
236     i = 1
237     while True:
238         example_id_num = "%s.%d" % (example_id, i)
239         if example_id_num in EXAMPLE_SET:
240             write_example_ref(ident, fw, example_id_num, ext)
241             i += 1
242         else:
243             break
244
245
246 def write_indented_lines(ident, fn, text, strip=True):
247     '''
248     Apply same indentation to all lines in a multilines text.
249     '''
250     if text is None:
251         return
252
253     lines = text.split("\n")
254
255     # strip empty lines from the start/end
256     while lines and not lines[0].strip():
257         del lines[0]
258     while lines and not lines[-1].strip():
259         del lines[-1]
260
261     if strip:
262         ident_strip = 1000
263         for l in lines:
264             if l.strip():
265                 ident_strip = min(ident_strip, len(l) - len(l.lstrip()))
266         for l in lines:
267             fn(ident + l[ident_strip:] + "\n")
268     else:
269         for l in lines:
270             fn(ident + l + "\n")
271
272
273 def pymethod2sphinx(ident, fw, identifier, py_func):
274     '''
275     class method to sphinx
276     '''
277     arg_str = inspect.formatargspec(*inspect.getargspec(py_func))
278     if arg_str.startswith("(self, "):
279         arg_str = "(" + arg_str[7:]
280         func_type = "method"
281     elif arg_str.startswith("(cls, "):
282         arg_str = "(" + arg_str[6:]
283         func_type = "classmethod"
284     else:
285         func_type = "staticmethod"
286
287     fw(ident + ".. %s:: %s%s\n\n" % (func_type, identifier, arg_str))
288     if py_func.__doc__:
289         write_indented_lines(ident + "   ", fw, py_func.__doc__)
290         fw("\n")
291
292
293 def pyfunc2sphinx(ident, fw, identifier, py_func, is_class=True):
294     '''
295     function or class method to sphinx
296     '''
297     arg_str = inspect.formatargspec(*inspect.getargspec(py_func))
298
299     if not is_class:
300         func_type = "function"
301
302         # ther rest are class methods
303     elif arg_str.startswith("(self, "):
304         arg_str = "(" + arg_str[7:]
305         func_type = "method"
306     elif arg_str.startswith("(cls, "):
307         arg_str = "(" + arg_str[6:]
308         func_type = "classmethod"
309     else:
310         func_type = "staticmethod"
311
312     fw(ident + ".. %s:: %s%s\n\n" % (func_type, identifier, arg_str))
313     if py_func.__doc__:
314         write_indented_lines(ident + "   ", fw, py_func.__doc__)
315         fw("\n")
316
317
318 def py_descr2sphinx(ident, fw, descr, module_name, type_name, identifier):
319     if identifier.startswith("_"):
320         return
321
322     doc = descr.__doc__
323     if not doc:
324         doc = undocumented_message(module_name, type_name, identifier)
325
326     if type(descr) == GetSetDescriptorType:
327         fw(ident + ".. attribute:: %s\n\n" % identifier)
328         write_indented_lines(ident + "   ", fw, doc, False)
329         fw("\n")
330     elif type(descr) == MemberDescriptorType: # same as above but use 'data'
331         fw(ident + ".. data:: %s\n\n" % identifier)
332         write_indented_lines(ident + "   ", fw, doc, False)
333         fw("\n")
334     elif type(descr) in (MethodDescriptorType, ClassMethodDescriptorType):
335         write_indented_lines(ident, fw, doc, False)
336         fw("\n")
337     else:
338         raise TypeError("type was not GetSetDescriptorType, MethodDescriptorType or ClassMethodDescriptorType")
339
340     write_example_ref(ident + "   ", fw, module_name + "." + type_name + "." + identifier)
341     fw("\n")
342
343
344 def py_c_func2sphinx(ident, fw, module_name, type_name, identifier, py_func, is_class=True):
345     '''
346     c defined function to sphinx.
347     '''
348
349     # dump the docstring, assume its formatted correctly
350     if py_func.__doc__:
351         write_indented_lines(ident, fw, py_func.__doc__, False)
352         fw("\n")
353     else:
354         fw(ident + ".. function:: %s()\n\n" % identifier)
355         fw(ident + "   " + undocumented_message(module_name, type_name, identifier))
356
357     if is_class:
358         write_example_ref(ident + "   ", fw, module_name + "." + type_name + "." + identifier)
359     else:
360         write_example_ref(ident + "   ", fw, module_name + "." + identifier)
361
362     fw("\n")
363
364
365 def pyprop2sphinx(ident, fw, identifier, py_prop):
366     '''
367     python property to sphinx
368     '''
369     # readonly properties use "data" directive, variables use "attribute" directive
370     if py_prop.fset is None:
371         fw(ident + ".. data:: %s\n\n" % identifier)
372     else:
373         fw(ident + ".. attribute:: %s\n\n" % identifier)
374     write_indented_lines(ident + "   ", fw, py_prop.__doc__)
375     if py_prop.fset is None:
376         fw(ident + "   (readonly)\n\n")
377
378
379 def pymodule2sphinx(BASEPATH, module_name, module, title):
380     import types
381     attribute_set = set()
382     filepath = os.path.join(BASEPATH, module_name + ".rst")
383
384     module_all = getattr(module, "__all__", None)
385     module_dir = sorted(dir(module))
386
387     if module_all:
388         module_dir = module_all
389
390     file = open(filepath, "w", encoding="utf-8")
391
392     fw = file.write
393
394     write_title(fw, "%s (%s)" % (title, module_name), "=")
395
396     # write reference, annoying since we should be able to direct reference the
397     # modules but we cant always!
398     fw(".. _%s:\n\n" % module_id_as_ref(module_name))
399
400     fw(".. module:: %s\n\n" % module_name)
401
402     if module.__doc__:
403         # Note, may contain sphinx syntax, dont mangle!
404         fw(module.__doc__.strip())
405         fw("\n\n")
406
407     write_example_ref("", fw, module_name)
408
409     # write submodules
410     # we could also scan files but this ensures __all__ is used correctly
411     if module_all is not None:
412         submod_name = None
413         submod = None
414         submod_ls = []
415         for submod_name in module_all:
416             ns = {}
417             exec_str = "from %s import %s as submod" % (module.__name__, submod_name)
418             exec(exec_str, ns, ns)
419             submod = ns["submod"]
420             if type(submod) == types.ModuleType:
421                 submod_ls.append((submod_name, submod))
422
423         del submod_name
424         del submod
425
426         if submod_ls:
427             fw(".. toctree::\n")
428             fw("   :maxdepth: 1\n\n")
429
430             for submod_name, submod in submod_ls:
431                 submod_name_full = "%s.%s" % (module_name, submod_name)
432                 fw("   %s.rst\n\n" % submod_name_full)
433
434                 pymodule2sphinx(BASEPATH, submod_name_full, submod, "%s submodule" % module_name)
435         del submod_ls
436     # done writing submodules!
437
438     # write members of the module
439     # only tested with PyStructs which are not exactly modules
440     for key, descr in sorted(type(module).__dict__.items()):
441         if key.startswith("__"):
442             continue
443         # naughty, we also add getset's into PyStructs, this is not typical py but also not incorrect.
444
445         # type_name is only used for examples and messages
446         type_name = str(type(module)).strip("<>").split(" ", 1)[-1][1:-1] # "<class 'bpy.app.handlers'>" --> bpy.app.handlers
447         if type(descr) == types.GetSetDescriptorType:
448             py_descr2sphinx("", fw, descr, module_name, type_name, key)
449             attribute_set.add(key)
450     descr_sorted = []
451     for key, descr in sorted(type(module).__dict__.items()):
452         if key.startswith("__"):
453             continue
454
455         if type(descr) == MemberDescriptorType:
456             if descr.__doc__:
457                 value = getattr(module, key, None)
458                 value_type = type(value)
459                 descr_sorted.append((key, descr, value, type(value)))
460     # sort by the valye type
461     descr_sorted.sort(key=lambda descr_data: str(descr_data[3]))
462     for key, descr, value, value_type in descr_sorted:
463         type_name = value_type.__name__
464         py_descr2sphinx("", fw, descr, module_name, type_name, key)
465
466         if is_struct_seq(value):
467             # ack, cant use typical reference because we double up once here
468             # and one fort he module!
469             full_name = "%s.%s" % (module_name, type_name)
470             fw("   :ref:`%s submodule details <%s>`\n\n\n" % (full_name, module_id_as_ref(full_name))) #  % (module_name, type_name)
471             del full_name
472
473         attribute_set.add(key)
474
475     del key, descr, descr_sorted
476
477     classes = []
478     submodules = []
479
480     # use this list so we can sort by type
481     module_dir_value_type = []
482
483     for attribute in module_dir:
484         if attribute.startswith("_"):
485             continue
486
487         if attribute in attribute_set:
488             continue
489
490         if attribute.startswith("n_"):  # annoying exception, needed for bpy.app
491             continue
492
493         # workaround for bpy.app documenting .index() and .count()
494         if isinstance(module, tuple) and hasattr(tuple, attribute):
495             continue
496
497         value = getattr(module, attribute)
498
499         module_dir_value_type.append((attribute, value, type(value)))
500
501     # sort by str of each type
502     # this way lists, functions etc are grouped.
503     module_dir_value_type.sort(key=lambda triple: str(triple[2]))
504
505     for attribute, value, value_type in module_dir_value_type:
506         if value_type == types.FunctionType:
507             pyfunc2sphinx("", fw, attribute, value, is_class=False)
508         elif value_type in (types.BuiltinMethodType, types.BuiltinFunctionType):  # both the same at the moment but to be future proof
509             # note: can't get args from these, so dump the string as is
510             # this means any module used like this must have fully formatted docstrings.
511             py_c_func2sphinx("", fw, module_name, None, attribute, value, is_class=False)
512         elif value_type == type:
513             classes.append((attribute, value))
514         elif issubclass(value_type, types.ModuleType):
515             submodules.append((attribute, value))
516         elif value_type in (bool, int, float, str, tuple):
517             # constant, not much fun we can do here except to list it.
518             # TODO, figure out some way to document these!
519             #fw(".. data:: %s\n\n" % attribute)
520             write_indented_lines("   ", fw, "constant value %s" % repr(value), False)
521             fw("\n")
522         else:
523             print("\tnot documenting %s.%s of %r type" % (module_name, attribute, value_type.__name__))
524             continue
525
526         attribute_set.add(attribute)
527         # TODO, more types...
528     del module_dir_value_type
529
530     # TODO, bpy_extras does this already, mathutils not.
531     """
532     if submodules:
533         fw("\n"
534            "**********\n"
535            "Submodules\n"
536            "**********\n"
537            "\n"
538            )
539         for attribute, submod in submodules:
540             fw("* :mod:`%s.%s`\n" % (module_name, attribute))
541         fw("\n")
542     """
543
544     # write collected classes now
545     for (type_name, value) in classes:
546         # May need to be its own function
547         fw(".. class:: %s\n\n" % type_name)
548         if value.__doc__:
549             write_indented_lines("   ", fw, value.__doc__, False)
550             fw("\n")
551         write_example_ref("   ", fw, module_name + "." + type_name)
552
553         descr_items = [(key, descr) for key, descr in sorted(value.__dict__.items()) if not key.startswith("__")]
554
555         for key, descr in descr_items:
556             if type(descr) == ClassMethodDescriptorType:
557                 py_descr2sphinx("   ", fw, descr, module_name, type_name, key)
558
559         for key, descr in descr_items:
560             if type(descr) == MethodDescriptorType:
561                 py_descr2sphinx("   ", fw, descr, module_name, type_name, key)
562
563         for key, descr in descr_items:
564             if type(descr) == GetSetDescriptorType:
565                 py_descr2sphinx("   ", fw, descr, module_name, type_name, key)
566
567         fw("\n\n")
568
569     file.close()
570
571
572 def pycontext2sphinx(BASEPATH):
573     # Only use once. very irregular
574
575     filepath = os.path.join(BASEPATH, "bpy.context.rst")
576     file = open(filepath, "w", encoding="utf-8")
577     fw = file.write
578     fw("Context Access (bpy.context)\n")
579     fw("============================\n\n")
580     fw(".. module:: bpy.context\n")
581     fw("\n")
582     fw("The context members available depend on the area of blender which is currently being accessed.\n")
583     fw("\n")
584     fw("Note that all context values are readonly, but may be modified through the data api or by running operators\n\n")
585
586     # nasty, get strings directly from blender because there is no other way to get it
587     import ctypes
588
589     context_strings = (
590         "screen_context_dir",
591         "view3d_context_dir",
592         "buttons_context_dir",
593         "image_context_dir",
594         "node_context_dir",
595         "text_context_dir",
596     )
597
598     # Changes in blender will force errors here
599     type_map = {
600         "active_base": ("ObjectBase", False),
601         "active_bone": ("Bone", False),
602         "active_object": ("Object", False),
603         "active_pose_bone": ("PoseBone", False),
604         "armature": ("Armature", False),
605         "bone": ("Bone", False),
606         "brush": ("Brush", False),
607         "camera": ("Camera", False),
608         "cloth": ("ClothModifier", False),
609         "collision": ("CollisionModifier", False),
610         "curve": ("Curve", False),
611         "edit_bone": ("EditBone", False),
612         "edit_image": ("Image", False),
613         "edit_object": ("Object", False),
614         "edit_text": ("Text", False),
615         "editable_bones": ("EditBone", True),
616         "fluid": ("FluidSimulationModifier", False),
617         "image_paint_object": ("Object", False),
618         "lamp": ("Lamp", False),
619         "lattice": ("Lattice", False),
620         "material": ("Material", False),
621         "material_slot": ("MaterialSlot", False),
622         "mesh": ("Mesh", False),
623         "meta_ball": ("MetaBall", False),
624         "object": ("Object", False),
625         "particle_edit_object": ("Object", False),
626         "particle_system": ("ParticleSystem", False),
627         "particle_system_editable": ("ParticleSystem", False),
628         "pose_bone": ("PoseBone", False),
629         "scene": ("Scene", False),
630         "sculpt_object": ("Object", False),
631         "selectable_bases": ("ObjectBase", True),
632         "selectable_objects": ("Object", True),
633         "selected_bases": ("ObjectBase", True),
634         "selected_bones": ("Bone", True),
635         "selected_editable_bases": ("ObjectBase", True),
636         "selected_editable_bones": ("Bone", True),
637         "selected_editable_objects": ("Object", True),
638         "selected_editable_sequences": ("Sequence", True),
639         "selected_nodes": ("Node", True),
640         "selected_objects": ("Object", True),
641         "selected_pose_bones": ("PoseBone", True),
642         "selected_sequences": ("Sequence", True),
643         "sequences": ("Sequence", True),
644         "smoke": ("SmokeModifier", False),
645         "soft_body": ("SoftBodyModifier", False),
646         "speaker": ("Speaker", False),
647         "texture": ("Texture", False),
648         "texture_slot": ("MaterialTextureSlot", False),
649         "vertex_paint_object": ("Object", False),
650         "visible_bases": ("ObjectBase", True),
651         "visible_bones": ("Object", True),
652         "visible_objects": ("Object", True),
653         "visible_pose_bones": ("PoseBone", True),
654         "weight_paint_object": ("Object", False),
655         "world": ("World", False),
656     }
657
658     unique = set()
659     blend_cdll = ctypes.CDLL("")
660     for ctx_str in context_strings:
661         subsection = "%s Context" % ctx_str.split("_")[0].title()
662         fw("\n%s\n%s\n\n" % (subsection, (len(subsection) * '-')))
663
664         attr = ctypes.addressof(getattr(blend_cdll, ctx_str))
665         c_char_p_p = ctypes.POINTER(ctypes.c_char_p)
666         char_array = c_char_p_p.from_address(attr)
667         i = 0
668         while char_array[i] is not None:
669             member = ctypes.string_at(char_array[i]).decode()
670             fw(".. data:: %s\n\n" % member)
671             member_type, is_seq = type_map[member]
672             fw("   :type: %s :class:`bpy.types.%s`\n\n" % ("sequence of " if is_seq else "", member_type))
673             unique.add(member)
674             i += 1
675
676     # generate typemap...
677     # for member in sorted(unique):
678     #     print('        "%s": ("", False),' % member)
679     if len(type_map) > len(unique):
680         raise Exception("Some types are not used: %s" % str([member for member in type_map if member not in unique]))
681     else:
682         pass  # will have raised an error above
683
684     file.close()
685
686
687 def pyrna_enum2sphinx(prop, use_empty_descriptions=False):
688     """ write a bullet point list of enum + descrptons
689     """
690
691     if use_empty_descriptions:
692         ok = True
693     else:
694         ok = False
695         for identifier, name, description in prop.enum_items:
696             if description:
697                 ok = True
698                 break
699
700     if ok:
701         return "".join(["* ``%s`` %s.\n" %
702                         (identifier,
703                          ", ".join(val for val in (name, description) if val),
704                          )
705                         for identifier, name, description in prop.enum_items
706                         ])
707     else:
708         return ""
709
710
711 def pyrna2sphinx(BASEPATH):
712     """ bpy.types and bpy.ops
713     """
714     structs, funcs, ops, props = rna_info.BuildRNAInfo()
715     if FILTER_BPY_TYPES is not None:
716         structs = {k: v for k, v in structs.items() if k[1] in FILTER_BPY_TYPES}
717
718     if FILTER_BPY_OPS is not None:
719         ops = {k: v for k, v in ops.items() if v.module_name in FILTER_BPY_OPS}
720
721     def write_param(ident, fw, prop, is_return=False):
722         if is_return:
723             id_name = "return"
724             id_type = "rtype"
725             kwargs = {"as_ret": True}
726             identifier = ""
727         else:
728             id_name = "arg"
729             id_type = "type"
730             kwargs = {"as_arg": True}
731             identifier = " %s" % prop.identifier
732
733         kwargs["class_fmt"] = ":class:`%s`"
734
735         kwargs["collection_id"] = _BPY_PROP_COLLECTION_ID
736
737         type_descr = prop.get_type_description(**kwargs)
738
739         enum_text = pyrna_enum2sphinx(prop)
740
741         if prop.name or prop.description or enum_text:
742             fw(ident + ":%s%s:\n\n" % (id_name, identifier))
743
744             if prop.name or prop.description:
745                 fw(ident + "   " + ", ".join(val for val in (prop.name, prop.description) if val) + "\n\n")
746
747             # special exception, cant use genric code here for enums
748             if enum_text:
749                 write_indented_lines(ident + "   ", fw, enum_text)
750                 fw("\n")
751             del enum_text
752             # end enum exception
753
754         fw(ident + ":%s%s: %s\n" % (id_type, identifier, type_descr))
755
756     def write_struct(struct):
757         #if not struct.identifier.startswith("Sc") and not struct.identifier.startswith("I"):
758         #    return
759
760         #if not struct.identifier == "Object":
761         #    return
762
763         filepath = os.path.join(BASEPATH, "bpy.types.%s.rst" % struct.identifier)
764         file = open(filepath, "w", encoding="utf-8")
765         fw = file.write
766
767         base_id = getattr(struct.base, "identifier", "")
768
769         if _BPY_STRUCT_FAKE:
770             if not base_id:
771                 base_id = _BPY_STRUCT_FAKE
772
773         if base_id:
774             title = "%s(%s)" % (struct.identifier, base_id)
775         else:
776             title = struct.identifier
777
778         write_title(fw, title, "=")
779
780         fw(".. module:: bpy.types\n\n")
781
782         # docs first?, ok
783         write_example_ref("", fw, "bpy.types.%s" % struct.identifier)
784
785         base_ids = [base.identifier for base in struct.get_bases()]
786
787         if _BPY_STRUCT_FAKE:
788             base_ids.append(_BPY_STRUCT_FAKE)
789
790         base_ids.reverse()
791
792         if base_ids:
793             if len(base_ids) > 1:
794                 fw("base classes --- ")
795             else:
796                 fw("base class --- ")
797
798             fw(", ".join((":class:`%s`" % base_id) for base_id in base_ids))
799             fw("\n\n")
800
801         subclass_ids = [s.identifier for s in structs.values() if s.base is struct if not rna_info.rna_id_ignore(s.identifier)]
802         if subclass_ids:
803             fw("subclasses --- \n" + ", ".join((":class:`%s`" % s) for s in subclass_ids) + "\n\n")
804
805         base_id = getattr(struct.base, "identifier", "")
806
807         if _BPY_STRUCT_FAKE:
808             if not base_id:
809                 base_id = _BPY_STRUCT_FAKE
810
811         if base_id:
812             fw(".. class:: %s(%s)\n\n" % (struct.identifier, base_id))
813         else:
814             fw(".. class:: %s\n\n" % struct.identifier)
815
816         fw("   %s\n\n" % struct.description)
817
818         # properties sorted in alphabetical order
819         sorted_struct_properties = struct.properties[:]
820         sorted_struct_properties.sort(key=lambda prop: prop.identifier)
821
822         for prop in sorted_struct_properties:
823             type_descr = prop.get_type_description(class_fmt=":class:`%s`", collection_id=_BPY_PROP_COLLECTION_ID)
824             # readonly properties use "data" directive, variables properties use "attribute" directive
825             if 'readonly' in type_descr:
826                 fw("   .. data:: %s\n\n" % prop.identifier)
827             else:
828                 fw("   .. attribute:: %s\n\n" % prop.identifier)
829             if prop.description:
830                 fw("      %s\n\n" % prop.description)
831
832             # special exception, cant use genric code here for enums
833             if prop.type == "enum":
834                 enum_text = pyrna_enum2sphinx(prop)
835                 if enum_text:
836                     write_indented_lines("      ", fw, enum_text)
837                     fw("\n")
838                 del enum_text
839             # end enum exception
840
841             fw("      :type: %s\n\n" % type_descr)
842
843         # python attributes
844         py_properties = struct.get_py_properties()
845         py_prop = None
846         for identifier, py_prop in py_properties:
847             pyprop2sphinx("   ", fw, identifier, py_prop)
848         del py_properties, py_prop
849
850         for func in struct.functions:
851             args_str = ", ".join(prop.get_arg_default(force=False) for prop in func.args)
852
853             fw("   .. %s:: %s(%s)\n\n" % ("classmethod" if func.is_classmethod else "method", func.identifier, args_str))
854             fw("      %s\n\n" % func.description)
855
856             for prop in func.args:
857                 write_param("      ", fw, prop)
858
859             if len(func.return_values) == 1:
860                 write_param("      ", fw, func.return_values[0], is_return=True)
861             elif func.return_values:  # multiple return values
862                 fw("      :return (%s):\n" % ", ".join(prop.identifier for prop in func.return_values))
863                 for prop in func.return_values:
864                     # TODO, pyrna_enum2sphinx for multiple return values... actually dont think we even use this but still!!!
865                     type_descr = prop.get_type_description(as_ret=True, class_fmt=":class:`%s`", collection_id=_BPY_PROP_COLLECTION_ID)
866                     descr = prop.description
867                     if not descr:
868                         descr = prop.name
869                     fw("         `%s`, %s, %s\n\n" % (prop.identifier, descr, type_descr))
870
871             write_example_ref("      ", fw, "bpy.types." + struct.identifier + "." + func.identifier)
872
873             fw("\n")
874
875         # python methods
876         py_funcs = struct.get_py_functions()
877         py_func = None
878
879         for identifier, py_func in py_funcs:
880             pyfunc2sphinx("   ", fw, identifier, py_func, is_class=True)
881         del py_funcs, py_func
882
883         py_funcs = struct.get_py_c_functions()
884         py_func = None
885
886         for identifier, py_func in py_funcs:
887             py_c_func2sphinx("   ", fw, "bpy.types", struct.identifier, identifier, py_func, is_class=True)
888
889         lines = []
890
891         if struct.base or _BPY_STRUCT_FAKE:
892             bases = list(reversed(struct.get_bases()))
893
894             # props
895             lines[:] = []
896
897             if _BPY_STRUCT_FAKE:
898                 descr_items = [(key, descr) for key, descr in sorted(bpy.types.Struct.__bases__[0].__dict__.items()) if not key.startswith("__")]
899
900             if _BPY_STRUCT_FAKE:
901                 for key, descr in descr_items:
902                     if type(descr) == GetSetDescriptorType:
903                         lines.append("   * :class:`%s.%s`\n" % (_BPY_STRUCT_FAKE, key))
904
905             for base in bases:
906                 for prop in base.properties:
907                     lines.append("   * :class:`%s.%s`\n" % (base.identifier, prop.identifier))
908
909                 for identifier, py_prop in base.get_py_properties():
910                     lines.append("   * :class:`%s.%s`\n" % (base.identifier, identifier))
911
912                 for identifier, py_prop in base.get_py_properties():
913                     lines.append("   * :class:`%s.%s`\n" % (base.identifier, identifier))
914
915             if lines:
916                 fw(".. rubric:: Inherited Properties\n\n")
917
918                 fw(".. hlist::\n")
919                 fw("   :columns: 2\n\n")
920
921                 for line in lines:
922                     fw(line)
923                 fw("\n")
924
925             # funcs
926             lines[:] = []
927
928             if _BPY_STRUCT_FAKE:
929                 for key, descr in descr_items:
930                     if type(descr) == MethodDescriptorType:
931                         lines.append("   * :class:`%s.%s`\n" % (_BPY_STRUCT_FAKE, key))
932
933             for base in bases:
934                 for func in base.functions:
935                     lines.append("   * :class:`%s.%s`\n" % (base.identifier, func.identifier))
936                 for identifier, py_func in base.get_py_functions():
937                     lines.append("   * :class:`%s.%s`\n" % (base.identifier, identifier))
938
939             if lines:
940                 fw(".. rubric:: Inherited Functions\n\n")
941
942                 fw(".. hlist::\n")
943                 fw("   :columns: 2\n\n")
944
945                 for line in lines:
946                     fw(line)
947                 fw("\n")
948
949             lines[:] = []
950
951         if struct.references:
952             # use this otherwise it gets in the index for a normal heading.
953             fw(".. rubric:: References\n\n")
954
955             fw(".. hlist::\n")
956             fw("   :columns: 2\n\n")
957
958             for ref in struct.references:
959                 ref_split = ref.split(".")
960                 if len(ref_split) > 2:
961                     ref = ref_split[-2] + "." + ref_split[-1]
962                 fw("   * :class:`%s`\n" % ref)
963             fw("\n")
964
965         # docs last?, disable for now
966         # write_example_ref("", fw, "bpy.types.%s" % struct.identifier)
967         file.close()
968
969     if "bpy.types" not in EXCLUDE_MODULES:
970         for struct in structs.values():
971             # TODO, rna_info should filter these out!
972             if "_OT_" in struct.identifier:
973                 continue
974             write_struct(struct)
975
976         def fake_bpy_type(class_value, class_name, descr_str, use_subclasses=True):
977             filepath = os.path.join(BASEPATH, "bpy.types.%s.rst" % class_name)
978             file = open(filepath, "w", encoding="utf-8")
979             fw = file.write
980
981             write_title(fw, class_name, "=")
982
983             fw(".. module:: bpy.types\n")
984             fw("\n")
985
986             if use_subclasses:
987                 subclass_ids = [s.identifier for s in structs.values() if s.base is None if not rna_info.rna_id_ignore(s.identifier)]
988                 if subclass_ids:
989                     fw("subclasses --- \n" + ", ".join((":class:`%s`" % s) for s in sorted(subclass_ids)) + "\n\n")
990
991             fw(".. class:: %s\n\n" % class_name)
992             fw("   %s\n\n" % descr_str)
993             fw("   .. note::\n\n")
994             fw("      Note that bpy.types.%s is not actually available from within blender, it only exists for the purpose of documentation.\n\n" % class_name)
995
996             descr_items = [(key, descr) for key, descr in sorted(class_value.__dict__.items()) if not key.startswith("__")]
997
998             for key, descr in descr_items:
999                 if type(descr) == MethodDescriptorType:  # GetSetDescriptorType, GetSetDescriptorType's are not documented yet
1000                     py_descr2sphinx("   ", fw, descr, "bpy.types", class_name, key)
1001
1002             for key, descr in descr_items:
1003                 if type(descr) == GetSetDescriptorType:
1004                     py_descr2sphinx("   ", fw, descr, "bpy.types", class_name, key)
1005             file.close()
1006
1007         # write fake classes
1008         if _BPY_STRUCT_FAKE:
1009             class_value = bpy.types.Struct.__bases__[0]
1010             fake_bpy_type(class_value, _BPY_STRUCT_FAKE, "built-in base class for all classes in bpy.types.", use_subclasses=True)
1011
1012         if _BPY_PROP_COLLECTION_FAKE:
1013             class_value = bpy.data.objects.__class__
1014             fake_bpy_type(class_value, _BPY_PROP_COLLECTION_FAKE, "built-in class used for all collections.", use_subclasses=False)
1015
1016     # operators
1017     def write_ops():
1018         API_BASEURL = "http://svn.blender.org/svnroot/bf-blender/trunk/blender/release/scripts"
1019         API_BASEURL_ADDON = "http://svn.blender.org/svnroot/bf-extensions/trunk/py/scripts"
1020         API_BASEURL_ADDON_CONTRIB = "http://svn.blender.org/svnroot/bf-extensions/contrib/py/scripts"
1021
1022         op_modules = {}
1023         for op in ops.values():
1024             op_modules.setdefault(op.module_name, []).append(op)
1025         del op
1026
1027         for op_module_name, ops_mod in op_modules.items():
1028             filepath = os.path.join(BASEPATH, "bpy.ops.%s.rst" % op_module_name)
1029             file = open(filepath, "w", encoding="utf-8")
1030             fw = file.write
1031
1032             title = "%s Operators" % op_module_name.replace("_", " ").title()
1033
1034             write_title(fw, title, "=")
1035
1036             fw(".. module:: bpy.ops.%s\n\n" % op_module_name)
1037
1038             ops_mod.sort(key=lambda op: op.func_name)
1039
1040             for op in ops_mod:
1041                 args_str = ", ".join(prop.get_arg_default(force=True) for prop in op.args)
1042                 fw(".. function:: %s(%s)\n\n" % (op.func_name, args_str))
1043
1044                 # if the description isn't valid, we output the standard warning
1045                 # with a link to the wiki so that people can help
1046                 if not op.description or op.description == "(undocumented operator)":
1047                     operator_description = undocumented_message('bpy.ops', op.module_name, op.func_name)
1048                 else:
1049                     operator_description = op.description
1050
1051                 fw("   %s\n\n" % operator_description)
1052                 for prop in op.args:
1053                     write_param("   ", fw, prop)
1054                 if op.args:
1055                     fw("\n")
1056
1057                 location = op.get_location()
1058                 if location != (None, None):
1059                     if location[0].startswith("addons_contrib" + os.sep):
1060                         url_base = API_BASEURL_ADDON_CONTRIB
1061                     elif location[0].startswith("addons" + os.sep):
1062                         url_base = API_BASEURL_ADDON
1063                     else:
1064                         url_base = API_BASEURL
1065
1066                     fw("   :file: `%s <%s/%s>`_:%d\n\n" % (location[0], url_base, location[0], location[1]))
1067
1068             file.close()
1069
1070     if "bpy.ops" not in EXCLUDE_MODULES:
1071         write_ops()
1072
1073
1074 def rna2sphinx(BASEPATH):
1075
1076     try:
1077         os.mkdir(BASEPATH)
1078     except:
1079         pass
1080
1081     # conf.py - empty for now
1082     filepath = os.path.join(BASEPATH, "conf.py")
1083     file = open(filepath, "w", encoding="utf-8")
1084     fw = file.write
1085
1086     version_string = ".".join(str(v) for v in bpy.app.version)
1087     if bpy.app.build_revision != "Unknown":
1088         version_string = version_string + " r" + bpy.app.build_revision
1089
1090     version_string_fp = "_".join(str(v) for v in bpy.app.version)
1091
1092     if bpy.app.version_cycle == "release":
1093         version_string_pdf = "%s%s_release" % ("_".join(str(v) for v in bpy.app.version[:2]), bpy.app.version_char)
1094     else:
1095         version_string_pdf = version_string_fp
1096
1097     fw("project = 'Blender'\n")
1098     # fw("master_doc = 'index'\n")
1099     fw("copyright = u'Blender Foundation'\n")
1100     fw("version = '%s - API'\n" % version_string)
1101     fw("release = '%s - API'\n" % version_string)
1102
1103     # until we get a theme for 'Naiad'
1104     if 0:
1105         fw("html_theme = 'blender-org'\n")
1106         fw("html_theme_path = ['../']\n")
1107
1108         # copied with the theme, exclude else we get an error [#28873]
1109         fw("html_favicon = 'favicon.ico'\n")
1110
1111     # not helpful since the source us generated, adds to upload size.
1112     fw("html_copy_source = False\n")
1113     fw("\n")
1114     # needed for latex, pdf gen
1115     fw("latex_documents = [ ('contents', 'contents.tex', 'Blender Index', 'Blender Foundation', 'manual'), ]\n")
1116     fw("latex_paper_size = 'a4paper'\n")
1117     file.close()
1118
1119     # main page needed for sphinx (index.html)
1120     filepath = os.path.join(BASEPATH, "contents.rst")
1121     file = open(filepath, "w", encoding="utf-8")
1122     fw = file.write
1123
1124     fw("%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n")
1125     fw(" Blender Documentation contents\n")
1126     fw("%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n")
1127     fw("\n")
1128     fw("Welcome, this document is an API reference for Blender %s. built %s.\n" % (version_string, bpy.app.build_date))
1129     fw("\n")
1130     fw("`A PDF version of this document is also available <blender_python_reference_%s.pdf>`_\n" % version_string_pdf)
1131
1132     fw("\n")
1133
1134     if not EXCLUDE_INFO_DOCS:
1135         fw("============================\n")
1136         fw("Blender/Python Documentation\n")
1137         fw("============================\n")
1138         fw("\n")
1139         fw("\n")
1140         fw(".. toctree::\n")
1141         fw("   :maxdepth: 1\n\n")
1142         for info, info_desc in INFO_DOCS:
1143             fw("   %s <%s>\n\n" % (info_desc, info))
1144         fw("\n")
1145
1146     fw("===================\n")
1147     fw("Application Modules\n")
1148     fw("===================\n")
1149     fw("\n")
1150     fw(".. toctree::\n")
1151     fw("   :maxdepth: 1\n\n")
1152     if "bpy.context" not in EXCLUDE_MODULES:
1153         fw("   bpy.context.rst\n\n")  # note: not actually a module
1154     if "bpy.data" not in EXCLUDE_MODULES:
1155         fw("   bpy.data.rst\n\n")  # note: not actually a module
1156     if "bpy.ops" not in EXCLUDE_MODULES:
1157         fw("   bpy.ops.rst\n\n")
1158     if "bpy.types" not in EXCLUDE_MODULES:
1159         fw("   bpy.types.rst\n\n")
1160
1161     # py modules
1162     if "bpy.utils" not in EXCLUDE_MODULES:
1163         fw("   bpy.utils.rst\n\n")
1164     if "bpy.path" not in EXCLUDE_MODULES:
1165         fw("   bpy.path.rst\n\n")
1166     if "bpy.app" not in EXCLUDE_MODULES:
1167         fw("   bpy.app.rst\n\n")
1168     if "bpy.app.handlers" not in EXCLUDE_MODULES:
1169         fw("   bpy.app.handlers.rst\n\n")
1170
1171     # C modules
1172     if "bpy.props" not in EXCLUDE_MODULES:
1173         fw("   bpy.props.rst\n\n")
1174
1175     fw("==================\n")
1176     fw("Standalone Modules\n")
1177     fw("==================\n")
1178     fw("\n")
1179     fw(".. toctree::\n")
1180     fw("   :maxdepth: 1\n\n")
1181
1182     if "mathutils" not in EXCLUDE_MODULES:
1183         fw("   mathutils.rst\n\n")
1184     if "mathutils.geometry" not in EXCLUDE_MODULES:
1185         fw("   mathutils.geometry.rst\n\n")
1186     if "bgl" not in EXCLUDE_MODULES:
1187         fw("   bgl.rst\n\n")
1188     if "blf" not in EXCLUDE_MODULES:
1189         fw("   blf.rst\n\n")
1190     if "gpu" not in EXCLUDE_MODULES:
1191         fw("   gpu.rst\n\n")
1192     if "aud" not in EXCLUDE_MODULES:
1193         fw("   aud.rst\n\n")
1194     if "bpy_extras" not in EXCLUDE_MODULES:
1195         fw("   bpy_extras.rst\n\n")
1196
1197     # game engine
1198     if "bge" not in EXCLUDE_MODULES:
1199         fw("===================\n")
1200         fw("Game Engine Modules\n")
1201         fw("===================\n")
1202         fw("\n")
1203         fw(".. toctree::\n")
1204         fw("   :maxdepth: 1\n\n")
1205         fw("   bge.types.rst\n\n")
1206         fw("   bge.logic.rst\n\n")
1207         fw("   bge.render.rst\n\n")
1208         fw("   bge.texture.rst\n\n")
1209         fw("   bge.events.rst\n\n")
1210         fw("   bge.constraints.rst\n\n")
1211
1212     # rna generated change log
1213     fw("========\n")
1214     fw("API Info\n")
1215     fw("========\n")
1216     fw("\n")
1217     fw(".. toctree::\n")
1218     fw("   :maxdepth: 1\n\n")
1219     fw("   change_log.rst\n\n")
1220
1221     fw("\n")
1222     fw("\n")
1223     fw(".. note:: The Blender Python API has areas which are still in development.\n")
1224     fw("   \n")
1225     fw("   The following areas are subject to change.\n")
1226     fw("      * operator behavior, names and arguments\n")
1227     fw("      * mesh creation and editing functions\n")
1228     fw("   \n")
1229     fw("   These parts of the API are relatively stable and are unlikely to change significantly\n")
1230     fw("      * data API, access to attributes of blender data such as mesh verts, material color, timeline frames and scene objects\n")
1231     fw("      * user interface functions for defining buttons, creation of menus, headers, panels\n")
1232     fw("      * render engine integration\n")
1233     fw("      * modules: bgl, mathutils & game engine.\n")
1234     fw("\n")
1235
1236     file.close()
1237
1238     # internal modules
1239     if "bpy.ops" not in EXCLUDE_MODULES:
1240         filepath = os.path.join(BASEPATH, "bpy.ops.rst")
1241         file = open(filepath, "w", encoding="utf-8")
1242         fw = file.write
1243         fw("Operators (bpy.ops)\n")
1244         fw("===================\n\n")
1245         write_example_ref("", fw, "bpy.ops")
1246         fw(".. toctree::\n")
1247         fw("   :glob:\n\n")
1248         fw("   bpy.ops.*\n\n")
1249         file.close()
1250
1251     if "bpy.types" not in EXCLUDE_MODULES:
1252         filepath = os.path.join(BASEPATH, "bpy.types.rst")
1253         file = open(filepath, "w", encoding="utf-8")
1254         fw = file.write
1255         fw("Types (bpy.types)\n")
1256         fw("=================\n\n")
1257         fw(".. toctree::\n")
1258         fw("   :glob:\n\n")
1259         fw("   bpy.types.*\n\n")
1260         file.close()
1261
1262     if "bpy.data" not in EXCLUDE_MODULES:
1263         # not actually a module, only write this file so we
1264         # can reference in the TOC
1265         filepath = os.path.join(BASEPATH, "bpy.data.rst")
1266         file = open(filepath, "w", encoding="utf-8")
1267         fw = file.write
1268         fw("Data Access (bpy.data)\n")
1269         fw("======================\n\n")
1270         fw(".. module:: bpy\n")
1271         fw("\n")
1272         fw("This module is used for all blender/python access.\n")
1273         fw("\n")
1274         fw(".. data:: data\n")
1275         fw("\n")
1276         fw("   Access to blenders internal data\n")
1277         fw("\n")
1278         fw("   :type: :class:`bpy.types.BlendData`\n")
1279         fw("\n")
1280         fw(".. literalinclude:: ../examples/bpy.data.py\n")
1281         file.close()
1282
1283     EXAMPLE_SET_USED.add("bpy.data")
1284
1285     module = None
1286
1287     if "bpy.context" not in EXCLUDE_MODULES:
1288         # one of a kind, context doc (uses ctypes to extract info!)
1289         pycontext2sphinx(BASEPATH)
1290
1291     # python modules
1292     if "bpy.utils" not in EXCLUDE_MODULES:
1293         from bpy import utils as module
1294         pymodule2sphinx(BASEPATH, "bpy.utils", module, "Utilities")
1295
1296     if "bpy.path" not in EXCLUDE_MODULES:
1297         from bpy import path as module
1298         pymodule2sphinx(BASEPATH, "bpy.path", module, "Path Utilities")
1299
1300     if "bpy_extras" not in EXCLUDE_MODULES:
1301         import bpy_extras as module
1302         pymodule2sphinx(BASEPATH, "bpy_extras", module, "Extra Utilities")
1303
1304     # C modules
1305     if "bpy.app" not in EXCLUDE_MODULES:
1306         from bpy import app as module
1307         pymodule2sphinx(BASEPATH, "bpy.app", module, "Application Data")
1308
1309     if "bpy.app.handlers" not in EXCLUDE_MODULES:    
1310         from bpy.app import handlers as module
1311         pymodule2sphinx(BASEPATH, "bpy.app.handlers", module, "Application Handlers")
1312
1313     if "bpy.props" not in EXCLUDE_MODULES:
1314         from bpy import props as module
1315         pymodule2sphinx(BASEPATH, "bpy.props", module, "Property Definitions")
1316
1317     if "mathutils" not in EXCLUDE_MODULES:
1318         import mathutils as module
1319         pymodule2sphinx(BASEPATH, "mathutils", module, "Math Types & Utilities")
1320
1321     if "mathutils.geometry" not in EXCLUDE_MODULES:
1322         import mathutils.geometry as module
1323         pymodule2sphinx(BASEPATH, "mathutils.geometry", module, "Geometry Utilities")
1324
1325     if "blf" not in EXCLUDE_MODULES:
1326         import blf as module
1327         pymodule2sphinx(BASEPATH, "blf", module, "Font Drawing")
1328
1329     if "bgl" not in EXCLUDE_MODULES:
1330         #import bgl as module
1331         #pymodule2sphinx(BASEPATH, "bgl", module, "Blender OpenGl wrapper")
1332         #del module
1333         import shutil
1334         shutil.copy2(os.path.join(BASEPATH, "..", "rst", "bgl.rst"), BASEPATH)
1335
1336     if "gpu" not in EXCLUDE_MODULES:
1337         #import gpu as module
1338         #pymodule2sphinx(BASEPATH, "gpu", module, "GPU Shader Module")
1339         #del module
1340         import shutil
1341         shutil.copy2(os.path.join(BASEPATH, "..", "rst", "gpu.rst"), BASEPATH)
1342
1343     if "aud" not in EXCLUDE_MODULES:
1344         import aud as module
1345         pymodule2sphinx(BASEPATH, "aud", module, "Audio System")
1346     del module
1347
1348     ## game engine
1349     import shutil
1350     # copy2 keeps time/date stamps
1351     if "bge" not in EXCLUDE_MODULES:
1352         shutil.copy2(os.path.join(BASEPATH, "..", "rst", "bge.types.rst"), BASEPATH)
1353         shutil.copy2(os.path.join(BASEPATH, "..", "rst", "bge.logic.rst"), BASEPATH)
1354         shutil.copy2(os.path.join(BASEPATH, "..", "rst", "bge.render.rst"), BASEPATH)
1355         shutil.copy2(os.path.join(BASEPATH, "..", "rst", "bge.texture.rst"), BASEPATH)
1356         shutil.copy2(os.path.join(BASEPATH, "..", "rst", "bge.events.rst"), BASEPATH)
1357         shutil.copy2(os.path.join(BASEPATH, "..", "rst", "bge.constraints.rst"), BASEPATH)
1358
1359     shutil.copy2(os.path.join(BASEPATH, "..", "rst", "change_log.rst"), BASEPATH)
1360
1361     if not EXCLUDE_INFO_DOCS:
1362         for info, info_desc in INFO_DOCS:
1363             shutil.copy2(os.path.join(BASEPATH, "..", "rst", info), BASEPATH)
1364
1365     if 0:
1366         filepath = os.path.join(BASEPATH, "bpy.rst")
1367         file = open(filepath, "w", encoding="utf-8")
1368         fw = file.write
1369
1370         fw("\n")
1371
1372         title = ":mod:`bpy` --- Blender Python Module"
1373
1374         write_title(fw, title, "=")
1375
1376         fw(".. module:: bpy.types\n\n")
1377         file.close()
1378
1379     # bpy.types and bpy.ops
1380     pyrna2sphinx(BASEPATH)
1381
1382     file.close()
1383
1384
1385 def main():
1386     import shutil
1387
1388     script_dir = os.path.dirname(__file__)
1389     path_in = os.path.join(script_dir, "sphinx-in")
1390     path_out = os.path.join(script_dir, "sphinx-out")
1391     path_examples = os.path.join(script_dir, "examples")
1392     # only for partial updates
1393     path_in_tmp = path_in + "-tmp"
1394
1395     if not os.path.exists(path_in):
1396         os.mkdir(path_in)
1397
1398     for f in os.listdir(path_examples):
1399         if f.endswith(".py"):
1400             EXAMPLE_SET.add(os.path.splitext(f)[0])
1401
1402     # only for full updates
1403     if _BPY_FULL_REBUILD:
1404         shutil.rmtree(path_in, True)
1405         shutil.rmtree(path_out, True)
1406     else:
1407         # write here, then move
1408         shutil.rmtree(path_in_tmp, True)
1409
1410     rna2sphinx(path_in_tmp)
1411
1412     if not _BPY_FULL_REBUILD:
1413         import filecmp
1414
1415         # now move changed files from 'path_in_tmp' --> 'path_in'
1416         file_list_path_in = set(os.listdir(path_in))
1417         file_list_path_in_tmp = set(os.listdir(path_in_tmp))
1418
1419         # remove deprecated files that have been removed.
1420         for f in sorted(file_list_path_in):
1421             if f not in file_list_path_in_tmp:
1422                 print("\tdeprecated: %s" % f)
1423                 os.remove(os.path.join(path_in, f))
1424
1425         # freshen with new files.
1426         for f in sorted(file_list_path_in_tmp):
1427             f_from = os.path.join(path_in_tmp, f)
1428             f_to = os.path.join(path_in, f)
1429
1430             do_copy = True
1431             if f in file_list_path_in:
1432                 if filecmp.cmp(f_from, f_to):
1433                     do_copy = False
1434
1435             if do_copy:
1436                 print("\tupdating: %s" % f)
1437                 shutil.copy(f_from, f_to)
1438             '''else:
1439                 print("\tkeeping: %s" % f) # eh, not that useful'''
1440
1441     EXAMPLE_SET_UNUSED = EXAMPLE_SET - EXAMPLE_SET_USED
1442     if EXAMPLE_SET_UNUSED:
1443         print("\nUnused examples found in '%s'..." % path_examples)
1444         for f in EXAMPLE_SET_UNUSED:
1445             print("    %s.py" % f)
1446         print("  %d total\n" % len(EXAMPLE_SET_UNUSED))
1447
1448     import sys
1449     sys.exit()
1450
1451 if __name__ == '__main__':
1452     main()