saumwacht-godot/tools/build_phase4_reference_gallery.py
Martin Stickelbroeck c2b447610c Baseline: Saumwacht 0.15.0 (Phase 20A) vor dem Umbau zu Champions of Luneraria
Unveränderter Ausgangsstand inklusive Masterprompt und Konzeptbildern
unter assets/. .godot/ und docs/screenshots/ bleiben laut .gitignore
unversioniert; letztere sind unter ../backups gesichert.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 20:00:36 +02:00

89 lines
7.2 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python3
"""Build an offline comparison index and catalog from the current export manifest."""
from html import escape
import json
from pathlib import Path
PROJECT = Path(__file__).resolve().parents[1]
OUT = PROJECT / 'docs/screenshots/phase4'
FACTIONS = {'glutpakt': 'Glutpakt', 'nietenkonvent': 'Nietenkonvent',
'nebelchor': 'Nebelchor', 'wurzelbund': 'Wurzelbund', 'neutral': 'Söldner & Arbeiter'}
ROLES = {'melee': 'Nahkampf', 'tank': 'Tank', 'ranged': 'Fernkampf',
'artillery': 'Artillerie', 'support': 'Unterstützung', 'mercenary': 'Söldner',
'worker': 'Arbeiter', 'lane': 'Lane-Segment', 'enemy': 'Wellengegner', 'core': 'Saumkern'}
def main():
assets = json.loads((PROJECT / 'assets/phase4_manifest.json').read_text())['assets']
cards = []
for faction, label in FACTIONS.items():
selected = [a for a in assets if a['faction'] == faction]
for start in range(0, len(selected), 2):
pair = selected[start:start + 2]
filename = 'reference-' + pair[0]['id'] + '.png'
assert (OUT / filename).is_file(), filename
title = ' / '.join(a['id'].upper() + ' · ' + a['name'] for a in pair)
downloads = ' · '.join(
f'{a["id"].upper()}: <a href="../../../assets/models/{a["file"]}" download>GLB</a> / '
f'<a href="../../../assets-source/phase4/blend/{a["id"]}.blend" download>Blender</a>' for a in pair)
cards.append(f'<article data-faction="{faction}"><header><span>{escape(label)}</span>'
f'<h2>{escape(title)}</h2></header><a href="{filename}" aria-label="{escape(title)} – Bild in voller Größe">'
f'<img src="{filename}" width="1600" height="960" loading="lazy" '
f'alt="{escape(title)}: jeweils Originalicon links und aktuelles 3D-Modell rechts"></a>'
f'<footer>{downloads}</footer></article>')
buttons = '<button type="button" data-filter="all" aria-pressed="true">Alle 46 Figuren</button>'
buttons += ''.join(f'<button type="button" data-filter="{key}" aria-pressed="false">{escape(value)}</button>'
for key, value in FACTIONS.items())
html = '''<!doctype html><html lang="de"><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
<title>Saumwacht · Bild und Modell</title><style>
:root{color-scheme:dark;font-family:system-ui,sans-serif;background:#10141b;color:#eee6d8}
*{box-sizing:border-box}body{margin:0}main{max-width:1440px;margin:auto;padding:48px 24px}
.eyebrow,article header span{font-size:.8rem;letter-spacing:.12em;color:#d7c392;text-transform:uppercase}
h1{font-size:clamp(2rem,5vw,3.7rem);line-height:1.05;margin:18px 0}p{color:#b6c0cf;max-width:850px;line-height:1.65}
nav{display:flex;gap:8px;flex-wrap:wrap;margin:28px 0 16px}button{font:inherit;cursor:pointer;border:1px solid #455063;
background:#1b222d;color:#dbe1eb;border-radius:6px;padding:11px 15px}button[aria-pressed="true"]{background:#ddc898;color:#171b23;border-color:#ddc898}
button:hover{border-color:#ddc898}button:focus-visible,a:focus-visible{outline:3px solid #82dbce;outline-offset:4px}
#count{font-size:.85rem;color:#9eacc0;margin-bottom:30px}article{margin:0 0 36px;border:1px solid #303a49;border-radius:10px;overflow:hidden;background:#151c26}
article header{padding:20px 24px}h2{font-size:1.1rem;font-weight:550;margin:7px 0 0}img{display:block;width:100%;height:auto}
article footer{padding:15px 24px;font-size:.85rem;color:#acb9cd}a{color:#ddc898;text-underline-offset:3px}
[hidden]{display:none!important}.foot{border-top:1px solid #303a49;padding-top:22px}
@media(max-width:600px){main{padding:28px 12px}article header,article footer{padding:14px}button{padding:10px;font-size:.85rem}}
</style><main><div class="eyebrow">Saumwacht / Grafiküberarbeitung / 16. September 2026</div>
<h1>Bild und Modell.<br>Direkt nebeneinander.</h1>
<p>Die zwanzig Glutpakt- und Wurzelbund-Figuren wurden mit hochdetaillierter Geometrie neu aufgebaut; Nietenkonvent und Nebelchor behalten ihren bisherigen Detailgrad. Jeder Vergleich zeigt die unveränderte Illustration neben dem aktuellen Modell aus Godot. Klicke auf ein Bild, um es in voller Größe zu öffnen.</p>
<p>Die neuen Glutpakt-Modelle besitzen modellierte Gelenke, Panzerplatten, Metallkanten, Finger und Feuerkammern. Die Illustrationen dienen als Stilvorlage; die Figuren sind eigenständige 3D-Umsetzungen. Der Wurzelbund ergänzt geflochtene Wurzeln, Borkenplatten, Blattadern, Pilzkelche und Heilbecken.</p>
<nav aria-label="Fraktion auswählen">''' + buttons + '''</nav><div id="count" role="status">23 Vergleichsbilder · 46 Figuren</div>
''' + '\n'.join(cards) + '''
<p class="foot">Die Vorschauen passen die Figuren auf eine gemeinsame Bühne ein; sie vergleichen keine Weltmaßstäbe. Lane, Teamkerne und die zehn Wellengegner wurden ebenfalls überarbeitet.
<a href="models-rift.png">Wellengegner ansehen</a> · <a href="models-environment.png">Umgebung ansehen</a> · <a href="models-workshop.png">3D-Werkstatt ansehen</a></p></main>
<script>
const cards=[...document.querySelectorAll('article')];
document.querySelectorAll('button[data-filter]').forEach(button=>button.addEventListener('click',()=>{
document.querySelectorAll('button[data-filter]').forEach(b=>b.setAttribute('aria-pressed',String(b===button)));
cards.forEach(card=>card.hidden=button.dataset.filter!=='all'&&card.dataset.faction!==button.dataset.filter);
const count=cards.filter(card=>!card.hidden).length;
document.getElementById('count').textContent=`${count} Vergleichsbilder · ${count*2} Figuren`;
}));
</script></html>'''
(OUT / 'reference-gallery.html').write_text(html, encoding='utf-8')
catalog = ['# Saumwacht · Modellkatalog', '',
'Stand: 17. September 2026. Zwanzig Glutpakt- und Wurzelbund-Figuren, zehn Wellengegner, Arbeiter und Kristalle wurden neu aufgebaut; Söldner und Lane erhielten zusätzliche Details. Die 59 Einträge stammen aus `assets/phase4_manifest.json`.', '',
'Maße in Metern: **B = X, H = Y, T = Z** in Godot, auf zwei Nachkommastellen gerundet. '
'GLBs sind über die IDs verlinkt; native Quellen liegen in `blend/<ID>.blend`. '
'[Anleitung](README.md) · [Bildvergleiche](../../docs/screenshots/phase4/reference-gallery.html)', '',
'| ID / GLB | Modell | Fraktion | Rolle | Upgrade | Dreiecke | B (m) | H (m) | T (m) |',
'| --- | --- | --- | --- | --- | ---: | ---: | ---: | ---: |']
for a in assets:
b = a['bounds_godot']
dims = [hi-lo for hi, lo in zip(b['max'], b['min'])]
faction = FACTIONS.get(a['faction'], 'Risskreaturen' if a['faction']=='rift' else 'Umgebung')
upgrade = 'Ja' if a.get('upgraded', a.get('upgrade', False)) else 'Nein'
catalog.append(f'| [{a["id"]}](../../assets/models/{a["file"]}) | {a["name"]} | {faction} | '
f'{ROLES[a["role"]]} | {upgrade} | {a["triangles"]} | ' + ' | '.join(f'{d:.2f}' for d in dims) + ' |')
(PROJECT / 'assets-source/phase4/catalog.md').write_text('\n'.join(catalog)+'\n', encoding='utf-8')
print('REFERENCE_GALLERY_PASS: 23 comparison sheets, 46 characters; catalog 59 models')
if __name__ == '__main__':
main()