123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792 |
- # ##### 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.
- #
- # ##### END GPL LICENSE BLOCK #####
- # <pep8 compliant>
- # classes for extracting info from blenders internal classes
- import bpy
- # use to strip python paths
- script_paths = bpy.utils.script_paths()
- _FAKE_STRUCT_SUBCLASS = True
- def _get_direct_attr(rna_type, attr):
- props = getattr(rna_type, attr)
- base = rna_type.base
- if not base:
- return [prop for prop in props]
- else:
- props_base = getattr(base, attr).values()
- return [prop for prop in props if prop not in props_base]
- def get_direct_properties(rna_type):
- return _get_direct_attr(rna_type, "properties")
- def get_direct_functions(rna_type):
- return _get_direct_attr(rna_type, "functions")
- def rna_id_ignore(rna_id):
- if rna_id == "rna_type":
- return True
- if "_OT_" in rna_id:
- return True
- if "_MT_" in rna_id:
- return True
- if "_PT_" in rna_id:
- return True
- if "_HT_" in rna_id:
- return True
- if "_KSI_" in rna_id:
- return True
- return False
- def range_str(val):
- if val < -10000000:
- return "-inf"
- elif val > 10000000:
- return "inf"
- elif type(val) == float:
- return '%g' % val
- else:
- return str(val)
- def float_as_string(f):
- val_str = "%g" % f
- if '.' not in val_str and '-' not in val_str: # value could be 1e-05
- val_str += '.0'
- return val_str
- def get_py_class_from_rna(rna_type):
- """ Gets the Python type for a class which isn't necessarily added to ``bpy.types``.
- """
- identifier = rna_type.identifier
- py_class = getattr(bpy.types, identifier, None)
- if py_class is not None:
- return py_class
- def subclasses_recurse(cls):
- for c in cls.__subclasses__():
- # is_registered
- if "bl_rna" in cls.__dict__:
- yield c
- yield from subclasses_recurse(c)
- while py_class is None:
- base = rna_type.base
- if base is None:
- raise Exception("can't find type")
- py_class_base = getattr(bpy.types, base.identifier, None)
- if py_class_base is not None:
- for cls in subclasses_recurse(py_class_base):
- if cls.bl_rna.identifier == identifier:
- return cls
- class InfoStructRNA:
- __slots__ = (
- "bl_rna",
- "identifier",
- "name",
- "description",
- "base",
- "nested",
- "full_path",
- "functions",
- "children",
- "references",
- "properties",
- )
- global_lookup = {}
- def __init__(self, rna_type):
- self.bl_rna = rna_type
- self.identifier = rna_type.identifier
- self.name = rna_type.name
- self.description = rna_type.description.strip()
- # set later
- self.base = None
- self.nested = None
- self.full_path = ""
- self.functions = []
- self.children = []
- self.references = []
- self.properties = []
- def build(self):
- rna_type = self.bl_rna
- parent_id = self.identifier
- self.properties[:] = [GetInfoPropertyRNA(rna_prop, parent_id)
- for rna_prop in get_direct_properties(rna_type) if rna_prop.identifier != "rna_type"]
- self.functions[:] = [GetInfoFunctionRNA(rna_prop, parent_id)
- for rna_prop in get_direct_functions(rna_type)]
- def get_bases(self):
- bases = []
- item = self
- while item:
- item = item.base
- if item:
- bases.append(item)
- return bases
- def get_nested_properties(self, ls=None):
- if not ls:
- ls = self.properties[:]
- if self.nested:
- self.nested.get_nested_properties(ls)
- return ls
- def _get_py_visible_attrs(self):
- attrs = []
- py_class = get_py_class_from_rna(self.bl_rna)
- for attr_str in dir(py_class):
- if attr_str.startswith("_"):
- continue
- attrs.append((attr_str, getattr(py_class, attr_str)))
- return attrs
- def get_py_properties(self):
- properties = []
- for identifier, attr in self._get_py_visible_attrs():
- if type(attr) is property:
- properties.append((identifier, attr))
- return properties
- def get_py_functions(self):
- import types
- functions = []
- for identifier, attr in self._get_py_visible_attrs():
- # methods may be python wrappers to C functions
- attr_func = getattr(attr, "__func__", attr)
- if type(attr_func) in {types.FunctionType, types.MethodType}:
- functions.append((identifier, attr))
- return functions
- def get_py_c_functions(self):
- import types
- functions = []
- for identifier, attr in self._get_py_visible_attrs():
- # methods may be python wrappers to C functions
- attr_func = getattr(attr, "__func__", attr)
- if type(attr_func) in {types.BuiltinMethodType, types.BuiltinFunctionType}:
- functions.append((identifier, attr))
- return functions
- def __str__(self):
- txt = ""
- txt += self.identifier
- if self.base:
- txt += "(%s)" % self.base.identifier
- txt += ": " + self.description + "\n"
- for prop in self.properties:
- txt += prop.__repr__() + "\n"
- for func in self.functions:
- txt += func.__repr__() + "\n"
- return txt
- class InfoPropertyRNA:
- __slots__ = (
- "bl_prop",
- "srna",
- "identifier",
- "name",
- "description",
- "default_str",
- "default",
- "enum_items",
- "min",
- "max",
- "array_length",
- "array_dimensions",
- "collection_type",
- "type",
- "fixed_type",
- "is_argument_optional",
- "is_enum_flag",
- "is_required",
- "is_readonly",
- "is_never_none",
- )
- global_lookup = {}
- def __init__(self, rna_prop):
- self.bl_prop = rna_prop
- self.identifier = rna_prop.identifier
- self.name = rna_prop.name
- self.description = rna_prop.description.strip()
- self.default_str = "<UNKNOWN>"
- def build(self):
- rna_prop = self.bl_prop
- self.enum_items = []
- self.min = getattr(rna_prop, "hard_min", -1)
- self.max = getattr(rna_prop, "hard_max", -1)
- self.array_length = getattr(rna_prop, "array_length", 0)
- self.array_dimensions = getattr(rna_prop, "array_dimensions", ())[:]
- self.collection_type = GetInfoStructRNA(rna_prop.srna)
- self.is_required = rna_prop.is_required
- self.is_readonly = rna_prop.is_readonly
- self.is_never_none = rna_prop.is_never_none
- self.is_argument_optional = rna_prop.is_argument_optional
- self.type = rna_prop.type.lower()
- fixed_type = getattr(rna_prop, "fixed_type", "")
- if fixed_type:
- self.fixed_type = GetInfoStructRNA(fixed_type) # valid for pointer/collections
- else:
- self.fixed_type = None
- if self.type == "enum":
- self.enum_items[:] = [(item.identifier, item.name, item.description) for item in rna_prop.enum_items]
- self.is_enum_flag = rna_prop.is_enum_flag
- else:
- self.is_enum_flag = False
- self.default_str = "" # fallback
- if self.array_length:
- self.default = tuple(getattr(rna_prop, "default_array", ()))
- if self.array_dimensions[1] != 0: # Multi-dimensional array, convert default flat one accordingly.
- self.default_str = tuple(float_as_string(v) if self.type == "float" else str(v) for v in self.default)
- for dim in self.array_dimensions[::-1]:
- if dim != 0:
- self.default = tuple(zip(*((iter(self.default),) * dim)))
- self.default_str = tuple("(%s)" % ", ".join(s for s in b) for b in zip(*((iter(self.default_str),) * dim)))
- self.default_str = self.default_str[0]
- elif self.type == "enum" and self.is_enum_flag:
- self.default = getattr(rna_prop, "default_flag", set())
- else:
- self.default = getattr(rna_prop, "default", None)
- if self.type == "pointer":
- # pointer has no default, just set as None
- self.default = None
- self.default_str = "None"
- elif self.type == "string":
- self.default_str = "\"%s\"" % self.default
- elif self.type == "enum":
- if self.is_enum_flag:
- # self.default_str = "%r" % self.default # repr or set()
- self.default_str = "{%s}" % repr(list(sorted(self.default)))[1:-1]
- else:
- self.default_str = "'%s'" % self.default
- elif self.array_length:
- if self.array_dimensions[1] == 0: # single dimension array, we already took care of multi-dimensions ones.
- # special case for floats
- if self.type == "float" and len(self.default) > 0:
- self.default_str = "(%s)" % ", ".join(float_as_string(f) for f in self.default)
- else:
- self.default_str = str(self.default)
- else:
- if self.type == "float":
- self.default_str = float_as_string(self.default)
- else:
- self.default_str = str(self.default)
- self.srna = GetInfoStructRNA(rna_prop.srna) # valid for pointer/collections
- def get_arg_default(self, force=True):
- default = self.default_str
- if default and (force or self.is_required is False):
- return "%s=%s" % (self.identifier, default)
- return self.identifier
- def get_type_description(self, as_ret=False, as_arg=False, class_fmt="%s", collection_id="Collection"):
- type_str = ""
- if self.fixed_type is None:
- type_str += self.type
- if self.array_length:
- if self.array_dimensions[1] != 0:
- type_str += " multi-dimensional array of %s items" % (" * ".join(str(d) for d in self.array_dimensions if d != 0))
- else:
- type_str += " array of %d items" % (self.array_length)
- if self.type in {"float", "int"}:
- type_str += " in [%s, %s]" % (range_str(self.min), range_str(self.max))
- elif self.type == "enum":
- if self.is_enum_flag:
- type_str += " set in {%s}" % ", ".join(("'%s'" % s[0]) for s in self.enum_items)
- else:
- type_str += " in [%s]" % ", ".join(("'%s'" % s[0]) for s in self.enum_items)
- if not (as_arg or as_ret):
- # write default property, ignore function args for this
- if self.type != "pointer":
- if self.default_str:
- type_str += ", default %s" % self.default_str
- else:
- if self.type == "collection":
- if self.collection_type:
- collection_str = (class_fmt % self.collection_type.identifier) + (" %s of " % collection_id)
- else:
- collection_str = "%s of " % collection_id
- else:
- collection_str = ""
- type_str += collection_str + (class_fmt % self.fixed_type.identifier)
- # setup qualifiers for this value.
- type_info = []
- if as_ret:
- pass
- elif as_arg:
- if not self.is_required:
- type_info.append("optional")
- if self.is_argument_optional:
- type_info.append("optional argument")
- else: # readonly is only useful for self's, not args
- if self.is_readonly:
- type_info.append("readonly")
- if self.is_never_none:
- type_info.append("never None")
- if type_info:
- type_str += (", (%s)" % ", ".join(type_info))
- return type_str
- def __str__(self):
- txt = ""
- txt += " * " + self.identifier + ": " + self.description
- return txt
- class InfoFunctionRNA:
- __slots__ = (
- "bl_func",
- "identifier",
- "description",
- "args",
- "return_values",
- "is_classmethod",
- )
- global_lookup = {}
- def __init__(self, rna_func):
- self.bl_func = rna_func
- self.identifier = rna_func.identifier
- # self.name = rna_func.name # functions have no name!
- self.description = rna_func.description.strip()
- self.is_classmethod = not rna_func.use_self
- self.args = []
- self.return_values = ()
- def build(self):
- rna_func = self.bl_func
- parent_id = rna_func
- self.return_values = []
- for rna_prop in rna_func.parameters.values():
- prop = GetInfoPropertyRNA(rna_prop, parent_id)
- if rna_prop.is_output:
- self.return_values.append(prop)
- else:
- self.args.append(prop)
- self.return_values = tuple(self.return_values)
- def __str__(self):
- txt = ''
- txt += ' * ' + self.identifier + '('
- for arg in self.args:
- txt += arg.identifier + ', '
- txt += '): ' + self.description
- return txt
- class InfoOperatorRNA:
- __slots__ = (
- "bl_op",
- "identifier",
- "name",
- "module_name",
- "func_name",
- "description",
- "args",
- )
- global_lookup = {}
- def __init__(self, rna_op):
- self.bl_op = rna_op
- self.identifier = rna_op.identifier
- mod, name = self.identifier.split("_OT_", 1)
- self.module_name = mod.lower()
- self.func_name = name
- # self.name = rna_func.name # functions have no name!
- self.description = rna_op.description.strip()
- self.args = []
- def build(self):
- rna_op = self.bl_op
- parent_id = self.identifier
- for rna_id, rna_prop in rna_op.properties.items():
- if rna_id == "rna_type":
- continue
- prop = GetInfoPropertyRNA(rna_prop, parent_id)
- self.args.append(prop)
- def get_location(self):
- try:
- op_class = getattr(bpy.types, self.identifier)
- except AttributeError:
- # defined in C.
- return None, None
- op_func = getattr(op_class, "execute", None)
- if op_func is None:
- op_func = getattr(op_class, "invoke", None)
- if op_func is None:
- op_func = getattr(op_class, "poll", None)
- if op_func:
- op_code = op_func.__code__
- source_path = op_code.co_filename
- # clear the prefix
- for p in script_paths:
- source_path = source_path.split(p)[-1]
- if source_path[0] in "/\\":
- source_path = source_path[1:]
- return source_path, op_code.co_firstlineno
- else:
- return None, None
- def _GetInfoRNA(bl_rna, cls, parent_id=""):
- if bl_rna is None:
- return None
- key = parent_id, bl_rna.identifier
- try:
- return cls.global_lookup[key]
- except KeyError:
- instance = cls.global_lookup[key] = cls(bl_rna)
- return instance
- def GetInfoStructRNA(bl_rna):
- return _GetInfoRNA(bl_rna, InfoStructRNA)
- def GetInfoPropertyRNA(bl_rna, parent_id):
- return _GetInfoRNA(bl_rna, InfoPropertyRNA, parent_id)
- def GetInfoFunctionRNA(bl_rna, parent_id):
- return _GetInfoRNA(bl_rna, InfoFunctionRNA, parent_id)
- def GetInfoOperatorRNA(bl_rna):
- return _GetInfoRNA(bl_rna, InfoOperatorRNA)
- def BuildRNAInfo():
- # needed on successive calls to prevent stale data access
- for cls in (InfoStructRNA, InfoFunctionRNA, InfoOperatorRNA, InfoPropertyRNA):
- cls.global_lookup.clear()
- del cls
- # Use for faster lookups
- # use rna_struct.identifier as the key for each dict
- rna_struct_dict = {} # store identifier:rna lookups
- rna_full_path_dict = {} # store the result of full_rna_struct_path(rna_struct)
- rna_children_dict = {} # store all rna_structs nested from here
- rna_references_dict = {} # store a list of rna path strings that reference this type
- # rna_functions_dict = {} # store all functions directly in this type (not inherited)
- def full_rna_struct_path(rna_struct):
- """
- Needed when referencing one struct from another
- """
- nested = rna_struct.nested
- if nested:
- return "%s.%s" % (full_rna_struct_path(nested), rna_struct.identifier)
- else:
- return rna_struct.identifier
- # def write_func(rna_func, ident):
- def base_id(rna_struct):
- try:
- return rna_struct.base.identifier
- except:
- return "" # invalid id
- #structs = [(base_id(rna_struct), rna_struct.identifier, rna_struct) for rna_struct in bpy.doc.structs.values()]
- '''
- structs = []
- for rna_struct in bpy.doc.structs.values():
- structs.append( (base_id(rna_struct), rna_struct.identifier, rna_struct) )
- '''
- structs = []
- for rna_type_name in dir(bpy.types):
- rna_type = getattr(bpy.types, rna_type_name)
- rna_struct = getattr(rna_type, "bl_rna", None)
- if rna_struct:
- # if not rna_type_name.startswith('__'):
- identifier = rna_struct.identifier
- if not rna_id_ignore(identifier):
- structs.append((base_id(rna_struct), identifier, rna_struct))
- # Simple lookup
- rna_struct_dict[identifier] = rna_struct
- # Store full rna path 'GameObjectSettings' -> 'Object.GameObjectSettings'
- rna_full_path_dict[identifier] = full_rna_struct_path(rna_struct)
- # Store a list of functions, remove inherited later
- # NOT USED YET
- ## rna_functions_dict[identifier] = get_direct_functions(rna_struct)
- # fill in these later
- rna_children_dict[identifier] = []
- rna_references_dict[identifier] = []
- else:
- print("Ignoring", rna_type_name)
- structs.sort() # not needed but speeds up sort below, setting items without an inheritance first
- # Arrange so classes are always defined in the correct order
- deps_ok = False
- while deps_ok is False:
- deps_ok = True
- rna_done = set()
- for i, (rna_base, identifier, rna_struct) in enumerate(structs):
- rna_done.add(identifier)
- if rna_base and rna_base not in rna_done:
- deps_ok = False
- data = structs.pop(i)
- ok = False
- while i < len(structs):
- if structs[i][1] == rna_base:
- structs.insert(i + 1, data) # insert after the item we depend on.
- ok = True
- break
- i += 1
- if not ok:
- print('Dependancy "%s" could not be found for "%s"' % (identifier, rna_base))
- break
- # Done ordering structs
- # precalculate vars to avoid a lot of looping
- for (rna_base, identifier, rna_struct) in structs:
- # rna_struct_path = full_rna_struct_path(rna_struct)
- rna_struct_path = rna_full_path_dict[identifier]
- for rna_prop in get_direct_properties(rna_struct):
- rna_prop_identifier = rna_prop.identifier
- if rna_prop_identifier == 'RNA' or rna_id_ignore(rna_prop_identifier):
- continue
- for rna_prop_ptr in (getattr(rna_prop, "fixed_type", None), getattr(rna_prop, "srna", None)):
- # Does this property point to me?
- if rna_prop_ptr and rna_prop_ptr.identifier in rna_references_dict:
- rna_references_dict[rna_prop_ptr.identifier].append(
- "%s.%s" % (rna_struct_path, rna_prop_identifier))
- for rna_func in get_direct_functions(rna_struct):
- for rna_prop_identifier, rna_prop in rna_func.parameters.items():
- if rna_prop_identifier == 'RNA' or rna_id_ignore(rna_prop_identifier):
- continue
- rna_prop_ptr = getattr(rna_prop, "fixed_type", None)
- # Does this property point to me?
- if rna_prop_ptr and rna_prop_ptr.identifier in rna_references_dict:
- rna_references_dict[rna_prop_ptr.identifier].append(
- "%s.%s" % (rna_struct_path, rna_func.identifier))
- # Store nested children
- nested = rna_struct.nested
- if nested:
- rna_children_dict[nested.identifier].append(rna_struct)
- # Sort the refs, just reads nicer
- for rna_refs in rna_references_dict.values():
- rna_refs.sort()
- info_structs = []
- for (rna_base, identifier, rna_struct) in structs:
- # if rna_struct.nested:
- # continue
- #write_struct(rna_struct, '')
- info_struct = GetInfoStructRNA(rna_struct)
- if rna_base:
- info_struct.base = GetInfoStructRNA(rna_struct_dict[rna_base])
- info_struct.nested = GetInfoStructRNA(rna_struct.nested)
- info_struct.children[:] = rna_children_dict[identifier]
- info_struct.references[:] = rna_references_dict[identifier]
- info_struct.full_path = rna_full_path_dict[identifier]
- info_structs.append(info_struct)
- for rna_info_prop in InfoPropertyRNA.global_lookup.values():
- rna_info_prop.build()
- for rna_info_prop in InfoFunctionRNA.global_lookup.values():
- rna_info_prop.build()
- done_keys = set()
- new_keys = set(InfoStructRNA.global_lookup.keys())
- while new_keys:
- for rna_key in new_keys:
- rna_info = InfoStructRNA.global_lookup[rna_key]
- rna_info.build()
- for prop in rna_info.properties:
- prop.build()
- for func in rna_info.functions:
- func.build()
- for prop in func.args:
- prop.build()
- for prop in func.return_values:
- prop.build()
- done_keys |= new_keys
- new_keys = set(InfoStructRNA.global_lookup.keys()) - done_keys
- # there are too many invalid defaults, unless we intend to fix, leave this off
- if 0:
- for rna_info in InfoStructRNA.global_lookup.values():
- for prop in rna_info.properties:
- # ERROR CHECK
- default = prop.default
- if type(default) in {float, int}:
- if default < prop.min or default > prop.max:
- print("\t %s.%s, %s not in [%s - %s]" %
- (rna_info.identifier, prop.identifier, default, prop.min, prop.max))
- # now for operators
- op_mods = dir(bpy.ops)
- for op_mod_name in sorted(op_mods):
- if op_mod_name.startswith('__'):
- continue
- op_mod = getattr(bpy.ops, op_mod_name)
- operators = dir(op_mod)
- for op in sorted(operators):
- try:
- rna_prop = getattr(op_mod, op).get_rna_type()
- except AttributeError:
- rna_prop = None
- except TypeError:
- rna_prop = None
- if rna_prop:
- GetInfoOperatorRNA(rna_prop)
- for rna_info in InfoOperatorRNA.global_lookup.values():
- rna_info.build()
- for rna_prop in rna_info.args:
- rna_prop.build()
- # for rna_info in InfoStructRNA.global_lookup.values():
- # print(rna_info)
- return (
- InfoStructRNA.global_lookup,
- InfoFunctionRNA.global_lookup,
- InfoOperatorRNA.global_lookup,
- InfoPropertyRNA.global_lookup,
- )
- def main():
- struct = BuildRNAInfo()[0]
- data = []
- for _struct_id, v in sorted(struct.items()):
- struct_id_str = v.identifier # "".join(sid for sid in struct_id if struct_id)
- for base in v.get_bases():
- struct_id_str = base.identifier + "|" + struct_id_str
- props = [(prop.identifier, prop) for prop in v.properties]
- for _prop_id, prop in sorted(props):
- # if prop.type == "boolean":
- # continue
- prop_type = prop.type
- if prop.array_length > 0:
- prop_type += "[%d]" % prop.array_length
- data.append(
- "%s.%s -> %s: %s%s %s" %
- (struct_id_str, prop.identifier, prop.identifier, prop_type,
- ", (read-only)" if prop.is_readonly else "", prop.description))
- data.sort()
- if bpy.app.background:
- import sys
- sys.stderr.write("\n".join(data))
- sys.stderr.write("\n\nEOF\n")
- else:
- text = bpy.data.texts.new(name="api.py")
- text.from_string(data)
- if __name__ == "__main__":
- main()
|