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