1099 lines
38 KiB
Python
1099 lines
38 KiB
Python
from typing import NamedTuple, Dict, List, Optional, Union, get_type_hints
|
|
import typing
|
|
import json
|
|
import os
|
|
|
|
get_origin = getattr(typing, "get_origin", lambda o: getattr(o, "__origin__", None))
|
|
get_args = getattr(typing, "get_args", lambda o: getattr(o, "__args__", None))
|
|
|
|
class BaseField(NamedTuple):
|
|
name: str
|
|
json_name: str
|
|
type: type
|
|
optional: bool
|
|
list: bool
|
|
dict: bool
|
|
|
|
def make_field(name, base):
|
|
json_name = "".join((p.title() if i > 0 else p) for i,p in enumerate(name.split("_")))
|
|
origin, args = get_origin(base), get_args(base)
|
|
optional = list_ = dict_ = False
|
|
if origin == Union and len(args) == 2 and type(None) in args:
|
|
base = args[args.index(type(None)) ^ 1]
|
|
optional = True
|
|
origin, args = get_origin(base), get_args(base)
|
|
if origin in (List, list):
|
|
base = args[0]
|
|
list_ = True
|
|
if origin in (Dict, dict) and len(args) == 2 and args[0] == str:
|
|
base = args[1]
|
|
dict_ = True
|
|
return BaseField(name, json_name, base, optional, list_, dict_)
|
|
|
|
def get_fields(cls):
|
|
if not hasattr(cls, "_base_fields"):
|
|
cls._base_fields = [make_field(name, typ) for name, typ in get_type_hints(cls).items()]
|
|
return cls._base_fields
|
|
|
|
def from_json(typ, json_obj):
|
|
if typ in (str, int, float, bool):
|
|
if not isinstance(json_obj, typ):
|
|
raise ValueError(f"Wrong type for {typ.__name__}, got {type(json_obj)} expected {typ}")
|
|
return json_obj
|
|
|
|
values = { }
|
|
for field in get_fields(typ):
|
|
json_field = json_obj.get(field.json_name)
|
|
if not field.optional and json_field is None:
|
|
raise ValueError(f"Missing non-optional field {typ.__name__}.{field.json_name}")
|
|
if json_field is None:
|
|
values[field.name] = None
|
|
continue
|
|
|
|
if field.list:
|
|
if not isinstance(json_field, list):
|
|
raise ValueError(f"Wrong type for {typ.__name__}.{field.json_name}, got {type(json_obj)} expected a list of {field.type}")
|
|
values[field.name] = [from_json(field.type, js) for js in json_field]
|
|
elif field.dict:
|
|
if not isinstance(json_field, dict):
|
|
raise ValueError(f"Wrong type for {typ.__name__}.{field.json_name}, got {type(json_obj)} expected an object containing {field.type}")
|
|
values[field.name] = { k: from_json(field.type, js) for k,js in json_field.items() }
|
|
else:
|
|
values[field.name] = from_json(field.type, json_field)
|
|
return typ(**values)
|
|
|
|
def to_json_imp(typ, obj):
|
|
if typ in (str, int, float, bool):
|
|
if not isinstance(obj, typ):
|
|
raise TypeError(f"Wrong type for {typ.__name__}, got {type(obj)} expected {typ}")
|
|
return obj
|
|
|
|
values = { }
|
|
for field in get_fields(typ):
|
|
obj_field = getattr(obj, field.name, None)
|
|
if not field.optional and obj_field is None:
|
|
raise TypeError(f"Missing non-optional field {typ.__name__}.{field.name}")
|
|
if obj_field is None:
|
|
values[field.json_name] = None
|
|
continue
|
|
|
|
if field.list:
|
|
if not isinstance(obj_field, list):
|
|
raise TypeError(f"Wrong type for {typ.__name__}.{field.name}, got {type(obj_field)} expected a list of {field.type}")
|
|
values[field.json_name] = [to_json_imp(field.type, js) for js in obj_field]
|
|
elif field.dict:
|
|
if not isinstance(obj_field, dict):
|
|
raise TypeError(f"Wrong type for {typ.__name__}.{field.name}, got {type(obj_field)} expected an object containing {field.type}")
|
|
values[field.json_name] = { k: to_json_imp(field.type, ob) for k,ob in obj_field.items() }
|
|
else:
|
|
values[field.json_name] = to_json_imp(field.type, obj_field)
|
|
return values
|
|
|
|
def to_json(obj):
|
|
return to_json_imp(type(obj), obj)
|
|
|
|
class Base:
|
|
def __init__(self, **kwargs):
|
|
for field in get_fields(type(self)):
|
|
val = kwargs.get(field.name, None)
|
|
if not field.optional and val is None:
|
|
if field.list:
|
|
val = []
|
|
elif field.dict:
|
|
val = { }
|
|
else:
|
|
val = field.type()
|
|
setattr(self, field.name, val)
|
|
|
|
def __repr__(self):
|
|
cls = type(self)
|
|
fs = ", ".join(f"{f.name}={getattr(self, f.name, None)!r}" for f in get_fields(cls))
|
|
return f"{cls.__name__}({fs})"
|
|
|
|
class Mod(Base):
|
|
pass
|
|
|
|
class Constant(Base):
|
|
name: str
|
|
value_int: int
|
|
|
|
class Type(Base):
|
|
key: str
|
|
base_name: str
|
|
size: Dict[str, int]
|
|
align: Dict[str, int]
|
|
kind: str
|
|
is_nullable: bool
|
|
is_const: bool
|
|
is_pod: bool
|
|
is_function: bool
|
|
array_length: Optional[int]
|
|
func_args: List["Argument"]
|
|
inner: Optional[str]
|
|
|
|
class Field(Base):
|
|
type: str
|
|
name: str
|
|
kind: str
|
|
private: bool
|
|
offset: Dict[str, int]
|
|
comment: Optional[str]
|
|
union_sized: Dict[str, bool]
|
|
union_preferred: bool
|
|
|
|
class Struct(Base):
|
|
name: str
|
|
short_name: str
|
|
fields: List[Field]
|
|
comment: Optional[str]
|
|
vertex_attrib_type: Optional[str]
|
|
is_union: bool
|
|
is_anonymous: bool
|
|
is_list: bool
|
|
is_element: bool
|
|
is_pod: bool
|
|
is_input: bool
|
|
is_callback: bool
|
|
is_interface: bool
|
|
member_functions: List[str]
|
|
member_globals: List[str]
|
|
|
|
class EnumValue(Base):
|
|
name: str
|
|
short_name: str
|
|
short_name_raw: str
|
|
value: int
|
|
comment: Optional[str]
|
|
flag: bool
|
|
auxiliary: bool
|
|
|
|
class Enum(Base):
|
|
name: str
|
|
short_name: str
|
|
values: List[str]
|
|
flag: bool
|
|
|
|
class Argument(Base):
|
|
name: str
|
|
type: str
|
|
kind: str
|
|
is_return: bool
|
|
by_ref: bool
|
|
return_ref: bool
|
|
|
|
class StringArgument(Base):
|
|
name: str
|
|
pointer_index: int
|
|
length_index: int
|
|
|
|
class ArrayArgument(Base):
|
|
name: str
|
|
pointer_index: int
|
|
num_index: int
|
|
|
|
class BlobArgument(Base):
|
|
name: str
|
|
pointer_index: int
|
|
size_index: int
|
|
|
|
class Function(Base):
|
|
name: str
|
|
short_name: str
|
|
pretty_name: str
|
|
return_type: str
|
|
return_kind: str
|
|
kind: str
|
|
arguments: List[Argument]
|
|
string_arguments: List[StringArgument]
|
|
array_arguments: List[ArrayArgument]
|
|
blob_arguments: List[BlobArgument]
|
|
is_inline: bool
|
|
member_name: Optional[str]
|
|
ffi_name: Optional[str]
|
|
catch_name: Optional[str]
|
|
len_name: Optional[str]
|
|
is_ffi: bool
|
|
is_catch: bool
|
|
is_len: bool
|
|
has_error: bool
|
|
has_panic: bool
|
|
alloc_type: str
|
|
return_array_scale: int
|
|
nullable_return: bool
|
|
|
|
class Global(Base):
|
|
name: str
|
|
short_name: str
|
|
type: str
|
|
|
|
class Typedef(Base):
|
|
name: str
|
|
short_name: str
|
|
type: str
|
|
|
|
class Declaration(Base):
|
|
kind: str
|
|
name: str
|
|
|
|
class MemberFunction(Base):
|
|
func: str
|
|
self_type: str
|
|
member_name: str
|
|
self_index: int
|
|
|
|
class MemberGlobal(Base):
|
|
global_name: str
|
|
self_type: str
|
|
member_name: str
|
|
|
|
class File(Base):
|
|
constants: Dict[str, Constant]
|
|
types: Dict[str, Type]
|
|
structs: Dict[str, Struct]
|
|
enums: Dict[str, Enum]
|
|
enum_values: Dict[str, EnumValue]
|
|
functions: Dict[str, Function]
|
|
globals: Dict[str, Global]
|
|
typedefs: Dict[str, Typedef]
|
|
member_functions: Dict[str, MemberFunction]
|
|
member_globals: Dict[str, MemberGlobal]
|
|
declarations: List[Declaration]
|
|
element_types: List[str]
|
|
|
|
def init_type(file, typ, key, mods):
|
|
name = typ["name"]
|
|
t = Type(base_name=name, key=key)
|
|
|
|
if typ.get("kind", "") == "define":
|
|
t.kind = "define"
|
|
|
|
if mods:
|
|
mods = mods[:]
|
|
|
|
if mods[0]["type"] == "nullable":
|
|
for n in range(len(mods)):
|
|
if mods[n]["type"] == "pointer":
|
|
mods[n]["nullable"] = True
|
|
mods = mods[1:]
|
|
break
|
|
|
|
if mods[0]["type"] == "const":
|
|
for n in range(len(mods)):
|
|
if mods[n]["type"] == "pointer":
|
|
mods[n]["const"] = True
|
|
mods = mods[1:]
|
|
break
|
|
|
|
if mods:
|
|
mod = mods[-1]
|
|
mt = mod["type"]
|
|
mods = mods[:-1]
|
|
|
|
if mt == "pointer":
|
|
t.kind = "pointer"
|
|
t.is_nullable = mod.get("nullable", False)
|
|
t.is_const = mod.get("const", False)
|
|
t.inner = parse_type_imp(file, typ, mods)
|
|
elif mt == "array":
|
|
length = eval_const(file, mod["length"])
|
|
t.kind = "array"
|
|
t.array_length = length
|
|
t.inner = parse_type_imp(file, typ, mods)
|
|
elif mt == "function":
|
|
t.kind = "function"
|
|
t.func_args = [parse_argument(file, arg) for arg in mod["args"]]
|
|
t.inner = parse_type_imp(file, typ, mods)
|
|
elif mt == "const":
|
|
t.kind = "const"
|
|
t.inner = parse_type_imp(file, typ, mods)
|
|
elif mt == "unsafe":
|
|
t.kind = "unsafe"
|
|
t.inner = parse_type_imp(file, typ, mods)
|
|
else:
|
|
raise ValueError(f"Unhandled mod {mt}")
|
|
|
|
return t
|
|
|
|
def eval_const(file, expr):
|
|
if expr in file.constants:
|
|
return file.constants[expr].value_int
|
|
elif expr in file.enum_values:
|
|
return file.enum_values[expr].value
|
|
else:
|
|
return int(expr, base=0)
|
|
|
|
def parse_type_imp(file, typ, mods):
|
|
key = typ["name"]
|
|
for mod in mods:
|
|
mt = mod["type"]
|
|
if mt == "pointer":
|
|
key = key + "*"
|
|
elif mt == "nullable":
|
|
key = key + "?"
|
|
elif mt == "unsafe":
|
|
key = key + " unsafe"
|
|
elif mt == "const":
|
|
key = key + " const"
|
|
elif mt == "array":
|
|
length = eval_const(file, mod["length"])
|
|
key = key + f"[{length}]"
|
|
elif mt == "function":
|
|
args = [parse_type(file, a["type"]) for a in mod["args"]]
|
|
args_str = ",".join(args)
|
|
key = key + f"({args_str})"
|
|
else:
|
|
raise ValueError(f"Unhandled mod {mt}")
|
|
|
|
if key not in file.types:
|
|
file.types[key] = init_type(file, typ, key, mods)
|
|
|
|
return key
|
|
|
|
def parse_type(file, typ, in_func=False):
|
|
mods = typ["mods"]
|
|
if in_func:
|
|
mods = [m for m in mods if m["type"] not in ("function", "inline", "abi")]
|
|
return parse_type_imp(file, typ, mods)
|
|
|
|
def parse_field(file: File, st: Struct, decl, anon_path):
|
|
anon_id = 0
|
|
|
|
kind, name = decl["kind"], decl.get("name")
|
|
if kind == "group":
|
|
for inner in decl["decls"]:
|
|
parse_field(file, st, inner, anon_path)
|
|
elif kind == "struct":
|
|
anon_name = f"{anon_path}.{anon_id}"
|
|
anon_id += 1
|
|
ast = Struct(name=anon_name)
|
|
ast.is_anonymous = True
|
|
at = Type(base_name=anon_name, key=anon_name)
|
|
at.kind = "struct"
|
|
file.types[anon_name] = at
|
|
file.structs[anon_name] = ast
|
|
if decl["structKind"] == "union":
|
|
ast.is_union = True
|
|
for inner in decl["decls"]:
|
|
parse_field(file, ast, inner, anon_name)
|
|
fd = Field(name="", type=anon_name)
|
|
st.fields.append(fd)
|
|
elif kind == "decl":
|
|
typ = parse_type(file, decl["type"])
|
|
fd = Field(name=name, type=typ)
|
|
if name.startswith("_"):
|
|
fd.private = True
|
|
st.fields.append(fd)
|
|
|
|
def postprocess_fields(file: File, st: Struct):
|
|
for field in st.fields:
|
|
tp = file.types[field.type]
|
|
if tp.kind == "array" and tp.inner == "char":
|
|
len_ix = find_index(st.fields, lambda f: f.name == field.name + "_length")
|
|
if len_ix >= 0:
|
|
field.kind = "inlineBuf"
|
|
st.fields[len_ix].kind = "inlineBufLength"
|
|
|
|
def shorten_name(name: str, prefix: str):
|
|
for part in prefix.split("_"):
|
|
if name.lower().startswith(part.lower() + "_"):
|
|
name = name[len(part)+1:]
|
|
if prefix.lower().endswith("_flags") and name.lower().startswith("flag_"):
|
|
name = name[5:]
|
|
return name
|
|
|
|
def shorten_global(name: str):
|
|
if name.lower().startswith("ufbx_"):
|
|
name = name[5:]
|
|
return name
|
|
|
|
class EnumCtx:
|
|
def __init__(self):
|
|
self.next_value = 0
|
|
self.hit_aux = False
|
|
|
|
def parse_enum(file: File, en: Enum, decl, ctx):
|
|
kind, name = decl["kind"], decl.get("name")
|
|
if kind == "group":
|
|
for inner in decl["decls"]:
|
|
parse_enum(file, en, inner, ctx)
|
|
elif kind == "decl":
|
|
if name in ("UFBX_ELEMENT_TYPE_FIRST_ATTRIB"):
|
|
ctx.hit_aux = True
|
|
ev = EnumValue(name=name, flag=en.flag)
|
|
ev.short_name_raw = shorten_name(name, en.name)
|
|
ev.short_name = ev.short_name_raw
|
|
ev.auxiliary = ctx.hit_aux
|
|
if ev.short_name[0].isdigit():
|
|
ev.short_name = "E" + ev.short_name
|
|
val = decl.get("value")
|
|
if val:
|
|
ev.value = eval_const(file, val)
|
|
else:
|
|
ev.value = ctx.next_value
|
|
ctx.next_value = ev.value + 1
|
|
en.values.append(name)
|
|
file.enum_values[name] = ev
|
|
|
|
def parse_argument(file: File, arg):
|
|
name = arg["name"]
|
|
typ = parse_type(file, arg["type"])
|
|
return Argument(name=name, type=typ, kind="")
|
|
|
|
def parse_func(file: File, decl):
|
|
name = decl["name"]
|
|
mods = decl["type"]["mods"]
|
|
func_mod = next(m for m in mods if m["type"] == "function")
|
|
|
|
fn = Function(name=name)
|
|
fn.short_name = shorten_global(name)
|
|
fn.pretty_name = fn.short_name
|
|
if fn.pretty_name.endswith("_len"):
|
|
fn.pretty_name = fn.pretty_name[:-4]
|
|
fn.return_type = parse_type(file, decl["type"], in_func=True)
|
|
fn.is_inline = any(m for m in mods if m["type"] == "inline")
|
|
fn.arguments = [parse_argument(file, arg) for arg in func_mod["args"]]
|
|
|
|
file.functions[name] = fn
|
|
file.declarations.append(Declaration(kind="function", name=name))
|
|
|
|
def parse_global(file: File, decl):
|
|
name = decl["name"]
|
|
|
|
gl = Global(name=name)
|
|
gl.short_name = shorten_global(name)
|
|
gl.type = parse_type(file, decl["type"])
|
|
file.globals[name] = gl
|
|
file.declarations.append(Declaration(kind="global", name=name))
|
|
|
|
def parse_typedef(file: File, decl):
|
|
name = decl["name"]
|
|
|
|
td = Typedef(name=name)
|
|
td.short_name = shorten_global(name)
|
|
td.type = parse_type(file, decl["type"])
|
|
file.typedefs[name] = td
|
|
file.declarations.append(Declaration(kind="typedef", name=name))
|
|
|
|
t = Type(name=name, base_name=name)
|
|
t.kind = "typedef"
|
|
t.key = name
|
|
t.inner = td.type
|
|
file.types[name] = t
|
|
|
|
def parse_decl(file: File, decl):
|
|
kind, name = decl["kind"], decl.get("name")
|
|
if kind == "group":
|
|
for inner in decl["decls"]:
|
|
parse_decl(file, inner)
|
|
elif kind == "struct":
|
|
st = Struct(name=name)
|
|
st.short_name = shorten_global(name)
|
|
t = Type(base_name=name, key=name)
|
|
t.kind = "struct"
|
|
file.structs[name] = st
|
|
file.types[name] = t
|
|
file.declarations.append(Declaration(kind="struct", name=name))
|
|
if decl.get("isList"):
|
|
st.is_list = True
|
|
for inner in decl["decls"]:
|
|
parse_field(file, st, inner, name)
|
|
elif kind == "enum":
|
|
en = Enum(name=name)
|
|
en.short_name = shorten_global(name)
|
|
t = Type(base_name=name, key=name)
|
|
t.kind = "enum"
|
|
|
|
if name.endswith("_flags"):
|
|
en.flag = True
|
|
|
|
file.enums[name] = en
|
|
file.types[name] = t
|
|
file.declarations.append(Declaration(kind="enum", name=name))
|
|
ctx = EnumCtx()
|
|
for inner in decl["decls"]:
|
|
parse_enum(file, en, inner, ctx)
|
|
elif kind == "decl":
|
|
if decl["declKind"] == "typedef":
|
|
if decl["type"]["name"] != decl["name"]:
|
|
parse_typedef(file, decl)
|
|
elif decl["isFunction"]:
|
|
parse_func(file, decl)
|
|
elif decl["kind"] == "decl":
|
|
parse_global(file, decl)
|
|
elif kind == "enumType":
|
|
line = decl["line"]
|
|
enum_name = decl["enumName"]
|
|
last_value = decl["lastValue"]
|
|
count_name = decl["countName"]
|
|
en = file.enums.get(enum_name)
|
|
if not en:
|
|
raise RuntimeError(f"ufbx.h:{line}: UFBX_ENUM_TYPE() has undefined enum name {enum_name}")
|
|
max_value = max((file.enum_values[n] for n in en.values), key=lambda v: v.value)
|
|
if max_value.name != last_value:
|
|
if last_value in file.enum_values:
|
|
wrong_value = file.enum_values[last_value].value
|
|
else:
|
|
wrong_value = "(undefined)"
|
|
raise RuntimeError(f"ufbx.h:{line}: UFBX_ENUM_TYPE() has wrong highest value ({last_value} = {wrong_value}), actual highest value is ({max_value.name} = {max_value.value})")
|
|
count = max_value.value + 1
|
|
file.constants[count_name] = Constant(name=count_name, value_int=count)
|
|
file.declarations.append(Declaration(kind="enumCount", name=count_name))
|
|
|
|
def parse_file(decls):
|
|
file = File()
|
|
|
|
# HACK
|
|
file.constants["UFBX_ERROR_STACK_MAX_DEPTH"] = Constant(name="UFBX_ERROR_STACK_MAX_DEPTH", value_int=8)
|
|
file.constants["UFBX_PANIC_MESSAGE_LENGTH"] = Constant(name="UFBX_PANIC_MESSAGE_LENGTH", value_int=128)
|
|
file.constants["UFBX_ERROR_INFO_LENGTH"] = Constant(name="UFBX_ERROR_INFO_LENGTH", value_int=256)
|
|
|
|
for decl in decls:
|
|
parse_decl(file, decl)
|
|
return file
|
|
|
|
class Arch:
|
|
def __init__(self, name, sizes):
|
|
self.name = name
|
|
self.sizes = sizes
|
|
|
|
sizes_base = {
|
|
"void": 0,
|
|
"char": 1,
|
|
"bool": 1,
|
|
"uint8_t": 1,
|
|
"int8_t": 1,
|
|
"uint16_t": 2,
|
|
"int16_t": 2,
|
|
"uint32_t": 4,
|
|
"int32_t": 4,
|
|
"uint64_t": 4,
|
|
"int64_t": 4,
|
|
"float": 4,
|
|
"double": 8,
|
|
"enum": 4,
|
|
}
|
|
|
|
sizes_32bit = {
|
|
**sizes_base,
|
|
"size_t": 4,
|
|
"ptrdiff_t": 4,
|
|
"*": 4,
|
|
}
|
|
|
|
sizes_64bit = {
|
|
**sizes_base,
|
|
"size_t": 8,
|
|
"ptrdiff_t": 8,
|
|
"*": 8,
|
|
}
|
|
|
|
archs = [
|
|
Arch("x86", sizes_32bit),
|
|
Arch("x64", sizes_64bit),
|
|
Arch("wasm", sizes_32bit),
|
|
]
|
|
|
|
def layout_struct(arch: Arch, file: File, typ: Type, st: Struct):
|
|
offset = 0
|
|
align = 0
|
|
union_size = 0
|
|
for field in st.fields:
|
|
field_type = file.types[field.type]
|
|
layout_type(arch, file, field_type)
|
|
field_size = field_type.size[arch.name]
|
|
field_align = field_type.align[arch.name]
|
|
|
|
align = max(align, field_align)
|
|
while offset % field_align != 0:
|
|
offset += 1
|
|
|
|
field.offset[arch.name] = offset
|
|
|
|
if st.is_union:
|
|
union_size = max(union_size, field_size)
|
|
else:
|
|
offset += field_size
|
|
|
|
while offset % align != 0:
|
|
offset += 1
|
|
|
|
if st.is_union:
|
|
offset = union_size
|
|
for field in st.fields:
|
|
field_type = file.types[field.type]
|
|
field_size = field_type.size[arch.name]
|
|
if field_size == union_size:
|
|
field.union_sized[arch.name] = True
|
|
|
|
typ.size[arch.name] = offset
|
|
typ.align[arch.name] = align
|
|
|
|
def layout_type(arch: Arch, file: File, typ: Type):
|
|
if arch.name in typ.size:
|
|
return
|
|
|
|
if typ.kind == "pointer":
|
|
size = arch.sizes["*"]
|
|
typ.size[arch.name] = size
|
|
typ.align[arch.name] = size
|
|
elif typ.kind == "":
|
|
size = arch.sizes[typ.base_name]
|
|
typ.size[arch.name] = size
|
|
typ.align[arch.name] = size
|
|
elif typ.kind == "enum":
|
|
size = arch.sizes["enum"]
|
|
typ.size[arch.name] = size
|
|
typ.align[arch.name] = size
|
|
elif typ.kind == "typedef":
|
|
inner = file.types[typ.inner]
|
|
layout_type(arch, file, inner)
|
|
typ.size[arch.name] = inner.size[arch.name]
|
|
typ.align[arch.name] = inner.align[arch.name]
|
|
elif typ.kind in ("const", "unsafe"):
|
|
inner = file.types[typ.inner]
|
|
layout_type(arch, file, inner)
|
|
typ.size[arch.name] = inner.size[arch.name]
|
|
typ.align[arch.name] = inner.align[arch.name]
|
|
elif typ.kind == "struct":
|
|
st = file.structs[typ.base_name]
|
|
layout_struct(arch, file, typ, st)
|
|
elif typ.kind == "array":
|
|
inner = file.types[typ.inner]
|
|
layout_type(arch, file, inner)
|
|
typ.size[arch.name] = inner.size[arch.name] * typ.array_length
|
|
typ.align[arch.name] = inner.align[arch.name]
|
|
elif typ.kind == "function":
|
|
inner = file.types[typ.inner]
|
|
layout_type(arch, file, inner)
|
|
typ.size[arch.name] = 0
|
|
typ.align[arch.name] = 0
|
|
elif typ.kind == "define":
|
|
typ.size[arch.name] = 0
|
|
typ.align[arch.name] = 0
|
|
else:
|
|
raise ValueError(f"Unhandled type kind: {typ.kind}")
|
|
|
|
def layout_file(arch: Arch, file: File):
|
|
for typ in file.types.values():
|
|
layout_type(arch, file, typ)
|
|
|
|
def to_pascal(name_snake):
|
|
parts = name_snake.lower().split("_")
|
|
for n in range(0, len(parts)):
|
|
parts[n] = parts[n].title()
|
|
return "".join(parts)
|
|
|
|
def to_camel(name_snake):
|
|
parts = name_snake.lower().split("_")
|
|
for n in range(1, len(parts)):
|
|
parts[n] = parts[n].title()
|
|
return "".join(parts)
|
|
|
|
prim_types = {
|
|
"bool",
|
|
"int8_t",
|
|
"uint8_t",
|
|
"int16_t",
|
|
"uint16_t",
|
|
"int32_t",
|
|
"uint32_t",
|
|
"int64_t",
|
|
"uint64_t",
|
|
"size_t",
|
|
"float",
|
|
"double",
|
|
}
|
|
|
|
pod_types = {
|
|
*prim_types,
|
|
}
|
|
|
|
ref_types = {
|
|
"ufbx_scene",
|
|
"ufbx_element",
|
|
"ufbx_anim",
|
|
"ufbx_props",
|
|
"ufbx_vertex_real",
|
|
"ufbx_vertex_vec2",
|
|
"ufbx_vertex_vec3",
|
|
"ufbx_vertex_vec4",
|
|
"ufbx_geometry_cache",
|
|
"ufbx_cache_channel",
|
|
"ufbx_cache_frame",
|
|
"ufbx_texture_file",
|
|
}
|
|
|
|
pod_structs = [
|
|
"ufbx_vec2",
|
|
"ufbx_vec3",
|
|
"ufbx_vec4",
|
|
"ufbx_quat",
|
|
"ufbx_matrix",
|
|
"ufbx_transform",
|
|
"ufbx_edge",
|
|
"ufbx_face",
|
|
"ufbx_lod_level",
|
|
"ufbx_skin_vertex",
|
|
"ufbx_skin_weight",
|
|
"ufbx_tangent",
|
|
"ufbx_keyframe",
|
|
"ufbx_curve_point",
|
|
"ufbx_surface_point",
|
|
"ufbx_topo_edge",
|
|
"ufbx_coordinate_axes",
|
|
]
|
|
|
|
input_structs = [
|
|
"ufbx_allocator_opts",
|
|
"ufbx_open_memory_opts",
|
|
"ufbx_load_opts",
|
|
"ufbx_evaluate_opts",
|
|
"ufbx_tessellate_curve_opts",
|
|
"ufbx_tessellate_surface_opts",
|
|
"ufbx_subdivide_opts",
|
|
"ufbx_geometry_cache_opts",
|
|
"ufbx_geometry_cache_data_opts",
|
|
]
|
|
|
|
interface_structs = [
|
|
"ufbx_allocator",
|
|
"ufbx_stream",
|
|
]
|
|
|
|
union_prefer = {
|
|
"ufbx_vec2.0": 0,
|
|
"ufbx_vec3.0": 0,
|
|
"ufbx_vec4.0": 0,
|
|
"ufbx_quat.0": 0,
|
|
"ufbx_matrix.0": 0,
|
|
"ufbx_scene.0": 0,
|
|
}
|
|
|
|
member_functions = [
|
|
MemberFunction(func="ufbx_find_prop_len", self_type="ufbx_props"),
|
|
MemberFunction(func="ufbx_find_prop", self_type="ufbx_props"),
|
|
MemberFunction(func="ufbx_find_real_len", self_type="ufbx_props"),
|
|
MemberFunction(func="ufbx_find_real", self_type="ufbx_props"),
|
|
MemberFunction(func="ufbx_find_vec3_len", self_type="ufbx_props"),
|
|
MemberFunction(func="ufbx_find_vec3", self_type="ufbx_props"),
|
|
MemberFunction(func="ufbx_find_int_len", self_type="ufbx_props"),
|
|
MemberFunction(func="ufbx_find_int", self_type="ufbx_props"),
|
|
MemberFunction(func="ufbx_find_bool_len", self_type="ufbx_props"),
|
|
MemberFunction(func="ufbx_find_bool", self_type="ufbx_props"),
|
|
MemberFunction(func="ufbx_find_string_len", self_type="ufbx_props"),
|
|
MemberFunction(func="ufbx_find_string", self_type="ufbx_props"),
|
|
MemberFunction(func="ufbx_find_element_len", self_type="ufbx_scene"),
|
|
MemberFunction(func="ufbx_find_element", self_type="ufbx_scene"),
|
|
MemberFunction(func="ufbx_find_node_len", self_type="ufbx_scene"),
|
|
MemberFunction(func="ufbx_find_node", self_type="ufbx_scene"),
|
|
MemberFunction(func="ufbx_find_anim_stack_len", self_type="ufbx_scene"),
|
|
MemberFunction(func="ufbx_find_anim_stack", self_type="ufbx_scene"),
|
|
MemberFunction(func="ufbx_find_anim_prop_len", self_type="ufbx_anim_layer"),
|
|
MemberFunction(func="ufbx_find_anim_prop", self_type="ufbx_anim_layer"),
|
|
MemberFunction(func="ufbx_find_anim_props", self_type="ufbx_anim_layer"),
|
|
MemberFunction(func="ufbx_get_compatible_matrix_for_normals", self_type="ufbx_node"),
|
|
MemberFunction(func="ufbx_evaluate_curve", self_type="ufbx_anim_curve", member_name="evaluate"),
|
|
MemberFunction(func="ufbx_evaluate_anim_value_real", self_type="ufbx_anim_value", member_name="evaluate_real"),
|
|
MemberFunction(func="ufbx_evaluate_anim_value_vec2", self_type="ufbx_anim_value", member_name="evaluate_vec2"),
|
|
MemberFunction(func="ufbx_evaluate_anim_value_vec3", self_type="ufbx_anim_value", member_name="evaluate_vec3"),
|
|
MemberFunction(func="ufbx_evaluate_transform", self_type="ufbx_node"),
|
|
MemberFunction(func="ufbx_evaluate_blend_weight", self_type="ufbx_blend_channel"),
|
|
MemberFunction(func="ufbx_evaluate_scene", self_type="ufbx_scene", member_name="evaluate"),
|
|
MemberFunction(func="ufbx_find_prop_texture_len", self_type="ufbx_material"),
|
|
MemberFunction(func="ufbx_find_prop_texture", self_type="ufbx_material"),
|
|
MemberFunction(func="ufbx_find_shader_prop_len", self_type="ufbx_shader"),
|
|
MemberFunction(func="ufbx_find_shader_prop", self_type="ufbx_shader"),
|
|
MemberFunction(func="ufbx_catch_get_skin_vertex_matrix", self_type="ufbx_skin_deformer"),
|
|
MemberFunction(func="ufbx_get_skin_vertex_matrix", self_type="ufbx_skin_deformer"),
|
|
MemberFunction(func="ufbx_get_blend_shape_vertex_offset", self_type="ufbx_blend_shape", member_name="get_vertex_offset"),
|
|
MemberFunction(func="ufbx_get_blend_vertex_offset", self_type="ufbx_blend_deformer", member_name="get_vertex_offset"),
|
|
MemberFunction(func="ufbx_add_blend_shape_vertex_offsets", self_type="ufbx_blend_shape", member_name="add_vertex_offsets"),
|
|
MemberFunction(func="ufbx_add_blend_vertex_offsets", self_type="ufbx_blend_deformer", member_name="add_vertex_offsets"),
|
|
MemberFunction(func="ufbx_evaluate_nurbs_basis", self_type="ufbx_nurbs_basis", member_name="evaluate"),
|
|
MemberFunction(func="ufbx_evaluate_nurbs_curve", self_type="ufbx_nurbs_curve", member_name="evaluate"),
|
|
MemberFunction(func="ufbx_evaluate_nurbs_surface", self_type="ufbx_nurbs_surface", member_name="evaluate"),
|
|
MemberFunction(func="ufbx_tessellate_nurbs_curve", self_type="ufbx_nurbs_curve", member_name="tessellate"),
|
|
MemberFunction(func="ufbx_tessellate_nurbs_surface", self_type="ufbx_nurbs_surface", member_name="tessellate"),
|
|
MemberFunction(func="ufbx_catch_triangulate_face", self_type="ufbx_mesh"),
|
|
MemberFunction(func="ufbx_triangulate_face", self_type="ufbx_mesh"),
|
|
MemberFunction(func="ufbx_subdivide_mesh", self_type="ufbx_mesh", member_name="subdivide"),
|
|
MemberFunction(func="ufbx_read_geometry_cache_real", self_type="ufbx_cache_frame", member_name="read_real"),
|
|
MemberFunction(func="ufbx_sample_geometry_cache_real", self_type="ufbx_cache_channel", member_name="sample_real"),
|
|
MemberFunction(func="ufbx_read_geometry_cache_vec3", self_type="ufbx_cache_frame", member_name="read_vec3"),
|
|
MemberFunction(func="ufbx_sample_geometry_cache_vec3", self_type="ufbx_cache_channel", member_name="sample_vec3"),
|
|
]
|
|
|
|
member_globals = [
|
|
MemberGlobal(global_name="ufbx_empty_string", self_type="ufbx_string", member_name="empty"),
|
|
MemberGlobal(global_name="ufbx_empty_blob", self_type="ufbx_blob", member_name="empty"),
|
|
MemberGlobal(global_name="ufbx_identity_matrix", self_type="ufbx_matrix", member_name="identity"),
|
|
MemberGlobal(global_name="ufbx_identity_transform", self_type="ufbx_transform", member_name="identity"),
|
|
MemberGlobal(global_name="ufbx_zero_vec2", self_type="ufbx_vec2", member_name="zero"),
|
|
MemberGlobal(global_name="ufbx_zero_vec3", self_type="ufbx_vec3", member_name="zero"),
|
|
MemberGlobal(global_name="ufbx_zero_vec4", self_type="ufbx_vec4", member_name="zero"),
|
|
MemberGlobal(global_name="ufbx_identity_quat", self_type="ufbx_quat", member_name="identity"),
|
|
MemberGlobal(global_name="ufbx_axes_right_handed_y_up", self_type="ufbx_coordinate_axes", member_name="right_handed_y_up"),
|
|
MemberGlobal(global_name="ufbx_axes_right_handed_z_up", self_type="ufbx_coordinate_axes", member_name="right_handed_z_up"),
|
|
MemberGlobal(global_name="ufbx_axes_left_handed_y_up", self_type="ufbx_coordinate_axes", member_name="left_handed_y_up"),
|
|
MemberGlobal(global_name="ufbx_axes_left_handed_z_up", self_type="ufbx_coordinate_axes", member_name="left_handed_z_up"),
|
|
]
|
|
|
|
def find_index(list, predicate):
|
|
for i,v in enumerate(list):
|
|
if predicate(v):
|
|
return i
|
|
return -1
|
|
|
|
if __name__ == "__main__":
|
|
src_path = os.path.dirname(os.path.realpath(__file__))
|
|
path = os.path.join(src_path, "build", "ufbx.json")
|
|
with open(path, "rt") as f:
|
|
js = json.load(f)
|
|
file = parse_file(js)
|
|
|
|
for name in file.enums["ufbx_element_type"].values:
|
|
ev = file.enum_values[name]
|
|
if ev.auxiliary: continue
|
|
name = name.lower().replace("ufbx_element_", "ufbx_")
|
|
st = file.structs[name]
|
|
st.is_element = True
|
|
file.element_types.append(name)
|
|
ref_types.add(name)
|
|
|
|
file.structs["ufbx_vertex_real"].vertex_attrib_type = "ufbx_real"
|
|
file.structs["ufbx_vertex_vec2"].vertex_attrib_type = "ufbx_vec2"
|
|
file.structs["ufbx_vertex_vec3"].vertex_attrib_type = "ufbx_vec3"
|
|
file.structs["ufbx_vertex_vec4"].vertex_attrib_type = "ufbx_vec4"
|
|
|
|
for pod in pod_structs:
|
|
file.structs[pod].is_pod = True
|
|
|
|
for inp in input_structs:
|
|
file.structs[inp].is_input = True
|
|
|
|
for inp in interface_structs:
|
|
file.structs[inp].is_interface = True
|
|
|
|
for st in file.structs.values():
|
|
if st.name.endswith("_cb"):
|
|
st.is_callback = True
|
|
|
|
for name, index in union_prefer.items():
|
|
st = file.structs[name]
|
|
st.fields[index].union_preferred = True
|
|
|
|
for st in file.structs.values():
|
|
if not st.is_union: continue
|
|
if any(f.union_preferred for f in st.fields): continue
|
|
st.fields[-1].union_preferred = True
|
|
|
|
for typ in file.types.values():
|
|
if typ.kind == "struct":
|
|
st = file.structs[typ.base_name]
|
|
if st.is_pod:
|
|
typ.is_pod = True
|
|
elif typ.kind == "":
|
|
if typ.base_name in pod_types:
|
|
typ.is_pod = True
|
|
|
|
for typ in file.types.values():
|
|
if typ.kind == "function":
|
|
typ.is_function = True
|
|
|
|
for typ in file.types.values():
|
|
if typ.kind == "typedef":
|
|
inner = file.types[typ.inner]
|
|
if inner.is_pod:
|
|
typ.is_pod = True
|
|
if inner.is_function:
|
|
typ.is_function = True
|
|
|
|
for typ in file.types.values():
|
|
if typ.kind == "pointer":
|
|
inner = file.types[typ.inner]
|
|
if inner.is_function:
|
|
typ.is_function = True
|
|
|
|
for st in file.structs.values():
|
|
postprocess_fields(file, st)
|
|
|
|
for func in file.functions.values():
|
|
for index, arg in enumerate(func.arguments):
|
|
if arg.kind: continue
|
|
if arg.type == "char const*":
|
|
len_name = arg.name + "_len"
|
|
len_index = find_index(func.arguments, lambda a: a.name == len_name)
|
|
if len_index >= 0:
|
|
arg.kind = "stringPointer"
|
|
func.arguments[len_index].kind = "stringLength"
|
|
sa = StringArgument(name=arg.name, pointer_index=index, len_index=len_index)
|
|
func.string_arguments.append(sa)
|
|
continue
|
|
|
|
typ = file.types[arg.type]
|
|
if arg.name == "retval":
|
|
arg.is_return = True
|
|
assert typ.kind == "pointer"
|
|
inner = file.types[typ.inner]
|
|
if inner.is_pod:
|
|
arg.kind = "pod"
|
|
func.return_kind = "pod"
|
|
elif typ.kind == "pointer":
|
|
inner = file.types[typ.inner]
|
|
|
|
num_name = "num_" + arg.name
|
|
num_index = find_index(func.arguments, lambda a: a.name == num_name)
|
|
|
|
size_name = arg.name + "_size"
|
|
size_index = find_index(func.arguments, lambda a: a.name == size_name)
|
|
|
|
if num_index < 0 and inner.key == "char":
|
|
num_index = size_index
|
|
|
|
if num_index >= 0:
|
|
arg.kind = "arrayPointer"
|
|
func.arguments[num_index].kind = "arrayLength"
|
|
aa = ArrayArgument(name=arg.name, pointer_index=index, num_index=num_index)
|
|
func.array_arguments.append(aa)
|
|
elif size_index >= 0 and inner.key == "void":
|
|
arg.kind = "blobPointer"
|
|
func.arguments[size_index].kind = "blobSize"
|
|
ba = BlobArgument(name=arg.name, pointer_index=index, size_index=size_index)
|
|
func.blob_arguments.append(ba)
|
|
elif typ.is_const and inner.is_pod:
|
|
arg.by_ref = True
|
|
arg.kind = "pod"
|
|
elif typ.is_const and inner.key in input_structs:
|
|
arg.by_ref = True
|
|
arg.kind = "input"
|
|
elif not typ.is_const and inner.key == "ufbx_error":
|
|
arg.by_ref = True
|
|
arg.kind = "error"
|
|
func.has_error = True
|
|
elif not typ.is_const and inner.key == "ufbx_panic":
|
|
arg.by_ref = True
|
|
arg.kind = "panic"
|
|
func.has_panic = True
|
|
elif typ.is_const and inner.key == "ufbx_stream":
|
|
arg.by_ref = True
|
|
arg.kind = "stream"
|
|
elif typ.is_const and inner.key in ref_types:
|
|
arg.kind = "ref"
|
|
elif typ.is_pod:
|
|
if arg.type in prim_types:
|
|
arg.kind = "prim"
|
|
else:
|
|
arg.kind = "pod"
|
|
elif typ.kind == "enum":
|
|
arg.kind = "enum"
|
|
|
|
rtyp = file.types[func.return_type]
|
|
if rtyp.kind == "enum":
|
|
func.return_kind = "enum"
|
|
elif func.return_type in prim_types:
|
|
func.return_kind = "prim"
|
|
elif rtyp.kind == "pointer":
|
|
inner = file.types[rtyp.inner]
|
|
if inner.key in ref_types:
|
|
func.return_kind = "ref"
|
|
|
|
for func in file.functions.values():
|
|
for index, arg in enumerate(func.arguments):
|
|
if arg.name == "retval": continue
|
|
typ = file.types[arg.type]
|
|
if typ.base_name in file.element_types:
|
|
arg.return_ref = True
|
|
if typ.base_name in { "ufbx_scene", "ufbx_anim", "ufbx_element", "ufbx_geometry_cache", "ufbx_props" }:
|
|
arg.return_ref = True
|
|
if len(func.arguments) == 1 and func.arguments[0].type == "void":
|
|
func.arguments.clear()
|
|
|
|
for func in file.functions.values():
|
|
if "_ffi_" in func.name:
|
|
func.is_ffi = True
|
|
non_ffi = func.name.replace("_ffi_", "_", 1)
|
|
file.functions[non_ffi].ffi_name = func.name
|
|
elif "_catch_" in func.name:
|
|
func.is_catch = True
|
|
non_catch = func.name.replace("_catch_", "_", 1)
|
|
file.functions[non_catch].catch_name = func.name
|
|
elif func.name.endswith("_len"):
|
|
func.is_len = True
|
|
non_len = func.name[:-4]
|
|
file.functions[non_len].len_name = func.name
|
|
|
|
file.functions["ufbx_load_file"].alloc_type = "scene"
|
|
file.functions["ufbx_load_file_len"].alloc_type = "scene"
|
|
file.functions["ufbx_load_memory"].alloc_type = "scene"
|
|
file.functions["ufbx_load_stream"].alloc_type = "scene"
|
|
file.functions["ufbx_load_stream_prefix"].alloc_type = "scene"
|
|
file.functions["ufbx_load_stdio"].alloc_type = "scene"
|
|
file.functions["ufbx_load_stdio_prefix"].alloc_type = "scene"
|
|
file.functions["ufbx_evaluate_scene"].alloc_type = "scene"
|
|
file.functions["ufbx_subdivide_mesh"].alloc_type = "mesh"
|
|
file.functions["ufbx_tessellate_nurbs_curve"].alloc_type = "line"
|
|
file.functions["ufbx_tessellate_nurbs_surface"].alloc_type = "mesh"
|
|
file.functions["ufbx_load_geometry_cache"].alloc_type = "geometryCache"
|
|
file.functions["ufbx_load_geometry_cache_len"].alloc_type = "geometryCache"
|
|
|
|
file.functions["ufbx_free_scene"].kind = "free"
|
|
file.functions["ufbx_free_mesh"].kind = "free"
|
|
file.functions["ufbx_free_line_curve"].kind = "free"
|
|
file.functions["ufbx_free_geometry_cache"].kind = "free"
|
|
|
|
file.functions["ufbx_retain_scene"].kind = "retain"
|
|
file.functions["ufbx_retain_mesh"].kind = "retain"
|
|
file.functions["ufbx_retain_line_curve"].kind = "retain"
|
|
file.functions["ufbx_retain_geometry_cache"].kind = "retain"
|
|
|
|
file.functions["ufbx_triangulate_face"].return_array_scale = 3
|
|
file.functions["ufbx_ffi_triangulate_face"].return_array_scale = 3
|
|
file.functions["ufbx_read_geometry_cache_real"].return_array_scale = 1
|
|
file.functions["ufbx_sample_geometry_cache_real"].return_array_scale = 1
|
|
file.functions["ufbx_read_geometry_cache_vec3"].return_array_scale = 1
|
|
file.functions["ufbx_sample_geometry_cache_vec3"].return_array_scale = 1
|
|
|
|
for func in file.functions.values():
|
|
t = file.types[func.return_type]
|
|
if t.kind == "pointer" and not func.alloc_type:
|
|
func.nullable_return = True
|
|
|
|
for mf in member_functions:
|
|
if not mf.member_name:
|
|
mf.member_name = mf.func.replace("ufbx_", "")
|
|
fn = file.functions[mf.func]
|
|
|
|
self_index = -1
|
|
for ix, arg in enumerate(fn.arguments):
|
|
t = file.types[arg.type]
|
|
while t.inner:
|
|
t = file.types[t.inner]
|
|
if t.base_name == mf.self_type:
|
|
self_index = ix
|
|
if self_index < 0:
|
|
raise RuntimeError(f"Could not find self ({mf.self_type}) for member {mf.func}")
|
|
mf.self_index = self_index
|
|
|
|
file.structs[mf.self_type].member_functions.append(mf.func)
|
|
|
|
file.member_functions[mf.func] = mf
|
|
|
|
for mg in member_globals:
|
|
file.structs[mg.self_type].member_globals.append(mg.global_name)
|
|
file.member_globals[mg.global_name] = mg
|
|
|
|
allow_missing_enum_count = ["ufbx_progress_result"]
|
|
for en in file.enums.values():
|
|
if en.flag: continue
|
|
if en.name in allow_missing_enum_count: continue
|
|
count_name = en.name.upper() + "_COUNT"
|
|
if count_name not in file.constants:
|
|
raise RuntimeError(f"enum {en.name} is missing UFBX_ENUM_TYPE()")
|
|
|
|
for arch in archs:
|
|
layout_file(arch, file)
|
|
|
|
path_dst = os.path.join(src_path, "build", "ufbx_typed.json")
|
|
with open(path_dst, "wt") as f:
|
|
json.dump(to_json(file), f, indent=2)
|