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)