recent updates
This commit is contained in:
@@ -0,0 +1,317 @@
|
||||
#!/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()
|
||||
Reference in New Issue
Block a user