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