#!/usr/bin/env python3 """ NexusOS folder icon builder. Emits one SVG per variant into places/, then rasterizes to ~/.icons/NexusOS//places/.png at 16/22/24/32/48/64/128. Run: python3 build.py # builds SVGs + renders all PNGs python3 build.py --svg-only # just rewrite SVGs """ from pathlib import Path import base64 import shutil import subprocess import sys ROOT = Path(__file__).resolve().parent PLACES = ROOT / "places" ICONS_OUT = Path.home() / ".icons" / "NexusOS" SIZES = [16, 22, 24, 32, 48, 64, 128] # N logo source. Inkscape's headless renderer doesn't follow external href # references, so we inline it as a base64 data URI. LOGO_PATH = Path.home() / "nexus-core" / "assets" / "n-small.png" LOGO_DATA_URI = ( "data:image/png;base64," + base64.b64encode(LOGO_PATH.read_bytes()).decode("ascii") ) # ── master folder geometry ──────────────────────────────────────────── # viewBox 0 0 128 128. Badge area: roughly (32,52)-(96,108). MASTER = """\ """ # Open-folder geometry used by folder-open and folder-drag-accept. # The front face is angled forward, exposing the back as a triangle on top. OPEN_MASTER = """\ """ # ── badge fragments ─────────────────────────────────────────────────── # All badges drawn in white (#f2f2f2) and centered around (64, 78). # Keep glyphs in roughly a 40-50px box for legibility at 22-32 sizes. BADGES = { "folder": "", # the base, no badge "folder-documents": """\ """, "folder-download": """\ """, "folder-music": """\ """, "folder-pictures": """\ """, "folder-videos": """\ """, "folder-templates": """\ """, "folder-publicshare": """\ """, "folder-home": """\ """, "folder-recent": """\ """, "folder-remote": """\ """, "folder-saved-search": """\ """, "folder-drag-accept": "USE_OPEN_MASTER", # uses OPEN_MASTER, no extra badge "folder-open": "USE_OPEN_MASTER", "folder-nexus-core": f"""\ """, } def svg_for(name: str, badge: str) -> str: if badge == "USE_OPEN_MASTER": body = OPEN_MASTER extra = "" else: body = MASTER extra = badge return f""" {body}{extra} """ def write_svgs() -> list[str]: PLACES.mkdir(parents=True, exist_ok=True) written = [] for name, badge in BADGES.items(): path = PLACES / f"{name}.svg" path.write_text(svg_for(name, badge)) written.append(name) return written def render_pngs(names: list[str]) -> None: for name in names: svg = PLACES / f"{name}.svg" for size in SIZES: out_dir = ICONS_OUT / f"{size}x{size}" / "places" out_dir.mkdir(parents=True, exist_ok=True) out = out_dir / f"{name}.png" subprocess.run( [ "inkscape", str(svg), "--export-type=png", f"--export-filename={out}", f"--export-width={size}", f"--export-height={size}", ], check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, ) print(f" rendered {name} @ {size}") def backup_existing_nexus_core() -> None: """Move the existing chunky 3D folder-nexus-core PNGs aside before overwriting.""" backup = ROOT / "_backup" / "folder-nexus-core-original" backup.mkdir(parents=True, exist_ok=True) for size in SIZES: src = ICONS_OUT / f"{size}x{size}" / "places" / "folder-nexus-core.png" if src.exists(): shutil.copy2(src, backup / f"{size}.png") print(f" backed up original folder-nexus-core to {backup}") def main() -> None: svg_only = "--svg-only" in sys.argv names = write_svgs() print(f"Wrote {len(names)} SVGs into {PLACES}") if svg_only: return backup_existing_nexus_core() render_pngs(names) # Refresh the GTK icon cache so file managers pick up the new icons. subprocess.run( ["gtk-update-icon-cache", "-f", str(ICONS_OUT)], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, ) print(f"\nDone. Set theme to NexusOS in your DE to see the icons.") if __name__ == "__main__": main()