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