# ***** BEGIN GPL LICENSE BLOCK ***** # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software Foundation, # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. # # Contributor(s): Campbell Barton, Witold Jaworski # ***** End GPL LICENSE BLOCK ***** script_help_msg = """ Usage: - Run this script from blenders root path: blender --background --factory-startup -noaudio --python doc/python_api/blender_stub_gen.py Important! To generate proper stub for bgl, do not use --background option """ import collections import sys import os import inspect import tokenize import types import logging try: import bpy # Blender module except ImportError: print("\nERROR: this script must run from inside Blender") print(script_help_msg) sys.exit(1) from collections import defaultdict from typing import Any, Dict, NamedTuple, DefaultDict, List, TextIO, Set, Tuple __script_dir = os.path.dirname(__file__) ROOT_DIR = os.path.join(__script_dir, 'bpy_stubs') logging.basicConfig(level=logging.DEBUG, format='%(levelname)s %(pathname)s:%(lineno)d %(message)s') def is_named_tuple(value: Any) -> bool: return isinstance(value, tuple) and type(value).__name__ != 'tuple' and hasattr(value, '_fields') C_STYLE_NAMED_TUPLE_FIELDS = {'n_fields', 'n_sequence_fields', 'n_unnamed_fields'} def is_c_style_named_tuple(value: Any) -> bool: """ https://docs.python.org/3/c-api/sequence.html """ return isinstance(value, tuple) and hasattr(value, 'n_sequence_fields') and hasattr(value, 'n_fields') and hasattr( value, 'n_unnamed_fields') class Visitor: def __init__(self, module_name: str): self.module_name = module_name def visit_module(self, name: str, value: types.ModuleType): logging.error( f'element {self.module_name}.{name} is not supported ({self.module_name}.{name}: "{type(value)}" = {repr(value)})') def visit_str(self, name: str, value: str): logging.error( f'element {self.module_name}.{name} is not supported ({self.module_name}.{name}: "{type(value)}" = {repr(value)})') def visit_float(self, name: str, value: float): logging.error( f'element {self.module_name}.{name} is not supported ({self.module_name}.{name}: "{type(value)}" = {repr(value)})') def visit_bytes(self, name: str, value: bytes): logging.error( f'element {self.module_name}.{name} is not supported ({self.module_name}.{name}: "{type(value)}" = {repr(value)})') def visit_bytearray(self, name: str, value: bytearray): logging.error( f'element {self.module_name}.{name} is not supported ({self.module_name}.{name}: "{type(value)}" = {repr(value)})') def visit_int(self, name: str, value: int): logging.error( f'element {self.module_name}.{name} is not supported ({self.module_name}.{name}: "{type(value)}" = {repr(value)})') def visit_complex(self, name: str, value: complex): logging.error( f'element {self.module_name}.{name} is not supported ({self.module_name}.{name}: "{type(value)}" = {repr(value)})') def visit_bool(self, name: str, value: bool): logging.error( f'element {self.module_name}.{name} is not supported ({self.module_name}.{name}: "{type(value)}" = {repr(value)})') def visit_mutable_mapping(self, name: str, value: collections.abc.MutableMapping): logging.error( f'element {self.module_name}.{name} is not supported ({self.module_name}.{name}: "{type(value)}" = {repr(value)})') def visit_mapping(self, name: str, value: collections.abc.Mapping): logging.error( f'element {self.module_name}.{name} is not supported ({self.module_name}.{name}: "{type(value)}" = {repr(value)})') def visit_mutable_sequence(self, name: str, value: collections.abc.MutableSequence): logging.error( f'element {self.module_name}.{name} is not supported ({self.module_name}.{name}: "{type(value)}" = {repr(value)})') def visit_sequence(self, name: str, value: collections.abc.Sequence): logging.error( f'element {self.module_name}.{name} is not supported ({self.module_name}.{name}: "{type(value)}" = {repr(value)})') if is_named_tuple(value): logging.error(f'type NamedTuple is not supported') if is_c_style_named_tuple(value): logging.error(f'c style NamedTuple is not supported') def visit_set(self, name: str, value: collections.abc.Set): logging.error( f'element {self.module_name}.{name} is not supported ({self.module_name}.{name}: "{type(value)}" = {repr(value)})') def visit_mutable_set(self, name: str, value: collections.abc.MutableSet): logging.error( f'element {self.module_name}.{name} is not supported ({self.module_name}.{name}: "{type(value)}" = {repr(value)})') def visit_class_method_descriptor(self, name: str, value: types.ClassMethodDescriptorType): logging.error( f'element {self.module_name}.{name} is not supported ({self.module_name}.{name}: "{type(value)}" = {repr(value)})') def visit_member_descriptor(self, name: str, value: types.MemberDescriptorType): logging.error( f'element {self.module_name}.{name} is not supported ({self.module_name}.{name}: "{type(value)}" = {repr(value)})') def visit_method_descriptor(self, name: str, value: types.MethodDescriptorType): logging.error( f'element {self.module_name}.{name} is not supported ({self.module_name}.{name}: "{type(value)}" = {repr(value)})') def visit_wrapper_descriptor(self, name: str, value: types.WrapperDescriptorType): logging.error( f'element {self.module_name}.{name} is not supported ({self.module_name}.{name}: "{type(value)}" = {repr(value)})') def visit_get_set_descriptor(self, name: str, value: types.GetSetDescriptorType): logging.error( f'element {self.module_name}.{name} is not supported ({self.module_name}.{name}: "{type(value)}" = {repr(value)})') def visit_builtin_function(self, name: str, value: types.BuiltinFunctionType): logging.error( f'element {self.module_name}.{name} is not supported ({self.module_name}.{name}: "{type(value)}" = {repr(value)})') def visit_coroutine(self, name: str, value: types.CoroutineType): logging.error( f'element {self.module_name}.{name} is not supported ({self.module_name}.{name}: "{type(value)}" = {repr(value)})') def visit_async_generator(self, name: str, value: types.AsyncGeneratorType): logging.error( f'element {self.module_name}.{name} is not supported ({self.module_name}.{name}: "{type(value)}" = {repr(value)})') def visit_function(self, name: str, value: types.FunctionType): logging.error( f'element {self.module_name}.{name} is not supported ({self.module_name}.{name}: "{type(value)}" = {repr(value)})') def visit_code(self, name: str, value: types.CodeType): logging.error( f'element {self.module_name}.{name} is not supported ({self.module_name}.{name}: "{type(value)}" = {repr(value)})') def visit_instancemethod(self, name: str, value: {"__func__"}): logging.error( f'element {self.module_name}.{name} is not supported ({self.module_name}.{name}: "{type(value)}" = {repr(value)})') def visit_class_definition(self, name: str, value: type): logging.error( f'element {self.module_name}.{name} is not supported ({self.module_name}.{name}: "{type(value)}" = {repr(value)})') if value.__name__ != name: # it is just an alias to class if value.__module__ in {self.module_name, None}: # it might be alias to private class logging.error('it is an alias to private class') else: logging.error('it is just an alias to class') else: pass def visit_variable(self, name: str, value: Any): """Visit variable with not obvious type: ether None or custom class""" logging.error( f'element {self.module_name}.{name} is not supported ({self.module_name}.{name}: "{type(value)}" = {repr(value)})') if inspect.isclass(type(value)) and type(value).__module__ in {self.module_name, None}: # it might variable with private class logging.error('it might be variable with private class') def fallback(self, name: str, value: Any): logging.error( f'element {self.module_name}.{name} is not supported ({self.module_name}.{name}: "{type(value)}" = {repr(value)})') def visit(self, attribute: str, value: Any): if isinstance(value, types.ModuleType): self.visit_module(attribute, value) elif isinstance(value, str): self.visit_str(attribute, value) elif isinstance(value, float): self.visit_float(attribute, value) elif isinstance(value, bytes): self.visit_bytes(attribute, value) elif isinstance(value, bytearray): self.visit_bytearray(attribute, value) elif isinstance(value, int): self.visit_int(attribute, value) elif isinstance(value, complex): self.visit_complex(attribute, value) elif isinstance(value, bool): self.visit_bool(attribute, value) elif isinstance(value, collections.abc.MutableMapping): self.visit_mutable_mapping(attribute, value) elif isinstance(value, collections.abc.Mapping): self.visit_mapping(attribute, value) elif isinstance(value, collections.abc.MutableSequence): self.visit_mutable_sequence(attribute, value) elif isinstance(value, collections.abc.Sequence): self.visit_sequence(attribute, value) elif isinstance(value, collections.abc.Set): self.visit_set(attribute, value) elif isinstance(value, collections.abc.MutableSet): self.visit_mutable_set(attribute, value) elif isinstance(value, types.ClassMethodDescriptorType): self.visit_class_method_descriptor(attribute, value) elif isinstance(value, types.MemberDescriptorType): self.visit_member_descriptor(attribute, value) elif isinstance(value, types.MethodDescriptorType): self.visit_method_descriptor(attribute, value) elif isinstance(value, types.WrapperDescriptorType): self.visit_wrapper_descriptor(attribute, value) elif isinstance(value, types.GetSetDescriptorType): self.visit_get_set_descriptor(attribute, value) elif isinstance(value, types.BuiltinFunctionType): # same as types.BuiltinMethodType self.visit_builtin_function(attribute, value) elif isinstance(value, types.CoroutineType): self.visit_coroutine(attribute, value) elif isinstance(value, types.AsyncGeneratorType): self.visit_async_generator(attribute, value) elif isinstance(value, types.FunctionType) or isinstance(value, types.MethodType): # LambdaType == FunctionType self.visit_function(attribute, value) elif isinstance(value, types.CodeType): self.visit_code(attribute, value) elif hasattr(value, '__func__'): # instancemethod type (not available in types module) self.visit_instancemethod(attribute, value) elif inspect.isclass(value): self.visit_class_definition(attribute, value) elif type(value) is not type and inspect.isclass(type(value)): # it is variable with custom class self.visit_variable(attribute, value) else: # logging.fatal(f'not documenting {self.module_name}.{attribute}: {type(value)}={repr(value)}') self.fallback(attribute, value) class Item(NamedTuple): attribute: str value: Any class Accumulator(Visitor): def __init__(self, module_name: str): super().__init__(module_name) self.accumulated: DefaultDict[type, List[Item]] = defaultdict(list) assert len([fn for fn in Visitor.__dict__.keys() if fn.startswith('visit_')]) == \ len([fn for fn in Accumulator.__dict__.keys() if fn.startswith('visit_')]), \ 'Sanity check failed. Accumulator should override all visit_* methods' def visit_module(self, name: str, value: types.ModuleType): self.accumulated[types.ModuleType].append(Item(name, value)) def visit_mutable_mapping(self, name: str, value: collections.abc.MutableMapping): self.accumulated[collections.abc.MutableMapping].append(Item(name, value)) def visit_mapping(self, name: str, value: collections.abc.Mapping): self.accumulated[collections.abc.Mapping].append(Item(name, value)) def visit_mutable_sequence(self, name: str, value: collections.abc.MutableSequence): self.accumulated[collections.abc.MutableSequence].append(Item(name, value)) def visit_sequence(self, name: str, value: collections.abc.Sequence): self.accumulated[collections.abc.Sequence].append(Item(name, value)) def visit_set(self, name: str, value: collections.abc.Set): self.accumulated[collections.abc.Set].append(Item(name, value)) def visit_mutable_set(self, name: str, value: collections.abc.MutableSet): self.accumulated[collections.abc.MutableSet].append(Item(name, value)) def visit_float(self, name: str, value: float): self.accumulated[float].append(Item(name, value)) def visit_str(self, name: str, value: str): self.accumulated[str].append(Item(name, value)) def visit_bytes(self, name: str, value: bytes): self.accumulated[bytes].append(Item(name, value)) def visit_bytearray(self, name: str, value: bytearray): self.accumulated[bytearray].append(Item(name, value)) def visit_int(self, name: str, value: int): self.accumulated[int].append(Item(name, value)) def visit_complex(self, name: str, value: complex): self.accumulated[complex].append(Item(name, value)) def visit_bool(self, name: str, value: bool): self.accumulated[bool].append(Item(name, value)) def visit_class_method_descriptor(self, name: str, value: types.ClassMethodDescriptorType): self.accumulated[types.ClassMethodDescriptorType].append(Item(name, value)) def visit_member_descriptor(self, name: str, value: types.MemberDescriptorType): self.accumulated[types.MemberDescriptorType].append(Item(name, value)) def visit_method_descriptor(self, name: str, value: types.MethodDescriptorType): self.accumulated[types.MethodDescriptorType].append(Item(name, value)) def visit_wrapper_descriptor(self, name: str, value: types.WrapperDescriptorType): self.accumulated[types.WrapperDescriptorType].append(Item(name, value)) def visit_get_set_descriptor(self, name: str, value: types.GetSetDescriptorType): self.accumulated[types.GetSetDescriptorType].append(Item(name, value)) def visit_builtin_function(self, name: str, value: types.BuiltinFunctionType): self.accumulated[types.BuiltinFunctionType].append(Item(name, value)) def visit_coroutine(self, name: str, value: types.CoroutineType): self.accumulated[types.CoroutineType].append(Item(name, value)) def visit_async_generator(self, name: str, value: types.AsyncGeneratorType): self.accumulated[types.AsyncGeneratorType].append(Item(name, value)) def visit_function(self, name: str, value: types.FunctionType): self.accumulated[types.FunctionType].append(Item(name, value)) def visit_code(self, name: str, value: types.CodeType): self.accumulated[types.CodeType].append(Item(name, value)) def visit_instancemethod(self, name: str, value: {"__func__"}): # raise NotImplementedError self.accumulated["instancemethod"].append(Item(name, value)) def visit_class_definition(self, name: str, value: type): self.accumulated[type].append(Item(name, value)) def visit_variable(self, name: str, value: Any): # raise NotImplementedError self.accumulated["variable"].append(Item(name, value)) def fallback(self, name: str, value: Any): self.accumulated["fallback"].append(Item(name, value)) @staticmethod def _resolve_class_order_in_module(classes: List[Item], module: types.ModuleType) -> List[Item]: """Makes variant of DAG (Directed Acyclic Graph) topological sort to find order of classes Best to test on blender aud module """ sorted_classes = [item for item in sorted(classes)] result = [] ordering: Dict[Item, type] = {} # map class to their bases (mro). Ignore object and self for item in sorted_classes: cls_mro = [mro for mro in inspect.getmro(item.value) if inspect.getmodule(mro).__name__ == module and mro not in {object, item.value}] ordering[item] = cls_mro while ordering: for item, cls_mro in ordering.copy().items(): if cls_mro: continue result.append(item) del ordering[item] for i in ordering.keys(): try: ordering[i].remove(item.value) except ValueError: pass assert len(result) == len(classes) return result def replay(self, visitor: Visitor): """Preprocess self.accumulated and re-visit accumulated elements * Sort entries by name * Sort classes by resolution order """ # process accumulated data about module for key in self.accumulated.keys(): self.accumulated[key].sort(key=lambda item: item.attribute) self.accumulated[type] = self._resolve_class_order_in_module(self.accumulated[type], self.module_name) # replay in particular order type_to_visitor = { types.ModuleType: visitor.visit_module, int: visitor.visit_int, float: visitor.visit_float, bytes: visitor.visit_bytes, bytearray: visitor.visit_bytearray, complex: visitor.visit_complex, bool: visitor.visit_bool, str: visitor.visit_str, "variable": visitor.visit_variable, collections.abc.MutableMapping: visitor.visit_mutable_mapping, collections.abc.Mapping: visitor.visit_mapping, collections.abc.Sequence: visitor.visit_sequence, collections.abc.MutableSequence: visitor.visit_mutable_sequence, collections.abc.Set: visitor.visit_set, collections.abc.MutableSet: visitor.visit_mutable_set, types.GetSetDescriptorType: visitor.visit_get_set_descriptor, types.WrapperDescriptorType: visitor.visit_wrapper_descriptor, types.MemberDescriptorType: visitor.visit_member_descriptor, types.BuiltinFunctionType: visitor.visit_builtin_function, types.CoroutineType: visitor.visit_coroutine, types.AsyncGeneratorType: visitor.visit_async_generator, types.FunctionType: visitor.visit_function, types.ClassMethodDescriptorType: visitor.visit_class_method_descriptor, types.MethodDescriptorType: visitor.visit_method_descriptor, "instancemethod": visitor.visit_instancemethod, type: visitor.visit_class_definition, types.CodeType: visitor.visit_code, "fallback": visitor.fallback } assert len([fn for fn in Visitor.__dict__.keys() if fn.startswith('visit_')]) + 1 == len(type_to_visitor), \ 'Sanity check failed. Accumulator might skip some types during replay' for key, callback in type_to_visitor.items(): for item in self.accumulated[key]: callback(item.attribute, item.value) class StubGen(Visitor): def __init__(self, module_name: str, io: TextIO): super().__init__(module_name) self.io = io self.indent_level = 0 def _indent_inc(self): self.indent_level += 4 def _indent_dec(self): self.indent_level -= 4 def _write(self, text: str): for line in text.splitlines(keepends=True): self.io.write(f'{self.indent_level * " "}{line}') def _write_constant(self, name: str, value: Any, stub_value=False, add_doc=False): if not stub_value: self._write(f'{name} = {repr(value)}\n') if add_doc: self._write(f'"""{value.__doc__}"""') return self._write(f'{name}') module = type(value).__module__ if value is None: type_hint = 'typing.Optional' logging.warning(f'{self.module_name}.{name} = None, can not get typehint') elif module != 'builtins': type_hint = f'"{module}.{type(value).__name__}"' else: type_hint = f'"{type(value).__name__}"' if type_hint: self._write(f': {type_hint} = ...\n') else: self._write(f' = ...\n') self._write(f'"""Runtime value: {repr(value)}') if add_doc: self._write('\n') self._write(value.__doc__) self._write('"""\n') def visit_module(self, name: str, value: types.ModuleType): if value.__name__.startswith(self.module_name + '.'): self._write(f'from . import {name}\n') else: if value.__name__ != name: self._write(f'import {value.__name__} as {name}\n') else: self._write(f'import {name}\n') def visit_str(self, name: str, value: str): self._write_constant(name, value) def visit_float(self, name: str, value: float): self._write_constant(name, value) def visit_bytes(self, name: str, value: bytes): self._write_constant(name, value) def visit_bytearray(self, name: str, value: bytearray): self._write_constant(name, value) def visit_int(self, name: str, value: int): self._write_constant(name, value) def visit_complex(self, name: str, value: complex): self._write_constant(name, value) def visit_bool(self, name: str, value: bool): self._write_constant(name, value) def visit_variable(self, name: str, value: Any): self._write_constant(name, value, stub_value=True) if value and inspect.isclass(type(value)) and type(value).__module__ in {self.module_name, None, 'builtins'}: # it might variable with private class logging.debug(f'{self.module_name}.{name}: {type(value)} private classes are not supported') def visit_sequence(self, name: str, value: collections.abc.Sequence): if is_named_tuple(value): logging.error(f'{self.module_name}.{name} type NamedTuple is not supported') return if is_c_style_named_tuple(value): logging.error(f'{self.module_name}.{name} c style NamedTuple is not supported') return self._write_constant(name, value) # todo not fully supported yet super().visit_sequence(name, value) def visit_set(self, name: str, value: collections.abc.Set): # set will be printed in random order... if type(value) is frozenset: items_str = ','.join(repr(item) for item in sorted(value)) self._write(f'{name} = frozenset({{{items_str}}})\n') else: # this is some kind of subclass of frozenset self._write_constant(name, value, stub_value=True, add_doc=True) def visit_builtin_function(self, name: str, value: types.BuiltinFunctionType): try: signature = inspect.signature(value) except ValueError as e: # most likely: no signature found for builtin signature = '(*args, **kwargs)' # logging.warning(f'function {self.module_name}.{name}: {e}') except RuntimeError as e: logging.error(f'something went very wrong with getting function signature: {e} ' f'Is {self.module_name}.{name}.__text_signature__ correct? (__text_signature__="{value.__text_signature__}")') return except tokenize.TokenError as e: logging.error( f'syntax error in {self.module_name}.{name}.__text_signature__="{value.__text_signature__}": {e}') return self._write(f'def {name}{signature}:\n') self._indent_inc() if value.__doc__: self._write(f'"""{value.__doc__}"""\n') self._write(f'...\n\n') self._indent_dec() def visit_instancemethod(self, name: str, value: {"__func__"}): # this is probably only alias to another function # but usually __module__ is not set and it is not possible to track source actual_function: types.FunctionType = value.__func__ # logging.debug(actual_function) self.visit_builtin_function(name, actual_function) def visit_class_definition(self, name: str, value: type): bases = [] for base in value.__bases__: if base.__module__ and base.__module__ != self.module_name and base.__module__ != 'builtins': bases.append(f'{base.__module__}.{base.__name__}') else: bases.append(base.__name__) # there is always `object` in bases self._write(f'class {name}({",".join(bases)}):\n') self._indent_inc() if value.__doc__: self._write(f'"""{value.__doc__}"""\n\n') # todo stub only functions that come from this class, not from base class acc = Accumulator(self.module_name) is_empty = True for attribute, value in inspect.getmembers(value): if attribute.startswith('__'): # and attribute != '__init__': continue is_empty = False acc.visit(attribute, value) if is_empty: self._write('...\n') else: acc.replay(self) # self.visit(attribute, value) self._indent_dec() def visit_method_descriptor(self, name: str, value: types.MethodDescriptorType): self.visit_builtin_function(name, value) def visit_class_method_descriptor(self, name: str, value: types.ClassMethodDescriptorType): self._write('@classmethod\n') self.visit_builtin_function(name, value) def visit_get_set_descriptor(self, name: str, value: types.GetSetDescriptorType): # logging.debug(f'{self.module_name}.{name}s={value}, {value.__objclass__}') # todo can we do better? self._write(f'{name} = property(lambda self: object(), lambda self, v: None, lambda self: None)\n') if value.__doc__: self._write(f'"""{value.__doc__}"""\n') def pymodule2stub(basepath: str, module_file_name: str, module: types.ModuleType, skip_attributes: Set[str] = None): fakemodule2stub(basepath, module_file_name, module.__name__, inspect.getmembers(module), module.__doc__, skip_attributes) def fakemodule2stub(basepath: str, module_file_name: str, module_name: str, module_members: List[Tuple[str, Any]], module_doc: str = None, skip_attributes: Set[str] = None): skip_attributes = skip_attributes or set() filepath = os.path.join(basepath, f'{module_file_name}.pyi') file = open(filepath, 'w') # The description of this module: if module_doc: file.write('"""') file.write(module_doc) file.write('"""\n\n') file.write('import typing\n\n') accumulator = Accumulator(module_name=module_name) for attribute, value in sorted(module_members): if attribute.startswith("__"): continue if attribute in skip_attributes: continue accumulator.visit(attribute, value) visitor = StubGen(module_name, file) accumulator.replay(visitor) file.close() def _discover_submodules(module: types.ModuleType) -> Set[types.ModuleType]: result = set() for attribute, value in inspect.getmembers(module): if isinstance(value, types.ModuleType): result.add(value) return result def pypackage2stub(basepath: str, module: types.ModuleType): submodules = _discover_submodules(module) if not submodules: pymodule2stub(basepath, module.__name__, module) return package_basepath = os.path.join(basepath, module.__name__) os.makedirs(package_basepath, exist_ok=True) pymodule2stub(package_basepath, '__init__', module) for submodule in sorted(submodules, key=lambda mod: mod.__name__): submodule_name = submodule.__name__ logging.info(f'Generating {submodule_name}') if submodule_name.startswith(module.__name__ + '.'): submodule_name = submodule_name[len(module.__name__ + '.'):] pymodule2stub(package_basepath, submodule_name, submodule) assert not _discover_submodules(submodule), \ f'recursive packages are not supported: {_discover_submodules(submodule)}. There was simply no need to do it' def stub_bpy(): """Generate stub files for _bpy module""" import _bpy bpy_basedir = os.path.join(ROOT_DIR, '_bpy') os.makedirs(bpy_basedir, exist_ok=True) logging.info('Generate _bpy') pymodule2stub(bpy_basedir, '__init__', _bpy, skip_attributes={'app', 'types', 'context', 'data'}) # todo deal with context, types, data and props, ops # logging.info('Generate _bpy.props') # pymodule2stub(bpy_basedir, "props", _bpy.props) # # logging.info('Generate _bpy.data') # fakemodule2stub(bpy_basedir, module_file_name='data', module_name='data', # module_members=inspect.getmembers(_bpy.data), module_doc=_bpy.data.__doc__) # # logging.info('Generate _bpy.types') # pymodule2stub(bpy_basedir, "types", _bpy.types) logging.info('Generate _bpy.app') app_basepath = os.path.join(bpy_basedir, 'app') os.makedirs(app_basepath, exist_ok=True) fakemodule2stub( app_basepath, '__init__', module_name='app', module_doc='', module_members=inspect.getmembers(_bpy.app), skip_attributes={'icons', 'timers'}.union(C_STYLE_NAMED_TUPLE_FIELDS)) logging.info('Generate _bpy.app.icons') pymodule2stub(app_basepath, 'icons', _bpy.app.icons) logging.info('Generate _bpy.app.timers') pymodule2stub(app_basepath, 'timers', _bpy.app.timers) logging.info('Generate _bpy._utils_previews') pymodule2stub(bpy_basedir, '_utils_previews', _bpy._utils_previews) logging.info('Generate _bpy._utils_units') pymodule2stub(bpy_basedir, '_utils_units', _bpy._utils_units) logging.info('Generate _bpy.msgbus') pymodule2stub(bpy_basedir, 'msgbus', _bpy.msgbus) del _bpy def stub_mathutils(): import mathutils logging.info('Generate mathutils') pypackage2stub(ROOT_DIR, mathutils) del mathutils def stub_bpy_path(): import _bpy_path logging.info('Generate _bpy_path') pypackage2stub(ROOT_DIR, _bpy_path) del _bpy_path def stub_bgl(): import bgl logging.info('Generate bgl') pypackage2stub(ROOT_DIR, bgl) del bgl def stub_bgf(): if bpy.app.build_options.freestyle: import bgf logging.info('Generate bgf') pypackage2stub(ROOT_DIR, bgf) del bgf else: logging.info('Skipping bgf (blender build without freestyle)') def stub_bl_math(): import bl_math logging.info('Generate bl_math') pypackage2stub(ROOT_DIR, bl_math) def stub_imbuf(): import imbuf logging.info('Generate imbuf') pypackage2stub(ROOT_DIR, imbuf) def stub_bmesh(): import bmesh logging.info('Generate bmesh') pypackage2stub(ROOT_DIR, bmesh) def stub_cycles(): if bpy.app.build_options.cycles: import _cycles logging.info('Generate _cycles') pypackage2stub(ROOT_DIR, _cycles) else: logging.info('Skipping cycles (blender build without cycles)') def stub_aud(): if bpy.app.build_options.audaspace: import aud logging.info('Generate aud') pypackage2stub(ROOT_DIR, aud) else: logging.info('Skipping aud (blender build without audaspace)') def stub_manta(): if bpy.app.build_options.fluid: import manta logging.info('Generate manta') pypackage2stub(ROOT_DIR, manta) else: logging.info('Skipping manta (blender build without fluid)') def stub_gpu(): import gpu logging.info('Generate gpu') pypackage2stub(ROOT_DIR, gpu) def stub_idprop(): import idprop logging.info('Generate idprop') pypackage2stub(ROOT_DIR, idprop) if __name__ == '__main__': # internal modules are listed in bpy_interface.c in array: bpy_internal_modules stub_bpy() stub_mathutils() stub_bpy_path() stub_bgl() # stub_bgf() stub_bl_math() stub_imbuf() stub_bmesh() stub_manta() stub_aud() stub_cycles() stub_gpu() stub_idprop() logging.info('Finished') del bpy