# Packs a .gltf with external buffers and images into one self-contained .glb, which is the only # model format Singe loads. Usage: python3 util/packGlb.py Model.gltf Model.glb import json, os, struct, sys src, out = sys.argv[1], sys.argv[2] base = os.path.dirname(src) g = json.load(open(src)) binary = bytearray() views = g.setdefault('bufferViews', []) def add(data): while len(binary) % 4: binary.append(0) off = len(binary); binary.extend(data) return off # Buffers: concatenate, remapping views. offsets = [] for b in g.get('buffers', []): data = open(os.path.join(base, b['uri']), 'rb').read() offsets.append(add(data)) for v in views: v['byteOffset'] = v.get('byteOffset', 0) + offsets[v.get('buffer', 0)] v['buffer'] = 0 # Images: each becomes a buffer view with a mime type. The table is exhaustive on purpose: a # two-way png/jpeg guess quietly labelled every .ktx2 as image/jpeg, which Singe survives because it # sniffs the magic number, and which no other glTF reader has to. mimeTypes = {'png': 'image/png', 'jpg': 'image/jpeg', 'jpeg': 'image/jpeg', 'ktx2': 'image/ktx2', 'webp': 'image/webp', 'basis': 'image/basis'} for img in g.get('images', []): uri = img.pop('uri') data = open(os.path.join(base, uri), 'rb').read() ext = uri.lower().rsplit('.', 1)[-1] if ext not in mimeTypes: sys.exit('No mime type known for ' + uri + '; add it to mimeTypes in util/packGlb.py.') views.append({'buffer': 0, 'byteOffset': add(data), 'byteLength': len(data)}) img['bufferView'] = len(views) - 1 img['mimeType'] = mimeTypes[ext] while len(binary) % 4: binary.append(0) g['buffers'] = [{'byteLength': len(binary)}] js = json.dumps(g, separators=(',', ':')).encode() while len(js) % 4: js += b' ' with open(out, 'wb') as f: f.write(struct.pack('<4sII', b'glTF', 2, 12 + 8 + len(js) + 8 + len(binary))) f.write(struct.pack('