Audit -> normalize (16 GLBs + thumbs) -> web/assets/manifest.json (the contract Lanes B/C/D read) -> facade gap-fill. Step 4 facades generated locally on the M3 Ultra via MODELBEAST flux_local (FLUX.2-klein-4B / MLX on MPS): 23 skins, free, ~5.7s each, blank signboard, eyeballed, harvested, mapped to shop types. Every registry type now has >=3 facades (stall 2->4, closing the Lane A gap). Corner-lot -side walls tagged face:"side" with empty types so no shop front ever selects a windowless wall. manifest validates green. Still gated on John (both optional -- the game runs asset-free): publish the 16 GLBs to 3GOD (needs GOD3_PW), MeshGod hero props (~$2.70). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
80 lines
3.5 KiB
Python
80 lines
3.5 KiB
Python
#!/usr/bin/env python3
|
|
"""Publish the normalized PROCITY GLBs + thumbnails to the shared 3GOD depot.
|
|
|
|
Mirrors the house push contract (meshgod/server.py:_push_3god) exactly:
|
|
* auth cookie: 3g = hmac_sha256(GOD3_PW, "3god-ok").hexdigest()
|
|
* GLB POST {depot}/api/upload?name=<file> body=glb model/gltf-binary
|
|
* thumb POST {depot}/api/thumb?name=<file> body=png image/png
|
|
|
|
Credentials come from the environment — this script never contains or logs the secret:
|
|
export GOD3_PW=... # the shared depot password (ask John)
|
|
export GOD3_DEPOT=https://digalot.fyi/3god # optional; this is the default
|
|
python3 pipeline/publish.py # publish every GLB in _normalized/
|
|
python3 pipeline/publish.py --dry-run # list what WOULD upload, no network
|
|
|
|
This is the one outward-facing step in the lane; run it deliberately. Uploads are
|
|
idempotent (same name overwrites) and reversible via the depot's own delete.
|
|
"""
|
|
import os, sys, glob, hmac, hashlib, json, urllib.request, urllib.parse
|
|
|
|
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
|
NORM = os.path.join(ROOT, "pipeline", "_normalized")
|
|
THUMBS = os.path.join(ROOT, "web", "assets", "thumbs")
|
|
DEPOT = os.environ.get("GOD3_DEPOT", "https://digalot.fyi/3god").rstrip("/")
|
|
PW = os.environ.get("GOD3_PW", "")
|
|
DRY = "--dry-run" in sys.argv
|
|
# assets intentionally excluded from publish (see AUDIT.md)
|
|
SKIP = {"procity_fit_cube_shelf_01.glb"}
|
|
|
|
|
|
def cookie():
|
|
tok = hmac.new(PW.encode(), b"3god-ok", hashlib.sha256).hexdigest()
|
|
return f"3g={tok}"
|
|
|
|
|
|
def post(path, body, ctype):
|
|
req = urllib.request.Request(DEPOT + path, data=body, method="POST",
|
|
headers={"Content-Type": ctype, "Cookie": cookie()})
|
|
with urllib.request.urlopen(req, timeout=120) as r:
|
|
return r.status, r.read().decode("utf-8", "replace")[:200]
|
|
|
|
|
|
def main():
|
|
glbs = sorted(g for g in glob.glob(NORM + "/*.glb") if os.path.basename(g) not in SKIP)
|
|
if not glbs:
|
|
print(f"no GLBs in {NORM} — run normalize.py first"); sys.exit(1)
|
|
print(f"depot: {DEPOT} assets: {len(glbs)} {'(DRY RUN)' if DRY else ''}")
|
|
if not DRY and not PW:
|
|
print("ERROR: GOD3_PW not set in environment. Ask John for the depot password, then:\n"
|
|
" export GOD3_PW=... && python3 pipeline/publish.py")
|
|
sys.exit(2)
|
|
published = []
|
|
for g in glbs:
|
|
name = os.path.basename(g)
|
|
thumb = os.path.join(THUMBS, name.replace(".glb", ".png"))
|
|
size = os.path.getsize(g)
|
|
if DRY:
|
|
print(f" would upload {name:38} {size//1024:>5}KB thumb={os.path.exists(thumb)}")
|
|
continue
|
|
try:
|
|
st, msg = post("/api/upload?name=" + urllib.parse.quote(name),
|
|
open(g, "rb").read(), "model/gltf-binary")
|
|
tstat = ""
|
|
if os.path.exists(thumb):
|
|
ts, _ = post("/api/thumb?name=" + urllib.parse.quote(name.replace(".glb", ".png")),
|
|
open(thumb, "rb").read(), "image/png")
|
|
tstat = f" thumb:{ts}"
|
|
ok = st in (200, 201)
|
|
print(f" {'OK ' if ok else 'ERR'} {name:38} glb:{st}{tstat}")
|
|
if ok:
|
|
published.append(name)
|
|
except Exception as e:
|
|
print(f" ERR {name:38} {e}")
|
|
if not DRY:
|
|
json.dump(published, open(os.path.join(ROOT, "pipeline", "_published.json"), "w"), indent=2)
|
|
print(f"\npublished {len(published)}/{len(glbs)} → depot. verify: curl -s {DEPOT}/api/list")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|