ShitboxInfinity/tools/flux_texture.sh
m3ultra 1db71420a3 Scenery v4: traffic signals, power lines, local FLUX fallback
Taps the last big unused seam in the OSM data: 140 highway=traffic_signals per
CBD level become pole + mast arm + two lantern heads with emissive lenses.
Timber power poles with catenary-sagging wires run down one side of every
residential street. Billboards now cycle three fictional ad designs.

Cloudflare's free tier (10k neurons/day) ran out mid-session, so flux_texture.sh
now falls back to the local MODELBEAST farm automatically -- transparent, same
output contract. tools/crop_poster.py trims generated billboards to the poster
face by saturation, since FLUX renders them in situ with sky and support pole
however the prompt is worded.

Fixed: signal lenses were built as 0.04 m beams and beam() bails under 0.05 m,
so they silently never existed -- caught by inspecting the exported GLB, not
the screenshot.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-28 17:21:35 +10:00

58 lines
2.6 KiB
Bash
Executable File

#!/bin/bash
# Generate a texture via Cloudflare Workers AI FLUX (flux-1-schnell).
# Usage: flux_texture.sh "<prompt>" <out.jpg> [size_px] [target_luma]
#
# target_luma (0-255) scales the result down to that mean brightness. FLUX
# returns a near-white sheet for anything described as pale -- "off white metal
# panel" came back at 240/255 however it was worded -- and a blown-out albedo
# renders as a featureless void in game. Only ever darkens.
# Credentials: sourced from known .env files; accepts CLOUDFLARE_API_TOKEN/CF_API_TOKEN
# + CLOUDFLARE_ACCOUNT_ID/CF_ACCOUNT_ID. Values never printed.
set -euo pipefail
for f in ~/Documents/fluxgod-work/.env ~/Documents/backnforth/.env ~/Documents/wardrobegod/.env; do
[ -f "$f" ] && set -a && source "$f" 2>/dev/null && set +a
done
TOKEN="${CLOUDFLARE_API_TOKEN:-${CF_API_TOKEN:-${CLOUDFLARE_TOKEN:-}}}"
ACCT="${CLOUDFLARE_ACCOUNT_ID:-${CF_ACCOUNT_ID:-${CLOUDFLARE_ACCOUNT:-}}}"
if [ -z "$TOKEN" ] || [ -z "$ACCT" ]; then
echo "MISSING_CREDS token_present=$([ -n "$TOKEN" ] && echo yes || echo no) acct_present=$([ -n "$ACCT" ] && echo yes || echo no)"
exit 1
fi
PROMPT="$1"
OUT="$2"
SIZE="${3:-512}"
LUMA="${4:-0}"
RESP=$(mktemp)
HTTP=$(curl -s -w '%{http_code}' -o "$RESP" --max-time 120 \
"https://api.cloudflare.com/client/v4/accounts/$ACCT/ai/run/@cf/black-forest-labs/flux-1-schnell" \
-H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
-d "$(python3 -c 'import json,sys; print(json.dumps({"prompt": sys.argv[1], "steps": 6}))' "$PROMPT")")
if [ "$HTTP" != "200" ]; then
ERR="$(python3 -c 'import json,sys; d=json.load(open(sys.argv[1])); print(d.get("errors"))' "$RESP" 2>/dev/null | head -c 200)"
rm -f "$RESP"
# Cloudflare's free tier is 10,000 neurons/day and a full set regen eats it.
# Fall back to the local MODELBEAST farm rather than failing the texture.
echo "cloudflare HTTP $HTTP ($ERR) -- falling back to local FLUX" >&2
exec python3 "$(dirname "$0")/flux_local.py" "$PROMPT" "$OUT" "$SIZE" "$LUMA"
fi
python3 - "$RESP" "$OUT" <<'EOF'
import base64, json, sys
d = json.load(open(sys.argv[1]))
open(sys.argv[2] + ".png", "wb").write(base64.b64decode(d["result"]["image"]))
EOF
sips -Z "$SIZE" -s format jpeg -s formatOptions 80 "$OUT.png" --out "$OUT" > /dev/null 2>&1
rm -f "$RESP" "$OUT.png"
if [ "$LUMA" != "0" ]; then
python3 - "$OUT" "$LUMA" <<'EOF'
import sys
from PIL import Image, ImageStat
im = Image.open(sys.argv[1]).convert("RGB")
mean = ImageStat.Stat(im.convert("L")).mean[0]
target = float(sys.argv[2])
if mean > target:
im = im.point(lambda v, k=target / mean: int(v * k))
im.save(sys.argv[1], quality=88)
EOF
fi
echo "OK $OUT ($(du -h "$OUT" | cut -f1))"