scopedvr-map-editor/tools/sync_models.sh
scopedvr-operator ab173ac8aa T2 Ueberall installierbar (Operator-Push): Modelle aus mitgeliefertem models/-Ordner
Worker hatte die Arbeit fertig, aber NICHT committet/gepusht (3. Fall heute) —
aus dem Karten-Arbeitsordner gerettet und hier gepusht.

- GlbMeshStore.quellen(): Reihenfolge EINE Stelle — Setting -> Sibling-Repos ->
  mitgelieferter Ordner models/ direkt neben der App (dedupliziert, typisiert)
- Klartext-Meldung FEHLT_TEXT statt stillem Platzhalter; Notfall-Modus: Terrain
  und Bauteile laufen ohne GLBs
- neu: tools/sync_models.sh (fuellt models/, --check), utils/modelle_status.gd
  (Autoload ModelleStatus), tools/probe_gen_map.gd
- neu: README_INSTALL.md (85 Zeilen, Schritt fuer Schritt + §5 Lizenzhinweis:
  ITHappy-Rohdaten NICHT weitergeben, App fuer Slys eigene Rechner)
- export_presets.cfg (eigenstaendiger Linux-/Windows-Export), .gitignore:
  models/ + export/ bleiben draussen (Lizenz + Groesse)
- Tests: test_model_sources (26/27; die rote ist umgebungsabhaengig:
  'lager_ist_leer' erwartet fehlende Sibling-Repos)

Belegt: Import 0 Fehler, gui_smoke 92, ui_layout 71, playtest_spawn 56.
2026-10-20 09:43:05 +00:00

185 lines
6.7 KiB
Bash
Executable file

