""" Fetches assets from polyhaven.com (all CC0) for the outpost into models/anchorage/source_polyhaven///. python -I build/fetch_polyhaven.py The user, 2026-10-14: "polyhaven.com is free to use gather many things from there its permitted". What is fetched is the list below; source_polyhaven/manifest.json records every file with its size and address. Nothing here is run or opened: the files are pictures (jpg, hdr) and models as glTF (text, a binary buffer and pictures). """ import json import os import sys import urllib.request HERE = os.path.dirname(os.path.abspath(__file__)) OUT = os.path.join(os.path.dirname(HERE), "source_polyhaven") UA = {"User-Agent": "TheDescent-AnchorageOutpost/1.0 (Blender asset fetch)"} HDRIS = {"snow_field": "4k"} # overcast sky over a snow plain: the scene's light TEXTURES = {"snow_02": "2k", "metal_plate": "2k"} # drifts and snow on roofs; tread plate for walkways MODELS = { # site props, with their textures at this size "exterior_aircon_unit": "2k", "portable_generator": "2k", "propane_tank": "1k", "small_lpg_tank": "1k", "utility_box_01": "1k", "utility_box_02": "1k", "power_box_01": "1k", "security_camera_01": "1k", "security_light": "1k", "portable_searchlight": "1k", "Barrel_01": "1k", "Barrel_02": "1k", "barrel_03": "1k", "old_military_crate": "1k", "wooden_military_crate": "1k", "plastic_crate_01": "1k", "metal_jerrycan": "1k", "concrete_road_barrier": "1k", "lifebuoy": "1k", "ocean_buoy": "2k", "ladder_sectioned_01": "1k", "tool_cart": "1k", "hand_truck": "1k", "street_lamp_02": "1k", } TEX_MAPS = ("Diffuse", "nor_gl", "Rough", "AO", "arm", "Displacement") def get_json(url): req = urllib.request.Request(url, headers=UA) with urllib.request.urlopen(req, timeout=60) as r: return json.loads(r.read().decode("utf-8")) def fetch(url, path): os.makedirs(os.path.dirname(path), exist_ok=True) if os.path.isfile(path) and os.path.getsize(path) > 0: return os.path.getsize(path), False req = urllib.request.Request(url, headers=UA) with urllib.request.urlopen(req, timeout=300) as r, open(path + ".part", "wb") as f: while True: chunk = r.read(1 << 20) if not chunk: break f.write(chunk) os.replace(path + ".part", path) return os.path.getsize(path), True def safe(name): """A file name from the site, kept inside its own folder.""" name = name.replace("\\", "/") parts = [p for p in name.split("/") if p not in ("", ".", "..")] return os.path.join(*parts) def main(): manifest = {"source": "https://polyhaven.com", "licence": "CC0 1.0 (public domain)", "assets": {}} total = 0 for kind, table in (("hdris", HDRIS), ("textures", TEXTURES), ("models", MODELS)): for aid, res in table.items(): try: files = get_json("https://api.polyhaven.com/files/" + aid) except Exception as e: # noqa: BLE001 print("FAILED %s: %s" % (aid, e)) continue folder = os.path.join(OUT, kind, aid) got = [] if kind == "hdris": want = [(files["hdri"][res]["hdr"]["url"], "%s_%s.hdr" % (aid, res))] elif kind == "textures": want = [] for m in TEX_MAPS: if m in files and res in files[m] and "jpg" in files[m][res]: u = files[m][res]["jpg"]["url"] want.append((u, u.rsplit("/", 1)[-1])) else: g = files["gltf"][res]["gltf"] want = [(g["url"], g["url"].rsplit("/", 1)[-1])] for rel, inc in (g.get("include") or {}).items(): want.append((inc["url"], rel)) for url, rel in want: if not url.startswith("https://dl.polyhaven.org/"): print("skipped (not from the site's own store): %s" % url) continue size, new = fetch(url, os.path.join(folder, safe(rel))) got.append({"file": safe(rel).replace("\\", "/"), "bytes": size, "url": url}) total += size manifest["assets"][aid] = {"kind": kind, "resolution": res, "page": "https://polyhaven.com/a/" + aid, "files": got} print("%-9s %-26s %-3s %2d files %7.1f MB" % (kind, aid, res, len(got), sum(f["bytes"] for f in got) / 1e6)) with open(os.path.join(OUT, "manifest.json"), "w", encoding="utf-8") as f: json.dump(manifest, f, indent=1) print("in all %.1f MB in %s" % (total / 1e6, OUT)) main()