Files
coven/modules/ufbx/bindgen/ufbx_ir.py

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)