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