""" Copies of scanned surfaces from the user's Fab library (Assets/VaultCache/FabLibrary: raw 4K texture sets) for the station's own geometry, at 2K: models/anchorage/textures/scans//{BaseColor,Normal,Roughness,AO}.jpg python build/make_scans.py [size, default 2048] The user pointed at the vault cache for materials on 2026-10-14 ("check for materials for vault cache"). Used are the sets an outpost on the ice can be built of; the list is SCANS below. The normal maps are DirectX style (made for Unreal); the copies have their green channel turned over, which is what Blender reads. textures/scans.json says which set each key comes from, how large the scanned area is, and where the full-size files are. """ import json import os import sys from PIL import Image Image.MAX_IMAGE_PIXELS = None HERE = os.path.dirname(os.path.abspath(__file__)) ROOT = os.path.dirname(HERE) VAULT = os.path.normpath(os.path.join(ROOT, "..", "..", "Assets", "VaultCache", "FabLibrary")) OUT = os.path.join(ROOT, "textures", "scans") # key: (folder in the Fab library, what it is used for) SCANS = { "concrete_dark": ("Concrete_Facade-0fc78edf", "dark precast panels with a joint: the structure behind the armour"), # the next two are sets the user had brought into the open file themselves (2026-10-14), so they are the ones used "concrete_base": ("Rough_Concrete_Wall-42c1f2e1", "board-marked cast concrete with run-off streaks: plinths, footings, saddles"), "concrete_floor": ("Plastered_Concrete_Wall-1f631631", "smooth light concrete: pads, slabs, kerbs"), } MAPS = ("BaseColor", "Normal", "Roughness", "AO") def main(): size = int(sys.argv[1]) if len(sys.argv) > 1 else 2048 report = {} for key, (folder, use) in SCANS.items(): src = None for dp, dn, fn in os.walk(os.path.join(VAULT, folder)): if any(f.endswith("_BaseColor.jpg") for f in fn): src = dp if src is None: raise SystemExit("texture set not found: %s" % folder) meta = {} for f in os.listdir(src): if f.endswith(".json"): with open(os.path.join(src, f), encoding="utf-8") as fh: j = json.load(fh) meta["name"], meta["id"] = j.get("name"), j.get("id") for m in j.get("meta") or []: if m.get("key") == "scanArea": meta["scan_area"] = m.get("value") area = (meta.get("scan_area") or "2x2 m").replace("m", "").strip().split("x") os.makedirs(os.path.join(OUT, key), exist_ok=True) files = {} for m in MAPS: hit = [f for f in os.listdir(src) if f.endswith("_%s.jpg" % m)] if not hit: continue im = Image.open(os.path.join(src, hit[0])).convert("RGB") if im.size[0] > size: im = im.resize((size, int(round(size * im.size[1] / im.size[0]))), Image.LANCZOS) if m == "Normal": r, g, b = im.split() im = Image.merge("RGB", (r, g.point(lambda v: 255 - v), b)) out = os.path.join(OUT, key, m + ".jpg") im.save(out, quality=92) files[m] = "scans/%s/%s.jpg" % (key, m) report[key] = {"set": folder, "name": meta.get("name"), "id": meta.get("id"), "use": use, "size_m": [float(area[0]), float(area[1])], "files": files, "source": os.path.relpath(src, ROOT).replace("\\", "/"), "normal": "green channel turned over (OpenGL)"} print("%-16s %-34s %s m %s" % (key, folder, "x".join(area), ", ".join(files))) with open(os.path.join(ROOT, "textures", "scans.json"), "w", encoding="utf-8") as f: json.dump(report, f, indent=1) main()