Files
2026-06-09 18:31:59 +02:00

318 lines
13 KiB
Python

#!/usr/bin/env python3
"""Select and regenerate hero images — Carbon Fiber Support CMS.
- Click a thumbnail to select it (gold border = selected)
- Click Regen under any thumbnail to regenerate just that variant
- Save → writes selections.json for Webflow upload
Usage:
python3 select_hero_images.py
"""
import json
import os
import subprocess
import threading
import tkinter as tk
from pathlib import Path
from PIL import Image, ImageTk
SOURCED_DIR = Path("/home/sirdrez/Downloads/Carbon Fiber Support_PDF/image-rendering/Application_Problems/grounded/sourced")
GENERATOR = Path("/home/sirdrez/Downloads/Carbon Fiber Support_PDF/generate_sourced_photoreal.py")
ENV_FILE = Path("/home/sirdrez/Downloads/Carbon Fiber Support_PDF/.env")
SELECTIONS_OUT = Path(__file__).parent / "selections.json"
APPLICATIONS = [
("bowing-basement-wall-repair", "Bowing Basement Wall Repair"),
("horizontal-basement-wall-cracks", "Horizontal Basement Wall Cracks"),
("parking-garage-column-wrapping", "Parking Garage Column Wrapping"),
("bridge-girder-strengthening", "Bridge Girder Strengthening"),
("stair-step-foundation-cracks", "Stair-Step Foundation Cracks"),
("parking-garage-deck-repair", "Parking Garage Deck Repair"),
("vertical-foundation-cracks", "Vertical Foundation Cracks"),
("poured-concrete-wall-repair", "Poured Concrete Wall Repair"),
("interior-block-wall-bulging", "Interior Block Wall Bulging"),
("foundation-wall-repair", "Foundation Wall Repair"),
("crawlspace-wall-reinforcement", "Crawlspace Wall Reinforcement"),
("cracked-concrete-slab-repair", "Cracked Concrete Slab Repair"),
("corner-crack-repair", "Corner Crack Repair"),
("concrete-block-wall-repair", "Concrete Block Wall Repair"),
("residential-retaining-wall-repair", "Residential Retaining Wall Repair"),
("commercial-retaining-wall-repair", "Commercial Retaining Wall Repair"),
("commercial-building-column-reinforcement", "Commercial Building Column Reinforcement"),
("warehouse-roof-truss-repair", "Warehouse Roof Truss Repair"),
("warehouse-beam-strengthening", "Warehouse Beam Strengthening"),
("parking-garage-beam-strengthening", "Parking Garage Beam Strengthening"),
("fire-and-impact-damage-beam-repair", "Fire and Impact Damage Beam Repair"),
("concrete-foundation-beam-repair", "Concrete Foundation Beam Repair"),
("bridge-column-and-pier-repair", "Bridge Column and Pier Repair"),
]
VARIANTS = ["v1", "v2", "v3", "v4"]
V_LABELS = ["v1 24mm", "v2 macro", "v3 3/4", "v4 alt"]
THUMB_W, THUMB_H = 190, 143
BG = "#0e0e0e"
ROW_EVEN = "#141414"
ROW_ODD = "#111111"
SEL_CLR = "#ffc107"
DIM_CLR = "#555555"
REGEN_BG = "#2a1a00"
REGEN_FG = "#ff9800"
BUSY_CLR = "#ff5722"
OK_CLR = "#4caf50"
TEXT_CLR = "#ffffff"
PLACEHOLDER = None # lazy-loaded gray image
def _load_env() -> dict:
env = os.environ.copy()
if ENV_FILE.exists():
for line in ENV_FILE.read_text().splitlines():
line = line.strip()
if line and not line.startswith("#") and "=" in line:
k, v = line.split("=", 1)
env[k.strip()] = v.strip()
return env
def _gray_placeholder(w: int, h: int) -> Image.Image:
img = Image.new("RGB", (w, h), "#1a1a1a")
return img
class App:
def __init__(self, root: tk.Tk):
self.root = root
self.root.title("CFS Image Selector")
self.root.configure(bg=BG)
self.root.geometry("1030x840")
self.selections: dict[str, int] = {s: 0 for s, _ in APPLICATIONS}
self._tk_imgs: dict = {}
self._img_lbls: dict = {} # (slug, v_idx) -> Label (image widget)
self._bdr_lbls: dict = {} # (slug, v_idx) -> same Label for border
self._txt_lbls: dict = {} # (slug, v_idx) -> variant text Label
self._regen_btns: dict = {} # (slug, v_idx) -> regen Button
self._busy: set = set() # (slug, v_idx) currently regenerating
self._build()
self._load_selections()
# ------------------------------------------------------------------ build
def _build(self):
bar = tk.Frame(self.root, bg=BG, pady=10)
bar.pack(fill="x", padx=20)
tk.Label(bar, text="Carbon Fiber Support — Select Hero Image",
bg=BG, fg=TEXT_CLR, font=("Helvetica", 12, "bold")).pack(side="left")
tk.Button(bar, text="Save Selections", command=self._save,
bg="#1a3a1a", fg=OK_CLR, relief="flat", padx=14, pady=6,
font=("Helvetica", 11, "bold"), cursor="hand2",
activebackground="#1f4a1f").pack(side="right", padx=(6, 0))
tk.Button(bar, text="All v1", command=lambda: self._select_all(0),
bg="#1a2a3a", fg="#64b5f6", relief="flat", padx=10, pady=6,
font=("Helvetica", 10), cursor="hand2").pack(side="right")
self._status = tk.StringVar(value="v1 pre-selected for all — click to change — Regen to regenerate")
tk.Label(self.root, textvariable=self._status, bg=BG, fg=DIM_CLR,
font=("Helvetica", 10), anchor="w").pack(fill="x", padx=20, pady=(0, 4))
outer = tk.Frame(self.root, bg=BG)
outer.pack(fill="both", expand=True, padx=8, pady=(0, 8))
canvas = tk.Canvas(outer, bg=BG, highlightthickness=0)
vsb = tk.Scrollbar(outer, orient="vertical", command=canvas.yview)
canvas.configure(yscrollcommand=vsb.set)
vsb.pack(side="right", fill="y")
canvas.pack(side="left", fill="both", expand=True)
self._inner = tk.Frame(canvas, bg=BG)
win_id = canvas.create_window((0, 0), window=self._inner, anchor="nw")
canvas.bind("<Configure>", lambda e: canvas.itemconfig(win_id, width=e.width))
self._inner.bind("<Configure>",
lambda _: canvas.configure(scrollregion=canvas.bbox("all")))
canvas.bind_all("<Button-4>", lambda _: canvas.yview_scroll(-1, "units"))
canvas.bind_all("<Button-5>", lambda _: canvas.yview_scroll(1, "units"))
canvas.bind_all("<MouseWheel>",
lambda e: canvas.yview_scroll(int(-1 * e.delta / 120), "units"))
for idx, (slug, name) in enumerate(APPLICATIONS):
self._build_row(idx, slug, name)
def _build_row(self, idx: int, slug: str, name: str):
bg = ROW_EVEN if idx % 2 == 0 else ROW_ODD
row = tk.Frame(self._inner, bg=bg, pady=8, padx=12)
row.pack(fill="x", pady=1)
tk.Label(row, text=f"{idx+1:02d} {name}", bg=bg, fg=TEXT_CLR,
font=("Helvetica", 10, "bold"), anchor="w", width=30).pack(side="left", padx=(0, 10))
for v_idx, (vk, vl) in enumerate(zip(VARIANTS, V_LABELS)):
path = SOURCED_DIR / f"{slug}_{vk}.jpg"
cell = tk.Frame(row, bg=bg)
cell.pack(side="left", padx=3)
# Image label
tk_img = self._make_tk_img(path)
self._tk_imgs[(slug, v_idx)] = tk_img
is_sel = (v_idx == 0)
border = SEL_CLR if is_sel else "#2a2a2a"
lbl = tk.Label(cell, image=tk_img,
highlightthickness=4, highlightbackground=border,
cursor="hand2", bg="#000")
lbl.pack()
self._img_lbls[(slug, v_idx)] = lbl
self._bdr_lbls[(slug, v_idx)] = lbl
lbl.bind("<Button-1>", lambda e, s=slug, vi=v_idx: self._select(s, vi))
lbl.bind("<Enter>", lambda e, s=slug, vi=v_idx: self._hover(s, vi, True))
lbl.bind("<Leave>", lambda e, s=slug, vi=v_idx: self._hover(s, vi, False))
# Variant label row: text + regen button side by side
foot = tk.Frame(cell, bg=bg)
foot.pack(fill="x")
txt = tk.Label(foot, text=vl, bg=bg,
fg=SEL_CLR if is_sel else DIM_CLR,
font=("Helvetica", 9), anchor="w")
txt.pack(side="left")
self._txt_lbls[(slug, v_idx)] = txt
rbtn = tk.Button(foot, text="Regen",
command=lambda s=slug, vi=v_idx: self._regen(s, vi),
bg=REGEN_BG, fg=REGEN_FG, relief="flat",
font=("Helvetica", 8), padx=5, pady=1,
cursor="hand2", activebackground="#3a2500")
rbtn.pack(side="right")
self._regen_btns[(slug, v_idx)] = rbtn
# ------------------------------------------------------------------ logic
def _make_tk_img(self, path: Path) -> ImageTk.PhotoImage:
if path.exists():
try:
img = Image.open(path)
img.thumbnail((THUMB_W, THUMB_H), Image.LANCZOS)
return ImageTk.PhotoImage(img)
except Exception:
pass
return ImageTk.PhotoImage(_gray_placeholder(THUMB_W, THUMB_H))
def _select(self, slug: str, v_idx: int):
old = self.selections[slug]
if old == v_idx:
return
lbl_old = self._bdr_lbls.get((slug, old))
if lbl_old:
lbl_old.configure(highlightbackground="#2a2a2a")
txt_old = self._txt_lbls.get((slug, old))
if txt_old:
txt_old.configure(fg=DIM_CLR)
lbl_new = self._bdr_lbls.get((slug, v_idx))
if lbl_new:
lbl_new.configure(highlightbackground=SEL_CLR)
txt_new = self._txt_lbls.get((slug, v_idx))
if txt_new:
txt_new.configure(fg=SEL_CLR)
self.selections[slug] = v_idx
self._status.set(f"Selected {slug}{VARIANTS[v_idx]}")
def _hover(self, slug: str, v_idx: int, entering: bool):
if self.selections[slug] == v_idx or (slug, v_idx) in self._busy:
return
lbl = self._bdr_lbls.get((slug, v_idx))
if lbl:
lbl.configure(highlightbackground="#555" if entering else "#2a2a2a")
def _select_all(self, v_idx: int):
for slug, _ in APPLICATIONS:
self._select(slug, v_idx)
self._status.set(f"All applications set to {VARIANTS[v_idx]}")
# ------------------------------------------------------------------ regen
def _regen(self, slug: str, v_idx: int):
key = (slug, v_idx)
if key in self._busy:
return
self._busy.add(key)
# Delete the file so the generator recreates it
path = SOURCED_DIR / f"{slug}_{VARIANTS[v_idx]}.jpg"
if path.exists():
path.unlink()
# Visual: busy state
lbl = self._bdr_lbls.get(key)
if lbl:
lbl.configure(highlightbackground=BUSY_CLR)
btn = self._regen_btns.get(key)
if btn:
btn.configure(text="...", state="disabled")
txt = self._txt_lbls.get(key)
if txt:
txt.configure(fg=BUSY_CLR)
self._status.set(f"Regenerating {slug} {VARIANTS[v_idx]} ...")
def run():
env = _load_env()
env["SLUG_FILTER_VARIANT"] = VARIANTS[v_idx]
subprocess.run(
["python3", str(GENERATOR), slug],
env=env, capture_output=True
)
self.root.after(0, lambda: self._regen_done(slug, v_idx))
threading.Thread(target=run, daemon=True).start()
def _regen_done(self, slug: str, v_idx: int):
key = (slug, v_idx)
self._busy.discard(key)
path = SOURCED_DIR / f"{slug}_{VARIANTS[v_idx]}.jpg"
tk_img = self._make_tk_img(path)
self._tk_imgs[key] = tk_img
lbl = self._img_lbls.get(key)
if lbl:
lbl.configure(image=tk_img)
is_sel = (self.selections[slug] == v_idx)
border = SEL_CLR if is_sel else "#2a2a2a"
bdr = self._bdr_lbls.get(key)
if bdr:
bdr.configure(highlightbackground=border)
txt = self._txt_lbls.get(key)
if txt:
txt.configure(fg=SEL_CLR if is_sel else DIM_CLR)
btn = self._regen_btns.get(key)
if btn:
btn.configure(text="Regen", state="normal")
self._status.set(f"Done {slug} {VARIANTS[v_idx]}")
# ------------------------------------------------------------------ save
def _save(self):
out = {slug: VARIANTS[vi] for slug, vi in self.selections.items()}
SELECTIONS_OUT.write_text(json.dumps(out, indent=2))
self._status.set(f"Saved {len(out)} selections → {SELECTIONS_OUT.name}")
def _load_selections(self):
if SELECTIONS_OUT.exists():
try:
data = json.loads(SELECTIONS_OUT.read_text())
for slug, vk in data.items():
if vk in VARIANTS:
self._select(slug, VARIANTS.index(vk))
self._status.set(f"Loaded {SELECTIONS_OUT.name}")
except Exception:
pass
if __name__ == "__main__":
root = tk.Tk()
App(root)
root.mainloop()