#!/usr/bin/env python3 """Stream an OBJ and report the numbers that decide how to tile it for SUM Parts. Reads line by line so a multi-GB mesh does not have to fit in RAM. Reports vertex/face counts, the bounding box, whether UVs and vertex colours are present, and the material/texture references. Usage: python inspect_obj.py path/to/Block.obj [more.obj ...] """ from __future__ import annotations import sys from pathlib import Path def inspect(path: Path) -> dict: n_v = n_vt = n_vn = n_f = 0 has_vertex_color = False mtllib: list[str] = [] usemtl: set[str] = set() lo = [float("inf")] * 3 hi = [float("-inf")] * 3 with path.open("r", encoding="utf-8", errors="replace") as f: for line in f: if line.startswith("v "): n_v += 1 parts = line.split() x, y, z = float(parts[1]), float(parts[2]), float(parts[3]) if x < lo[0]: lo[0] = x if y < lo[1]: lo[1] = y if z < lo[2]: lo[2] = z if x > hi[0]: hi[0] = x if y > hi[1]: hi[1] = y if z > hi[2]: hi[2] = z # "v x y z r g b" is how some exporters carry per-vertex colour if len(parts) >= 7: has_vertex_color = True elif line.startswith("vt "): n_vt += 1 elif line.startswith("vn "): n_vn += 1 elif line.startswith("f "): n_f += 1 elif line.startswith("mtllib"): mtllib.append(line.split(maxsplit=1)[1].strip()) elif line.startswith("usemtl"): usemtl.add(line.split(maxsplit=1)[1].strip()) return { "path": path, "size_mb": path.stat().st_size / 1024 / 1024, "n_v": n_v, "n_vt": n_vt, "n_vn": n_vn, "n_f": n_f, "vertex_color": has_vertex_color, "mtllib": mtllib, "usemtl": sorted(usemtl), "lo": lo, "hi": hi, } def main() -> None: if len(sys.argv) < 2: print(__doc__) raise SystemExit(2) for arg in sys.argv[1:]: p = Path(arg) if not p.exists(): print(f"{arg}: not found") continue r = inspect(p) ext = [r["hi"][i] - r["lo"][i] for i in range(3)] area = ext[0] * ext[1] print(f"=== {p.name} ({r['size_mb']:,.0f} MB) ===") print(f" vertices : {r['n_v']:>12,}") print(f" faces : {r['n_f']:>12,}") print(f" texcoords : {r['n_vt']:>12,} {'(UV present)' if r['n_vt'] else '(NO UV)'}") print(f" normals : {r['n_vn']:>12,}") print(f" vtx color : {r['vertex_color']}") print(f" mtllib : {r['mtllib']}") print(f" materials : {len(r['usemtl'])} -> {r['usemtl'][:5]}" f"{' ...' if len(r['usemtl']) > 5 else ''}") print(f" bbox min : {[round(v, 2) for v in r['lo']]}") print(f" bbox max : {[round(v, 2) for v in r['hi']]}") print(f" extent : {[round(v, 2) for v in ext]} (metres, local)") print(f" footprint : {area / 1e6:.3f} km^2") if area: print(f" density : {r['n_f'] / area:,.1f} faces/m^2") # SUM Parts tiles are ~252 m square; this is how many that footprint is print(f" ~252m tiles: {max(1, round(ext[0] / 252)) * max(1, round(ext[1] / 252))}") print() if __name__ == "__main__": main()