#!/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("", lambda e: canvas.itemconfig(win_id, width=e.width)) self._inner.bind("", lambda _: canvas.configure(scrollregion=canvas.bbox("all"))) canvas.bind_all("", lambda _: canvas.yview_scroll(-1, "units")) canvas.bind_all("", lambda _: canvas.yview_scroll(1, "units")) canvas.bind_all("", 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("", lambda e, s=slug, vi=v_idx: self._select(s, vi)) lbl.bind("", lambda e, s=slug, vi=v_idx: self._hover(s, vi, True)) lbl.bind("", 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()