blender/release/scripts/modules/rna_info.py

664 lines
21 KiB
Python

# ##### 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
class InfoStructRNA:
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 = getattr(bpy.types, self.identifier)
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:
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.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.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
if self.array_length:
self.default = tuple(getattr(rna_prop, "default_array", ()))
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)
self.default_str = "" # fallback
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()
else:
self.default_str = "'%s'" % self.default
elif self.array_length:
self.default_str = ''
# special case for floats
if len(self.default) > 0:
if self.type == "float":
self.default_str = "(%s)" % ", ".join(float_as_string(f) for f in self.default)
if not self.default_str:
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:
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")
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:
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:
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):
op_class = getattr(bpy.types, self.identifier)
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():
# 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:
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:
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()
for rna_info in InfoStructRNA.global_lookup.values():
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()
if 1:
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()
except AttributeError:
rna_prop = None
except TypeError:
rna_prop = None
if rna_prop:
GetInfoOperatorRNA(rna_prop.bl_rna)
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
if __name__ == "__main__":
import rna_info
struct = rna_info.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)