Code4Scene / scripts /simworld_logo.py
KoeYe's picture
Claude Opus 5.5
SimWorld emblem as the site logo; Text-to-Scene charts titled Text-to-Scene, not Research Track
27aa296 verified
Raw History Blame Contribute Delete
4.07 kB
#!/usr/bin/env python3
"""The SimWorld emblem as the site's logo (user, 2026-10-17: "网页的logo换成SimWorld的logo").
- assets/simworld-mark.png: the emblem the user supplied (2000 px, transparent), cropped to its alpha bounds, centred on
a square, scaled to 192 px and stored with a 256-colour palette (10 kB; no visible difference at this size).
- Every page header: the emblem replaces the wireframe cube in front of "Code4Scene"; the name and "by SimWorld" stay.
- Every page's favicon, plus an apple-touch icon: the emblem instead of assets/code4scene-mark.svg (kept in the repo).
- assets/buildingbench.css: the mark's size, 30 px (26 px on phones), where the cube was 26 x 29 px.
Pages are edited as text, line for line, so nothing else in them changes (paper.html is hand-written for Scholar).
scripts/t2s_gallery.py writes the same header and icons into the gallery pages it builds. Re-runnable.
python3 scripts/simworld_logo.py [--src /path/to/simworld-emblem.png]
"""
from __future__ import annotations
import argparse
import re
from pathlib import Path
from PIL import Image
ROOT = Path(__file__).resolve().parent.parent
MARK = "assets/simworld-mark.png"
V = "20261017logo"
CUBE = re.compile(r'<svg aria-hidden="true" fill="none" viewbox="0 0 30 32">.*?</svg>', re.S)
OLD_ICON = re.compile(r'<link[^>]*assets/code4scene-mark\.svg[^>]*>')
CSS_START, CSS_END = "/* SimWorld emblem in the header (scripts/simworld_logo.py) */", "/* end SimWorld emblem */"
CSS = """
.brand-home .brand-mark{display:block;flex:none;width:30px;height:30px}
@media(max-width:650px){.brand-home .brand-mark{width:26px;height:26px}}
"""
def mark(src: Path) -> None:
im = Image.open(src).convert("RGBA")
im = im.crop(im.getbbox())
side = max(im.size)
sq = Image.new("RGBA", (side, side), (0, 0, 0, 0))
sq.alpha_composite(im, ((side - im.width) // 2, (side - im.height) // 2))
sq = sq.resize((192, 192), Image.LANCZOS)
sq.quantize(colors=256, method=Image.Quantize.FASTOCTREE, dither=Image.Dither.FLOYDSTEINBERG).save(ROOT / MARK, optimize=True)
def page(p: Path) -> bool:
s0 = s = p.read_text()
prefix = "../" * (len(p.relative_to(ROOT).parts) - 1)
src = f"{prefix}{MARK}?v={V}"
img = f'<img alt="" class="brand-mark" decoding="async" height="30" src="{src}" width="30"/>'
s = CUBE.sub(img, s, count=1)
s = re.sub(r'<img alt="" class="brand-mark"[^>]*/>', img, s, count=1) # a re-run: the current version
icons = (f'<link href="{src}" rel="icon" sizes="192x192" type="image/png"/>'
f'<link href="{src}" rel="apple-touch-icon"/>')
if OLD_ICON.search(s):
s = OLD_ICON.sub(icons, s, count=1)
else:
s = re.sub(r'<link href="[^"]*simworld-mark\.png[^"]*" rel="icon"[^>]*/><link href="[^"]*" rel="apple-touch-icon"/>',
icons, s, count=1)
s = re.sub(r'(assets/buildingbench\.css)\?v=[^"]*', rf"\1?v={V}", s)
if s != s0:
p.write_text(s)
return s != s0
def css() -> None:
p = ROOT / "assets/buildingbench.css"
s = p.read_text()
block = CSS_START + CSS + CSS_END
if CSS_START in s:
s = s[:s.index(CSS_START)] + block + s[s.index(CSS_END) + len(CSS_END):]
else:
s = s.rstrip("\n") + "\n" + block + "\n"
p.write_text(s)
def main() -> None:
ap = argparse.ArgumentParser(description=__doc__.split("\n")[0])
ap.add_argument("--src", type=Path, help="the emblem image (transparent); omit to keep assets/simworld-mark.png")
a = ap.parse_args()
if a.src:
mark(a.src)
assert (ROOT / MARK).exists(), "run once with --src"
pages = [ROOT / "index.html", ROOT / "cases.html", ROOT / "paper.html"] + sorted((ROOT / "gallery").rglob("*.html"))
n = sum(page(p) for p in pages if p.exists())
css()
left = [str(p.relative_to(ROOT)) for p in pages if "code4scene-mark.svg" in p.read_text() or "M15 2 27 9v14" in p.read_text()]
assert not left, left
print(f"{n} pages updated; mark {(ROOT / MARK).stat().st_size} bytes")
if __name__ == "__main__":
main()