5088b0154d
The "bl_i18n_utils" module has been excluded I'm not sure how best to run it with reasonable code coverage.
915 lines
31 KiB
Python
915 lines
31 KiB
Python
# SPDX-FileCopyrightText: 2009-2023 Blender Authors
|
|
#
|
|
# SPDX-License-Identifier: GPL-2.0-or-later
|
|
|
|
# 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}".format(val)
|
|
else:
|
|
return str(val)
|
|
|
|
|
|
def float_as_string(f):
|
|
val_str = "{:g}".format(f)
|
|
# Ensure a `.0` suffix for whole numbers, excluding scientific notation such as `1e-05` or `1e+5`.
|
|
if '.' not in val_str and 'e' not in val_str:
|
|
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",
|
|
"py_class",
|
|
"module_name",
|
|
)
|
|
|
|
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 = []
|
|
|
|
self.py_class = get_py_class_from_rna(self.bl_rna)
|
|
self.module_name = (
|
|
self.py_class.__module__
|
|
if (self.py_class and not hasattr(bpy.types, self.identifier)) else
|
|
"bpy.types"
|
|
)
|
|
if self.module_name == "bpy_types":
|
|
self.module_name = "bpy.types"
|
|
|
|
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 = self.py_class
|
|
|
|
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}) or
|
|
# Without the `objclass` check, many inherited methods are included.
|
|
(type(attr_func) == types.MethodDescriptorType and attr_func.__objclass__ == self.py_class)
|
|
):
|
|
functions.append((identifier, attr))
|
|
return functions
|
|
|
|
def get_py_c_properties_getset(self):
|
|
import types
|
|
properties_getset = []
|
|
for identifier, descr in self.py_class.__dict__.items():
|
|
if type(descr) == types.GetSetDescriptorType:
|
|
properties_getset.append((identifier, descr))
|
|
return properties_getset
|
|
|
|
def __str__(self):
|
|
|
|
txt = ""
|
|
txt += self.identifier
|
|
if self.base:
|
|
txt += "({:s})".format(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",
|
|
"enum_pointer",
|
|
"min",
|
|
"max",
|
|
"array_length",
|
|
"array_dimensions",
|
|
"collection_type",
|
|
"type",
|
|
"fixed_type",
|
|
"subtype",
|
|
"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.subtype = getattr(rna_prop, "subtype", "")
|
|
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
|
|
|
|
self.enum_pointer = 0
|
|
if self.type == "enum":
|
|
# WARNING: don't convert to a tuple as this causes dynamically allocated enums to access freed memory
|
|
# since freeing the iterator may free the memory used to store the internal `EnumPropertyItem` array.
|
|
# To support this properly RNA would have to support owning the dynamically allocated memory.
|
|
items = rna_prop.enum_items
|
|
items_static = tuple(rna_prop.enum_items_static)
|
|
self.enum_items[:] = [(item.identifier, item.name, item.description) for item in items]
|
|
self.is_enum_flag = rna_prop.is_enum_flag
|
|
# Prioritize static items as this is never going to be allocated data and is therefor
|
|
# will be a stable match to compare against.
|
|
item = (items_static or items)
|
|
if item:
|
|
self.enum_pointer = item[0].as_pointer()
|
|
del items, items_static, item
|
|
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})".format(", ".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}\"".format(self.default)
|
|
elif self.type == "enum":
|
|
if self.is_enum_flag:
|
|
# self.default_str = repr(self.default) # repr or set()
|
|
self.default_str = "{{{:s}}}".format(repr(list(sorted(self.default)))[1:-1])
|
|
else:
|
|
self.default_str = "'{:s}'".format(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})".format(", ".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}".format(self.identifier, default)
|
|
return self.identifier
|
|
|
|
def get_type_description(
|
|
self, *,
|
|
as_ret=False,
|
|
as_arg=False,
|
|
class_fmt="{:s}",
|
|
mathutils_fmt="{:s}",
|
|
collection_id="Collection",
|
|
enum_descr_override=None,
|
|
):
|
|
"""
|
|
:arg enum_descr_override: Optionally override items for enum.
|
|
Otherwise expand the literal items.
|
|
:type enum_descr_override: string or None when unset.
|
|
"""
|
|
type_str = ""
|
|
if self.fixed_type is None:
|
|
type_str += self.type
|
|
if self.array_length:
|
|
if self.array_dimensions[1] != 0:
|
|
dimension_str = " of {:s} items".format(
|
|
" * ".join(str(d) for d in self.array_dimensions if d != 0)
|
|
)
|
|
type_str += " multi-dimensional array" + dimension_str
|
|
else:
|
|
dimension_str = " of {:d} items".format(self.array_length)
|
|
type_str += " array" + dimension_str
|
|
|
|
# Describe mathutils types; logic mirrors pyrna_math_object_from_array
|
|
if self.type == "float":
|
|
if self.subtype == "MATRIX":
|
|
if self.array_length in {9, 16}:
|
|
type_str = (mathutils_fmt.format("Matrix")) + dimension_str
|
|
elif self.subtype in {"COLOR", "COLOR_GAMMA"}:
|
|
if self.array_length == 3:
|
|
type_str = (mathutils_fmt.format("Color")) + dimension_str
|
|
elif self.subtype in {"EULER", "QUATERNION"}:
|
|
if self.array_length == 3:
|
|
type_str = (mathutils_fmt.format("Euler")) + " rotation" + dimension_str
|
|
elif self.array_length == 4:
|
|
type_str = (mathutils_fmt.format("Quaternion")) + " rotation" + dimension_str
|
|
elif self.subtype in {
|
|
'COORDINATES', 'TRANSLATION', 'DIRECTION', 'VELOCITY',
|
|
'ACCELERATION', 'XYZ', 'XYZ_LENGTH',
|
|
}:
|
|
if 2 <= self.array_length <= 4:
|
|
type_str = (mathutils_fmt.format("Vector")) + dimension_str
|
|
|
|
if self.type in {"float", "int"}:
|
|
type_str += " in [{:s}, {:s}]".format(range_str(self.min), range_str(self.max))
|
|
elif self.type == "enum":
|
|
enum_descr = enum_descr_override
|
|
if not enum_descr:
|
|
if self.is_enum_flag:
|
|
enum_descr = "{{{:s}}}".format(", ".join(("'{:s}'".format(s[0])) for s in self.enum_items))
|
|
else:
|
|
enum_descr = "[{:s}]".format(", ".join(("'{:s}'".format(s[0])) for s in self.enum_items))
|
|
if self.is_enum_flag:
|
|
type_str += " set in {:s}".format(enum_descr)
|
|
else:
|
|
type_str += " in {:s}".format(enum_descr)
|
|
del enum_descr
|
|
|
|
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}".format(self.default_str)
|
|
|
|
else:
|
|
if self.type == "collection":
|
|
if self.collection_type:
|
|
collection_str = (
|
|
class_fmt.format(self.collection_type.identifier) +
|
|
" {:s} of ".format(collection_id)
|
|
)
|
|
else:
|
|
collection_str = "{:s} of ".format(collection_id)
|
|
else:
|
|
collection_str = ""
|
|
|
|
type_str += collection_str + (class_fmt.format(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})".format(", ".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}".format(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 = []
|
|
|
|
def _bpy_types_iterator():
|
|
# Don't report when these types are ignored.
|
|
suppress_warning = {
|
|
"bpy_func",
|
|
"bpy_prop",
|
|
"bpy_prop_array",
|
|
"bpy_prop_collection",
|
|
"bpy_struct",
|
|
"bpy_struct_meta_idprop",
|
|
}
|
|
|
|
names_unique = set()
|
|
rna_type_list = []
|
|
for rna_type_name in dir(bpy.types):
|
|
names_unique.add(rna_type_name)
|
|
rna_type = getattr(bpy.types, rna_type_name)
|
|
rna_struct = getattr(rna_type, "bl_rna", None)
|
|
if rna_struct is not None:
|
|
rna_type_list.append(rna_type)
|
|
yield (rna_type_name, rna_struct)
|
|
elif rna_type_name.startswith("_"):
|
|
# Ignore "__dir__", "__getattr__" .. etc.
|
|
pass
|
|
elif rna_type_name in suppress_warning:
|
|
pass
|
|
else:
|
|
print("rna_info.BuildRNAInfo(..): ignoring type", repr(rna_type_name))
|
|
|
|
# Now, there are some sub-classes in add-ons we also want to include.
|
|
# Cycles for e.g. these are referenced from the Scene, but not part of
|
|
# bpy.types module.
|
|
# Include all sub-classes we didn't already get from 'bpy.types'.
|
|
i = 0
|
|
while i < len(rna_type_list):
|
|
rna_type = rna_type_list[i]
|
|
for rna_sub_type in rna_type.__subclasses__():
|
|
rna_sub_struct = getattr(rna_sub_type, "bl_rna", None)
|
|
if rna_sub_struct is not None:
|
|
rna_sub_type_name = rna_sub_struct.identifier
|
|
if rna_sub_type_name not in names_unique:
|
|
names_unique.add(rna_sub_type_name)
|
|
rna_type_list.append(rna_sub_type)
|
|
# The bl_idname may not match the class name in the file.
|
|
# Always use the 'bl_idname' because using the Python
|
|
# class name causes confusion - having two names for the same thing.
|
|
# Since having two names for the same thing is trickier to support
|
|
# without a significant benefit.
|
|
yield (rna_sub_type_name, rna_sub_struct)
|
|
i += 1
|
|
|
|
for (_rna_type_name, rna_struct) in _bpy_types_iterator():
|
|
# 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] = []
|
|
|
|
del _bpy_types_iterator
|
|
|
|
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("Dependency \"{:s}\" could not be found for \"{:s}\"".format(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}".format(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}".format(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}]".format(
|
|
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}]".format(prop.array_length)
|
|
|
|
data.append(
|
|
"{:s}.{:s} -> {:s}: {:s}{:s} {:s}".format(
|
|
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("\n".join(data))
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|