#!/usr/bin/env bash
# Modelle-Sync für den eigenständigen Map Builder (Karte t_12cf94fe).
#
# Was das Script macht:
# Füllt den Ordner models/ DIREKT NEBEN der App (im Editor-Repo = Projekt-
# ordner, in der exportierten App = neben der EXE) mit den echten 3D-Modellen
# (.glb) und den Pack-Texturen — kopiert aus einem Client-Klon. Danach läuft
# der Map Builder auf jedem beliebigen PC OHNE Client/Godot-Installation.
#
# Spiegelstruktur (wichtig: die App erwartet die GLBs DIREKT im models-Ordner):
# models/<oid>.glb (flache GLBs)
# models/military_desert/<kategorie>/<oid>.glb (verschachtelte GLBs)
# models/textures/*.png (Pack-Palette)
#
# Aufruf (aus dem Map-Editor-Repo oder aus der exportierten App):
# tools/sync_models.sh # models/ füllen (idempotent — gefahrlos wiederholbar)
# tools/sync_models.sh --check # nur prüfen: Exit 0 = models/ aktuell,
# # Exit 1 = es fehlt/wich ab (ohne Änderung)
#
# Client-Klon finden: Umgebungsvariable SCOPEDVR_CLIENT_DIR, sonst die üblichen
# Nachbar-Ordner ../scopedvr-client und ../../scopedvr-client.
#
# Abhängigkeiten: python3 (Standard auf Linux/Mac; auf Windows python.org oder
# Git-Bash). KEINE rsync-Abhängigkeit — das Spiegeln (Kopieren + Überflüssiges
# löschen) macht ein eingebautes Python-Script, damit es auf jedem PC ohne
# Zusatzinstallation läuft (Slys Wunsch: keinerlei Abhängigkeiten).
#
# Lizenz (WICHTIG, siehe README_INSTALL.md): die Modelle/Texturen stammen aus
# dem ITHappy-Pack und dürfen NICHT weitergegeben werden — der models/-Ordner
# bleibt auf Slys eigenen Rechnern. Deshalb steht models/ auch in .gitignore.
#
set -u
MODE="sync"
if [ "${1:-}" = "--check" ]; then
MODE="check"
fi
if [ "${1:-}" != "" ] && [ "${1:-}" != "--check" ]; then
echo "Unbekannter Parameter: $1 (erlaubt: --check oder nichts)"
exit 2
fi
DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
TARGET="$DIR/models"
CLIENT="${SCOPEDVR_CLIENT_DIR:-}"
# --- Client-Klon finden (wie tools/sync_objects.sh) -------------------------
if [ -z "$CLIENT" ]; then
for k in "$DIR/../scopedvr-client" "$DIR/../../scopedvr-client"; do
[ -d "$k/assets/objekte" ] && CLIENT="$(cd "$k" && pwd)" && break
done
fi
if [ -z "$CLIENT" ] || [ ! -d "$CLIENT/assets/objekte" ]; then
echo "FEHLER: Client-Klon mit assets/objekte nicht gefunden."
echo " Ordner ../scopedvr-client neben den Map-Editor legen oder"
echo " SCOPEDVR_CLIENT_DIR=/pfad/zum/client setzen."
exit 1
fi
echo "Client: $CLIENT"
echo "models/: $TARGET"
echo "Modus: $MODE"
if ! command -v python3 >/dev/null 2>&1; then
echo "FEHLER: python3 fehlt (Standard auf Linux/Mac; auf Windows python.org installieren)."
exit 1
fi
# --- .gdignore: Godot soll models/ NICHT importieren/packen ------------------
# (FileAccess mit absoluten Pfaden funktioniert trotzdem — genau dafür
# ist der Ordner da. Fehlt die Datei, ist das eine Abweichung.)
GDIGNORE="$TARGET/.gdignore"
mkdir -p "$TARGET"
GDIGNORE_MISSING=0
if [ ! -f "$GDIGNORE" ]; then
GDIGNORE_MISSING=1
if [ "$MODE" = "check" ]; then
echo "ABWEICHUNG: $GDIGNORE fehlt (Godot würde den Ordner sonst importieren)."
else
touch "$GDIGNORE"
echo "angelegt: $GDIGNORE"
fi
fi
# --- Spiegeln: Objekte (GLBs) + Pack-Texturen, ohne .import-Leichen ---------
# WICHTIG (Karte t_12cf94fe): die App erwartet die GLBs DIREKT im models-Ordner
# (models/<oid>.glb) — NICHT verschachtelt in models/assets/objekte.
# Deshalb wird $CLIENT/assets/objekte nach $TARGET (models/) gespiegelt.
SRC_OBJEKTE="$CLIENT/assets/objekte"
SRC_TEX="$CLIENT/assets/textures/military_desert"
DST_OBJEKTE="$TARGET"
DST_TEX="$TARGET/textures"
# Python macht das Spiegeln (copy2 + überflüssige Dateien löschen).
# --check (Dry-Run) zeigt Abweichungen OHNE etwas zu verändern.
python3 - "$SRC_OBJEKTE" "$DST_OBJEKTE" "$SRC_TEX" "$DST_TEX" "$MODE" <<'PY'
import os, sys, shutil, filecmp
pairs = [
("Objekte", sys.argv[1], sys.argv[2]),
("Texturen", sys.argv[3], sys.argv[4]),
]
mode = sys.argv[5]
def scan(root: str) -> dict:
out = {}
if not os.path.isdir(root):
return out
for r, dirs, files in os.walk(root):
dirs[:] = [d for d in dirs if d != ".godot"]
for f in files:
if f.endswith(".import") or f == ".gdignore":
continue
full = os.path.join(r, f)
out[os.path.relpath(full, root)] = full
return out
def run(label: str, src: str, dst: str) -> int:
if not os.path.isdir(src):
print("ABWEICHUNG: Quelle fehlt: %s" % src)
return 1
s_map = scan(src)
d_map = scan(dst)
changes = []
for rel, sp in s_map.items():
dp = os.path.join(dst, rel)
if not os.path.exists(dp):
changes.append(("neu", rel))
else:
try:
if not filecmp.cmp(sp, dp, shallow=False):
changes.append(("geändert", rel))
except OSError:
changes.append(("geändert", rel))
for rel in d_map:
if rel not in s_map:
changes.append(("überflüssig", rel))
if not changes:
return 0
if mode == "check":
for action, rel in changes[:20]:
print("ABWEICHUNG (%s): %s: %s" % (label, action, rel))
if len(changes) > 20:
print("ABWEICHUNG (%s): … und %d weitere" % (label, len(changes) - 20))
return 1
for action, rel in changes:
dp = os.path.join(dst, rel)
if action == "überflüssig":
if os.path.exists(dp):
os.remove(dp)
else:
os.makedirs(os.path.dirname(dp), exist_ok=True)
shutil.copy2(s_map[rel], dp)
print("%s: %d Änderungen übernommen." % (label, len(changes)))
return 0
dev = 0
dev |= run("Objekte", pairs[0][1], pairs[0][2])
dev |= run("Texturen", pairs[1][1], pairs[1][2])
sys.exit(dev)
PY
DEVIATION=$?
# --- Größe + Zahl am Ende ----------------------------------------------------
GLB_COUNT="$(find "$DST_OBJEKTE" -name '*.glb' 2>/dev/null | wc -l)"
TEX_COUNT="$(find "$DST_TEX" -name '*.png' 2>/dev/null | wc -l)"
SIZE="$(du -sh "$TARGET" 2>/dev/null | cut -f1)"
echo "---"
echo "models/: $GLB_COUNT GLBs, $TEX_COUNT Pack-Texturen, Gesamtgröße: $SIZE"
if [ "$MODE" = "check" ]; then
if [ "$DEVIATION" -ne 0 ] || [ "$GDIGNORE_MISSING" -ne 0 ]; then
echo "--check: models/ weicht vom Client-Klon ab (Exit 1)."
exit 1
fi
echo "--check: models/ ist aktuell (Exit 0)."
exit 0
fi
if [ "$DEVIATION" -ne 0 ]; then
echo "Sync mit Fehlern (siehe oben)."
exit 1
fi
echo "Sync fertig: models/ ist auf dem Stand des Client-Klons."