handship / scripts /fetch-kenney-assets.py
ImTamsi's picture
fix: serve static game from repo root for HF Space
1fe789b
Raw
History Blame Contribute Delete
5.83 kB
#!/usr/bin/env python3
"""Download Kenney Space Kit (CC0) → assets/ (GLTF or OBJ)."""
from __future__ import annotations
import json
import shutil
import urllib.request
import zipfile
from pathlib import Path
URL = "https://kenney.nl/media/pages/assets/space-kit/cceeafbd0c-1677698978/kenney_space-kit.zip"
ROOT = Path(__file__).resolve().parent.parent
ASSETS = ROOT / "assets"
ZIP_PATH = Path("/tmp/kenney-space-kit.zip")
SHIP_KEYS = ("craft_racer", "craft_speeder", "craft_miner", "craft_cargo", "ship")
ASTEROID_KEYS = ("meteor_detailed", "meteor_half", "meteor", "rock")
PROJECTILE_KEYS = ("laser", "rocket", "weapon", "bullet")
def download_zip() -> zipfile.ZipFile:
print("Downloading Kenney Space Kit (CC0)…")
urllib.request.urlretrieve(URL, ZIP_PATH)
return zipfile.ZipFile(ZIP_PATH)
def index_members(zf: zipfile.ZipFile) -> dict[str, list[str]]:
by_stem: dict[str, list[str]] = {}
for name in zf.namelist():
if name.endswith("/"):
continue
stem = Path(name).stem.lower()
by_stem.setdefault(stem, []).append(name)
return by_stem
def pick_path(by_stem: dict[str, list[str]], keys: tuple[str, ...], ext: str) -> str | None:
for key in keys:
kl = key.lower()
for stem, paths in by_stem.items():
if kl in stem:
for p in paths:
if p.lower().endswith(ext):
return p
return None
def patch_gltf_bin_uri(gltf_path: Path, bin_name: str) -> None:
data = json.loads(gltf_path.read_text(encoding="utf-8"))
for buf in data.get("buffers", []):
if "uri" in buf and str(buf["uri"]).endswith(".bin"):
buf["uri"] = bin_name
gltf_path.write_text(json.dumps(data, separators=(",", ":")), encoding="utf-8")
def extract_gltf(zf: zipfile.ZipFile, member: str, dest_stem: str) -> None:
ASSETS.mkdir(parents=True, exist_ok=True)
tmp = ASSETS / "_tmp"
tmp.mkdir(exist_ok=True)
zf.extract(member, tmp)
src = tmp / member
dest_gltf = ASSETS / f"{dest_stem}.gltf"
shutil.move(src, dest_gltf)
bin_member = member.replace(".gltf", ".bin")
if bin_member in zf.namelist():
zf.extract(bin_member, tmp)
src_bin = tmp / bin_member
dest_bin = ASSETS / f"{dest_stem}.bin"
shutil.move(src_bin, dest_bin)
patch_gltf_bin_uri(dest_gltf, f"{dest_stem}.bin")
shutil.rmtree(tmp, ignore_errors=True)
print(f" glTF → {dest_stem}.gltf")
def extract_obj_mtl(zf: zipfile.ZipFile, obj_member: str, dest_stem: str) -> None:
ASSETS.mkdir(parents=True, exist_ok=True)
tmp = ASSETS / "_tmp"
tmp.mkdir(exist_ok=True)
zf.extract(obj_member, tmp)
shutil.move(tmp / obj_member, ASSETS / f"{dest_stem}.obj")
mtl_member = obj_member.replace(".obj", ".mtl")
if mtl_member in zf.namelist():
zf.extract(mtl_member, tmp)
shutil.move(tmp / mtl_member, ASSETS / f"{dest_stem}.mtl")
shutil.rmtree(tmp, ignore_errors=True)
print(f" OBJ → {dest_stem}.obj")
def main() -> None:
zf = download_zip()
by_stem = index_members(zf)
exts = sorted({Path(n).suffix.lower() for names in by_stem.values() for n in names})
print(f"Archive extensions: {', '.join(exts)}")
use_gltf = any(p.endswith(".gltf") for names in by_stem.values() for p in names)
use_obj = any(p.endswith(".obj") for names in by_stem.values() for p in names)
if not use_gltf and not use_obj:
sample = [n for names in by_stem.values() for n in names][:30]
print("Sample paths:", *sample, sep="\n ")
raise SystemExit("No GLTF or OBJ models in archive")
fmt = "gltf" if use_gltf else "obj"
ext = f".{fmt}" if fmt == "gltf" else ".obj"
print(f"Using format: {fmt.upper()}")
ship = pick_path(by_stem, SHIP_KEYS, ext)
asteroid = pick_path(by_stem, ASTEROID_KEYS, ext)
projectile = pick_path(by_stem, PROJECTILE_KEYS, ext)
if not ship:
print("Craft-like stems:", [s for s in by_stem if "craft" in s][:15])
raise SystemExit("Ship model not found")
print("Extracting…")
if use_gltf:
extract_gltf(zf, ship, "ship")
if asteroid:
extract_gltf(zf, asteroid, "asteroid")
else:
shutil.copy(ASSETS / "ship.gltf", ASSETS / "asteroid.gltf")
if (ASSETS / "ship.bin").exists():
shutil.copy(ASSETS / "ship.bin", ASSETS / "asteroid.bin")
patch_gltf_bin_uri(ASSETS / "asteroid.gltf", "asteroid.bin")
if projectile:
extract_gltf(zf, projectile, "projectile")
else:
shutil.copy(ASSETS / "ship.gltf", ASSETS / "projectile.gltf")
if (ASSETS / "ship.bin").exists():
shutil.copy(ASSETS / "ship.bin", ASSETS / "projectile.bin")
patch_gltf_bin_uri(ASSETS / "projectile.gltf", "projectile.bin")
else:
extract_obj_mtl(zf, ship, "ship")
if asteroid:
extract_obj_mtl(zf, asteroid, "asteroid")
else:
shutil.copy(ASSETS / "ship.obj", ASSETS / "asteroid.obj")
if (ASSETS / "ship.mtl").exists():
shutil.copy(ASSETS / "ship.mtl", ASSETS / "asteroid.mtl")
if projectile:
extract_obj_mtl(zf, projectile, "projectile")
else:
shutil.copy(ASSETS / "ship.obj", ASSETS / "projectile.obj")
if (ASSETS / "ship.mtl").exists():
shutil.copy(ASSETS / "ship.mtl", ASSETS / "projectile.mtl")
zf.close()
(ASSETS / "format.txt").write_text(fmt, encoding="utf-8")
for p in sorted(ASSETS.iterdir()):
if p.name not in {".gitkeep", "_tmp"}:
print(f" {p.name} ({p.stat().st_size} bytes)")
print(f"Done — {ASSETS}")
if __name__ == "__main__":
main()