Skip to content

model

FreeBodyEngine.core.files.loaders.model #

MODEL_MANIFEST_KEY = '_ENGINE_model_manifest.json' module-attribute #

GLBParser(data_bytes) #

Splits a .glb (binary glTF) blob into its JSON and binary chunks, per the glTF 2.0 binary container spec - the JSON chunk always comes first and is required, the BIN chunk is optional and, when present, always second.

Parses data_bytes immediately - raises ValueError if it isn't a valid, version-2 .glb container, or if its declared length exceeds the actual data.

bin_chunk = None instance-attribute #

bytes = data_bytes instance-attribute #

json_chunk = None instance-attribute #

get_binary_buffer() #

Returns the raw BIN chunk bytes, or b"" if the file had none.

get_json() #

Returns the parsed glTF JSON document (the same schema a .gltf text file contains).

GLTFParser(gltf_dict, bin_data, base_path=None) #

Builds a Model from a parsed glTF JSON document (gltf_dict) plus its binary buffer data - works for both .gltf (with bin_data either empty or decoded from a data: URI) and .glb (with bin_data from GLBParser.get_binary_buffer()). base_path is the directory the source file lives in, needed to resolve any uri that points at an external sibling file (a separate .bin or image) rather than embedding its data inline.

Stores the parsed inputs; does no work itself - see the class docstring.

COMPONENT_FORMAT = {5120: 'b', 5121: 'B', 5122: 'h', 5123: 'H', 5125: 'I', 5126: 'f'} class-attribute instance-attribute #

NUMPY_DTYPE = {5120: np.int8, 5121: np.uint8, 5122: np.int16, 5123: np.uint16, 5125: np.uint32, 5126: np.float32} class-attribute instance-attribute #

NUM_COMPONENTS = {'SCALAR': 1, 'VEC2': 2, 'VEC3': 3, 'VEC4': 4, 'MAT2': 4, 'MAT3': 9, 'MAT4': 16} class-attribute instance-attribute #

base_path = base_path instance-attribute #

bin_data = bin_data or b'' instance-attribute #

gltf = gltf_dict instance-attribute #

build_materials(renderer, pipeline) #

Builds a real Material (via pipeline.create_material()) for every entry in the glTF's materials array, keyed by material name (a generated material_{i} for an unnamed one, or {name}_{i} if that name collides with an earlier material). Relies on self._textures (image_index -> loaded Texture) already being populated - build_model() always builds textures before calling this. Note glTF's own default baseColorFactor/roughnessFactor/ etc. are white/rough/metallic, not black - this mirrors those defaults exactly rather than reusing whatever this engine's own Material default happens to be.

build_model(model_name=None, scale=None) #

Builds a Model from this glTF: every image is decoded and uploaded once and every material is built once, both cached on self (as _textures/_materials) so calling build_model() again on the same parser - e.g. once per piece of a multi-mesh file like a glTF chess set - doesn't redo that work.

model_name selects which mesh(es) to include: None (the default) combines every mesh in the file into one Model, a string selects the mesh with that exact name (raising ValueError if none matches), and an int selects the mesh at that index directly - needed because glTF mesh names aren't guaranteed unique. scale, if given, is applied to every included mesh's raw vertex positions.

Raises ValueError if the glTF has no meshes at all, or if model_name doesn't resolve to any mesh.

get_accessor_data(accessor_index) #

Returns a (count, num_components) NumPy array for the accessor - every caller already immediately wraps this in np.asarray()/ np.array(), so returning an array directly (instead of a Python list of tuples) is a transparent change, not an API break.

Reinterprets the raw buffer bytes straight into a strided NumPy view (np.ndarray(buffer=...)) rather than calling struct.unpack() once per element in a Python loop - for a glTF file with real vertex counts (tens of thousands to low millions for a detailed mesh), that per-element Python loop dominates load time; this does the equivalent reinterpretation as a single C-level pass.

get_image_data(image_index) #

Returns the raw encoded bytes (PNG/JPEG, not yet decoded) of image image_index - from the GLB BIN chunk, a base64 data: URI, or (only when base_path was given) an external sibling file.

resolve_texture(texture_ref, textures) #

Looks a glTF texture reference (e.g. a material's baseColorTexture, {"index": ..., "texCoord": ...}) up in textures (an image-index -> loaded-Texture map, as built by build_model()) and returns the resolved Texture, or None if texture_ref is None or names a texture/image that doesn't exist.

bake_gltf_to_fbmesh(parser, image_name_prefix) #

Pre-decodes every mesh primitive and material in parser's glTF into a flat .fbmesh: a b"FBMH" magic, a little-endian uint32 giving the length of a JSON header, that header, then a binary blob the header's per-array {"offset", "length"} entries point into (each array 4-byte aligned within the blob, so a reader can np.frombuffer() straight over it with no copy or re-parse - see load_baked_model()).

This is intentionally independent of build_model()/build_materials() above: those call get_service('renderer')/get_service('graphics') to create real GL textures/materials immediately, which needs a live GL context this build-time tool never has. Baked materials instead reference textures by path - embedded images are extracted to real files (returned as {relative_name: bytes} for the caller to bundle alongside everything else) and loaded back through the normal load_file()/load_texture() path at runtime, the same as any other project image (including, in a release build, that image's own shared- atlas packing).

Some material-building logic (default values, factor-vs-texture resolution) is deliberately mirrored from build_materials() rather than shared with it, since that version is wired directly to GL object creation - keep the two in sync by hand if glTF material handling ever changes.

load_baked_model(file) #

Loads a .fbmesh written by bake_gltf_to_fbmesh() - every array is a direct np.frombuffer() view over the file's own bytes (no JSON accessor indirection, no per-element decode of any kind), and every material's textures load through the normal load_file() path (so a release build's shared texture atlas still applies to them).

load_model(file) #

Loads a .gltf or .glb model file into a Model.

Checks build/builder.py's model manifest first - if this asset has a pre-baked .fbmesh from a freebody build/dev build, loads that instead (via load_baked_model(), no glTF parsing or texture decode at all). Otherwise parses the glTF fresh on the spot (resolving any external .bin/image siblings relative to the file's own directory) and builds every mesh in it into one combined Model - callers that need one specific mesh out of a multi-mesh file go through GLTFParser.build_model() directly instead of this loader.

Raises ValueError if file's extension is neither .gltf nor .glb.