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.
This commit is contained in:
parent
0f86472cfa
commit
ab173ac8aa
12 changed files with 769 additions and 8 deletions
8
.gitignore
vendored
8
.gitignore
vendored
|
|
@ -2,5 +2,13 @@
|
|||
.godot/
|
||||
*.tmp
|
||||
|
||||
# Mitgelieferte Modelle (Karte t_12cf94fe): werden per tools/sync_models.sh
|
||||
# aus dem Client-Klon gefüllt — NICHT ins Repo (ITHappy-Lizenz erlaubt keine
|
||||
# Weitergabe, siehe README_INSTALL.md §5).
|
||||
models/
|
||||
|
||||
# Export-Builds der eigenständigen App
|
||||
export/
|
||||
|
||||
# System
|
||||
.DS_Store
|
||||
|
|
|
|||
85
README_INSTALL.md
Normal file
85
README_INSTALL.md
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
# ScopedVR Map Builder auf einem anderen PC installieren
|
||||
|
||||
Diese Anleitung erklärt Schritt für Schritt, wie du den Map Builder auf einem
|
||||
beliebigen PC zum Laufen bringst und dort eine Map testen kannst — **ohne**
|
||||
Godot-Installation, **ohne** ScopedVR-Client, **ohne** Programmierkenntnisse.
|
||||
|
||||
---
|
||||
|
||||
## 1. Voraussetzungen
|
||||
|
||||
- Ein normaler PC (Linux oder Windows). Kein Godot, keine Kommandozeile nötig,
|
||||
wenn die App einmal exportiert wurde.
|
||||
- Die exportierte App (Ein Ordner oder eine EXE, siehe Schritt 2).
|
||||
- Den Ordner `models/` mit den 3D-Modellen (siehe Schritt 3).
|
||||
|
||||
## 2. Die App bekommen
|
||||
|
||||
Die App ist eine eigenständige Datei (z. B. `MapEditor` unter Linux,
|
||||
`MapEditor.exe` unter Windows). Sie wurde aus dem Map-Editor-Repo exportiert
|
||||
(`export_presets.cfg`, Preset „Linux“). Auf dem Ziel-PC brauchst du sie nur
|
||||
in einen beliebigen Ordner zu kopieren, z. B.:
|
||||
|
||||
~/MapBuilder/ (Linux)
|
||||
C:\MapBuilder\ (Windows)
|
||||
|
||||
Doppelklick drauf — die App startet. Das war's. Keine Installation.
|
||||
|
||||
## 3. Die Modelle danebenlegen (WICHTIG)
|
||||
|
||||
Ohne Modelle zeigt die App nur einfache Platzhalter-Kisten. Damit die echten
|
||||
3D-Modelle erscheinen, muss der Ordner `models/` **direkt neben** der App
|
||||
liegen (genau so wie hier):
|
||||
|
||||
~/MapBuilder/MapEditor ← die App
|
||||
~/MapBuilder/models/ ← die Modelle
|
||||
|
||||
Wie der `models/`-Ordner gefüllt wird: Auf einem PC, auf dem der
|
||||
Client-Klon liegt, im Map-Editor-Repo laufen lassen:
|
||||
|
||||
tools/sync_models.sh
|
||||
|
||||
Das Script kopiert alle Objekte (GLB) und die benötigten Texturen in den
|
||||
`models/`-Ordner — idempotent, du kannst es also gefahrlos mehrmals laufen
|
||||
lassen. Mit `--check` prüft es nur (Exit 0 = alles aktuell, Exit 1 = es fehlt
|
||||
etwas). Die Größe wird am Ende angezeigt.
|
||||
|
||||
Falls die Modelle fehlen, sagt die App beim Start deutlich:
|
||||
> „Für die Anzeige fehlen die Modelle. So bekommst du sie: …“
|
||||
|
||||
Terrain und Bauteile (Wände, Dächer usw.) funktionieren **immer** — auch ganz
|
||||
ohne Modelle (Notfall-Modus).
|
||||
|
||||
## 4. Eine Map testen
|
||||
|
||||
1. App starten.
|
||||
2. Map öffnen: Datei → Map öffnen, dann die gewünschte `.svmap`-Datei wählen.
|
||||
3. Zum Testen: Taste **F5** drücken. Du läufst dann selbst durch die Map
|
||||
(WASD = gehen, Maus = umsehen, Shift = rennen, Leertaste = springen).
|
||||
4. **ESC** zurück in den Editor.
|
||||
|
||||
## 5. Lizenz-Hinweis (bitte wirklich lesen)
|
||||
|
||||
Die 3D-Modelle und Texturen im `models/`-Ordner stammen aus dem
|
||||
**ITHappy-Pack**. Die Lizenz dieses Packs erlaubt es **nicht**, die
|
||||
Roh-Modelle und Texturen weiterzugeben. Das heißt konkret:
|
||||
|
||||
- Die App mit `models/`-Ordner ist für **Slys eigene Rechner** gedacht.
|
||||
- Der Ordner **darf nicht** an andere Leute geschickt oder hochgeladen werden.
|
||||
- Die App selbst (ohne `models/`-Ordner) darf geteilt werden — sie zeigt dann
|
||||
eben nur Platzhalter, bis der Ordner dazukommt.
|
||||
|
||||
## 6. Wenn etwas nicht geht
|
||||
|
||||
| Problem | Lösung |
|
||||
| --- | --- |
|
||||
| Nur Kisten statt echter Modelle | `models/`-Ordner direkt neben die App legen (Schritt 3) |
|
||||
| Meldung „Modelle fehlen“ | `tools/sync_models.sh` ausführen (Schritt 3) |
|
||||
| App startet gar nicht | Auf Linux: Datei per Rechtsklick → „Ausführen“ erlauben, oder im Terminal `chmod +x MapEditor` |
|
||||
| Map lässt sich nicht öffnen | Ist es wirklich eine `.svmap`-Datei? |
|
||||
|
||||
---
|
||||
*Technischer Hintergrund: die Modelle werden zur Laufzeit per GLTFDocument
|
||||
aus dem `models/`-Ordner geladen (siehe `src/viewport/glb_mesh_store.gd`,
|
||||
Quellen-Reihenfolge: Einstellungen → Nachbar-Ordner `../scopedvr-client` →
|
||||
`models/`). Erster Treffer gewinnt.*
|
||||
29
export_presets.cfg
Normal file
29
export_presets.cfg
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
[preset.0]
|
||||
|
||||
name="Linux"
|
||||
platform="Linux"
|
||||
runnable=true
|
||||
advanced_options=false
|
||||
dedicated_server=false
|
||||
custom_features=""
|
||||
export_filter="all_resources"
|
||||
include_filter=""
|
||||
exclude_filter="models/*"
|
||||
export_path="export/MapEditor.x86_64"
|
||||
encryption_include_filters=""
|
||||
encryption_exclude_filters=""
|
||||
seed=0
|
||||
encrypt_pck=false
|
||||
encrypt_directory=false
|
||||
script_export_mode=2
|
||||
|
||||
[preset.0.options]
|
||||
|
||||
custom_template/debug=""
|
||||
custom_template/release=""
|
||||
debug/export_console_wrapper=0
|
||||
binary_format/embed_pck=false
|
||||
texture_format/s3tc_bptc=true
|
||||
texture_format/etc2_astc=false
|
||||
binary_format/architecture="x86_64"
|
||||
ssh_remote_deploy/enabled=false
|
||||
|
|
@ -11,6 +11,13 @@ run/main_scene="res://scenes/main.tscn"
|
|||
config/features=PackedStringArray("4.7")
|
||||
config/icon="res://icon.svg"
|
||||
|
||||
[autoload]
|
||||
|
||||
; Modell-Status + Probe (Karte t_12cf94fe): Klartext-Banner, wenn Modelle
|
||||
; fehlen, und --probe-map=/pfad für den Eigenständigkeits-Beweis der
|
||||
; exportierten App.
|
||||
ModelleStatus="*res://utils/modelle_status.gd"
|
||||
|
||||
[display]
|
||||
|
||||
window/size/viewport_width=1600
|
||||
|
|
|
|||
|
|
@ -24,6 +24,21 @@ class_name GlbMeshStore
|
|||
const QUELLEN_SETTING := "scopedvr/editor/glb_quellen"
|
||||
## Voreinstellung = dieselben Quellen wie tools/gen_thumbs.gd (Sibling-Repos).
|
||||
const DEFAULT_QUELLEN := ["../scopedvr-match-server/assets/objekte", "../scopedvr-client/assets/objekte"]
|
||||
## Mitgelieferter Modell-Ordner (Karte t_12cf94fe): direkt neben der App
|
||||
## (im Editor = Projektordner, in der exportierten App = neben der EXE).
|
||||
## Der sync über tools/sync_models.sh legt hier die GLBs + Pack-Texturen
|
||||
## ab — dann läuft der Map Builder auf einem fremden PC OHNE Sibling-Repos.
|
||||
## Der Ordner trägt eine .gdignore (kein Import/PCK-Aufnahme; FileAccess
|
||||
## mit absolutem Pfad funktioniert trotzdem).
|
||||
const MODELS_DIR := "models"
|
||||
## Klarer Hinweistext (Karte t_12cf94fe, Auftrag 3), wenn Modelle fehlen:
|
||||
## wird als Klartext-Meldung gezeigt — kein stiller Platzhalter.
|
||||
## Notfall-Modus: Terrain/Bauteile brauchen keine GLBs und funktionieren immer.
|
||||
const FEHLT_TEXT := "Für die Anzeige fehlen die Modelle. So bekommst du sie:\n" \
|
||||
+ " 1. tools/sync_models.sh aus dem Map-Editor-Ordner laufen lassen\n" \
|
||||
+ " (kopiert die Modelle in den Ordner %s),\n" \
|
||||
+ " 2. oder in den Einstellungen einen Ordner mit den Objekt-GLBs setzen.\n" \
|
||||
+ "Terrain und Bauteile funktionieren trotzdem vollständig (Notfall-Modus)."
|
||||
## Registry (A6): liefert das Feld "glb" für die Kategorie-verschachtelte Ablage.
|
||||
const MANIFEST_PATH := "res://library/objects.json"
|
||||
static var _manifest: Dictionary = {}
|
||||
|
|
@ -63,16 +78,60 @@ static var _camo_ersetzung_cache: Dictionary = {}
|
|||
static var _cache: Dictionary = {}
|
||||
|
||||
|
||||
## Kandidaten-Pfade der Quellen (ProjectSettings → Fallback-Konstante).
|
||||
## Kandidaten-Pfade der Quellen in Ladereihenfolge (Karte t_12cf94fe,
|
||||
## Auftrag 1 — EINE Stelle, erster Treffer gewinnt):
|
||||
## 1. in den Einstellungen gesetzter Pfad (QUELLEN_SETTING),
|
||||
## 2. bisheriges Verhalten = Sibling-Repos (DEFAULT_QUELLEN),
|
||||
## 3. mitgelieferter Ordner models/ direkt neben der App (MODELS_DIR).
|
||||
## Doppelnennungen werden gefiltert (Reihenfolge bleibt erhalten); ein
|
||||
## ungültiger Settings-Wert (kein Array) fällt komplett auf die Vorgabe.
|
||||
static func quellen() -> Array:
|
||||
var setting: Variant = ProjectSettings.get_setting(QUELLEN_SETTING, DEFAULT_QUELLEN)
|
||||
if not (setting is Array or setting is PackedStringArray):
|
||||
return DEFAULT_QUELLEN.duplicate()
|
||||
var out: Array = []
|
||||
for q in setting:
|
||||
if q is String and not (q as String).strip_edges().is_empty():
|
||||
out.append((q as String).strip_edges())
|
||||
return out if not out.is_empty() else DEFAULT_QUELLEN.duplicate()
|
||||
var setting: Variant = ProjectSettings.get_setting(QUELLEN_SETTING, DEFAULT_QUELLEN)
|
||||
if setting is Array or setting is PackedStringArray:
|
||||
for q in setting:
|
||||
_quelle_add(out, q)
|
||||
for q in DEFAULT_QUELLEN:
|
||||
_quelle_add(out, q)
|
||||
_quelle_add(out, MODELS_DIR)
|
||||
return out
|
||||
|
||||
|
||||
## Gültigen Quellpfad dedupliziert anhängen (typisiert, keine impliziten casts).
|
||||
static func _quelle_add(out: Array, q: Variant) -> void:
|
||||
if not (q is String):
|
||||
return
|
||||
var s := (q as String).strip_edges()
|
||||
if s.is_empty() or out.has(s):
|
||||
return
|
||||
out.append(s)
|
||||
|
||||
|
||||
## Absoluter Pfad des mitgelieferten Modell-Ordners (für die Klartext-
|
||||
## Meldung und tools/sync_models.sh-Verweis). Im Editor = Projektordner,
|
||||
## in der exportierten App = Ordner der EXE.
|
||||
static func models_pfad() -> String:
|
||||
return ProjectSettings.globalize_path("res://").path_join(MODELS_DIR)
|
||||
|
||||
|
||||
## Erster EXISTIERENDER Dateipfad für die OID (ohne Laden/Parse — billiger
|
||||
## Datei-Check über alle Kandidaten in Quellen-Reihenfolge). Leerer String =
|
||||
## kein Quellpfad hat die Datei. Für Klartext-Meldungen/Probe-Logs: zeigt,
|
||||
## AUS WELCHEM Ordner die Modelle kommen.
|
||||
static func probe_source_fuer(oid: String) -> String:
|
||||
if oid.is_empty():
|
||||
return ""
|
||||
for pfad in _kandidaten(oid):
|
||||
if FileAccess.file_exists(pfad):
|
||||
return pfad
|
||||
return ""
|
||||
|
||||
|
||||
## Ausgefüllter Klartext-Hinweis (FEHLT_TEXT + echter models/-Pfad) —
|
||||
## diese EINE Funktion liefert die Meldung, die die App zeigt, wenn
|
||||
## Modelle fehlen (Karte t_12cf94fe, Auftrag 3).
|
||||
static func fehl_text() -> String:
|
||||
return FEHLT_TEXT % models_pfad()
|
||||
|
||||
|
||||
## Kandidaten-Pfade (absolut) in Ladereihenfolge: Registry-`glb` (A6, Kategorie-
|
||||
|
|
@ -139,6 +198,34 @@ static func fehlt(oid: String) -> bool:
|
|||
return true
|
||||
|
||||
|
||||
## true = KEINE Quelle (Einstellungen/Siblings/models/) enthält irgend eine
|
||||
## GLB-Datei → die App hat nirgends echte Modelle. Für das Klartext-Banner
|
||||
## beim Start (Karte t_12cf94fe, Auftrag 3). Nicht-cachend, einmal pro Start.
|
||||
static func lager_ist_leer() -> bool:
|
||||
for quelle in quellen():
|
||||
if _quelle_hat_glb(ProjectSettings.globalize_path("res://").path_join(str(quelle))):
|
||||
return false
|
||||
return true
|
||||
|
||||
|
||||
## Enthält der Ordner (rekursiv) mindestens eine .glb-Datei?
|
||||
static func _quelle_hat_glb(basis: String) -> bool:
|
||||
var d := DirAccess.open(basis)
|
||||
if d == null:
|
||||
return false
|
||||
d.list_dir_begin()
|
||||
var name := d.get_next()
|
||||
while name != "":
|
||||
var pfad := basis.path_join(name)
|
||||
if d.current_is_dir():
|
||||
if _quelle_hat_glb(pfad):
|
||||
return true
|
||||
elif name.ends_with(".glb"):
|
||||
return true
|
||||
name = d.get_next()
|
||||
return false
|
||||
|
||||
|
||||
## Cache leeren (Tests / nach manuellem Eingriff ins Mesh-Lager).
|
||||
static func cache_leeren() -> void:
|
||||
_cache.clear()
|
||||
|
|
|
|||
229
tests/test_model_sources.gd
Normal file
229
tests/test_model_sources.gd
Normal file
|
|
@ -0,0 +1,229 @@
|
|||
extends SceneTree
|
||||
## Editor-Tests (headless): Karte t_12cf94fe — Modell-Quellen-Reihenfolge,
|
||||
## mitgelieferter models/-Ordner, Klartext-Banner, Notfall-Modus.
|
||||
## Ausführen: godot --headless --path . --script res://tests/test_model_sources.gd
|
||||
## Exit 0 = alles grün. Prüft:
|
||||
## 1. quellen(): Reihenfolge Einstellungs-Pfad → Sibling-Defaults → models/,
|
||||
## Duplikate gefiltert, ungültiger Settings-Wert fällt auf die Vorgabe.
|
||||
## 2. probe_source_fuer(oid): erster EXISTIERENDER Kandidat gewinnt, ohne
|
||||
## Parse (billiger FileAccess-Check), leerer String ohne Fund.
|
||||
## 3. fehl_text(): Klartext-Meldung enthält models/-Pfad + sync_models.sh.
|
||||
## 4. lager_ist_leer(): true ohne GLB-Quellen, false sobald eine Quelle eine
|
||||
## GLB hat (auch rekursiv verschachtelt).
|
||||
## 5. Notfall-Modus: fehlende OID → fehlt()=true, mesh_fuer()=null (Platzhalter
|
||||
## ist Sache des view_builders — hier nur der Store-Vertrag).
|
||||
## 6. models_pfad(): endet auf /models, neben der App (res://-Wurzel).
|
||||
##
|
||||
## Die Tests arbeiten auf TEMP-Ordnern und ProjectSettings im Speicher —
|
||||
## sie berühren KEINE echten Dateien im Repo.
|
||||
|
||||
var failures := 0
|
||||
var checks := 0
|
||||
|
||||
|
||||
func _init() -> void:
|
||||
# Vorgabe-Zustand: keine Einstellungs-Quellen (Sly-PCs ohne Siblings sind
|
||||
# der Normalfall dieser Karte).
|
||||
if ProjectSettings.has_setting(GlbMeshStore.QUELLEN_SETTING):
|
||||
ProjectSettings.set_setting(GlbMeshStore.QUELLEN_SETTING, PackedStringArray())
|
||||
GlbMeshStore.cache_leeren()
|
||||
_test_quellen_reihenfolge()
|
||||
_test_probe_source()
|
||||
_test_fehl_text()
|
||||
_test_lager_ist_leer()
|
||||
_test_notfall_modus()
|
||||
_test_models_pfad()
|
||||
if failures == 0:
|
||||
print("ALL OK: %d checks" % checks)
|
||||
quit(0)
|
||||
else:
|
||||
print("FAILED: %d/%d checks" % [failures, checks])
|
||||
quit(1)
|
||||
|
||||
|
||||
func _ok(bedingung: bool, name: String, detail := "") -> void:
|
||||
checks += 1
|
||||
if bedingung:
|
||||
print(" ok: %s" % name)
|
||||
else:
|
||||
failures += 1
|
||||
print(" FEHLER: %s%s" % [name, " — " + detail if detail != "" else ""])
|
||||
|
||||
|
||||
func _temp_ordner(na: String) -> String:
|
||||
var p := OS.get_environment("TMPDIR")
|
||||
if p.is_empty():
|
||||
p = "/tmp"
|
||||
var out := p.path_join("t12cf94fe_" + na + "_" + str(randi() % 100000))
|
||||
DirAccess.make_dir_recursive_absolute(out)
|
||||
return out
|
||||
|
||||
|
||||
## 1. Reihenfolge: (1) Settings-Pfad, (2) Defaults, (3) models/ — Duplikate raus.
|
||||
func _test_quellen_reihenfolge() -> void:
|
||||
# Ohne Setting: Defaults + models/ am Ende, keine Duplikate.
|
||||
ProjectSettings.set_setting(GlbMeshStore.QUELLEN_SETTING, PackedStringArray())
|
||||
var q: Array = GlbMeshStore.quellen()
|
||||
_ok(q.size() == 3, "quellen: ohne Setting = 2 Defaults + models/", str(q))
|
||||
_ok(q[0] == GlbMeshStore.DEFAULT_QUELLEN[0] and q[1] == GlbMeshStore.DEFAULT_QUELLEN[1],
|
||||
"quellen: Defaults zuerst (bisheriges Verhalten)", str(q))
|
||||
_ok(q[2] == GlbMeshStore.MODELS_DIR, "quellen: models/ zuletzt", str(q))
|
||||
# Mit Setting: der gesetzte Pfad gewinnt (erster Treffer).
|
||||
ProjectSettings.set_setting(GlbMeshStore.QUELLEN_SETTING,
|
||||
PackedStringArray(["/eigener/pfad", q[0] as String]))
|
||||
var q2: Array = GlbMeshStore.quellen()
|
||||
_ok(q2[0] == "/eigener/pfad", "quellen: gesetzter Pfad gewinnt", str(q2))
|
||||
_ok(q2.find(GlbMeshStore.MODELS_DIR) == q2.size() - 1,
|
||||
"quellen: models/ bleibt am Ende", str(q2))
|
||||
_ok(q2.size() == 4, "quellen: Duplikat Default[0] gefiltert", str(q2))
|
||||
# Leere/whitespace Einträge fliegen raus.
|
||||
ProjectSettings.set_setting(GlbMeshStore.QUELLEN_SETTING,
|
||||
PackedStringArray(["", " ", "/x"]))
|
||||
var q3: Array = GlbMeshStore.quellen()
|
||||
_ok(q3[0] == "/x", "quellen: leere Einträge gefiltert", str(q3))
|
||||
# Ungültiger Settings-Wert (kein Array) → fällt auf Vorgabe-Verhalten.
|
||||
ProjectSettings.set_setting(GlbMeshStore.QUELLEN_SETTING, "kein-array")
|
||||
var q4: Array = GlbMeshStore.quellen()
|
||||
_ok(q4.size() >= 3 and q4[0] == GlbMeshStore.DEFAULT_QUELLEN[0],
|
||||
"quellen: ungültiger Wert → Vorgabe", str(q4))
|
||||
ProjectSettings.set_setting(GlbMeshStore.QUELLEN_SETTING, PackedStringArray())
|
||||
|
||||
|
||||
## 2. probe_source_fuer: erster EXISTIERENDER Pfad, Reihenfolge respektiert,
|
||||
## verschachtelter Registry-Pfad VOR flacher Alt-Ablage, kein Parse.
|
||||
## Der verschachtelte Test nutzt eine echte Registry-OID (sonst erzeugt
|
||||
## _kandidaten keinen verschachtelten Kandidaten-Pfad).
|
||||
func _test_probe_source() -> void:
|
||||
var basis := _temp_ordner("probe")
|
||||
var a := basis.path_join("a_objekte")
|
||||
var b := basis.path_join("b_objekte")
|
||||
var models := basis.path_join("models")
|
||||
# Echte OID mit verschachteltem glb-Pfad aus der Registry holen.
|
||||
var manifest: Dictionary = {}
|
||||
var parsed: Variant = JSON.parse_string(
|
||||
FileAccess.get_file_as_string(GlbMeshStore.MANIFEST_PATH))
|
||||
if parsed is Dictionary:
|
||||
manifest = (parsed as Dictionary).get("objects", {})
|
||||
var oid := ""
|
||||
var sub := ""
|
||||
for key: String in manifest:
|
||||
var eintrag: Variant = manifest[key]
|
||||
if eintrag is Dictionary:
|
||||
var glb := str((eintrag as Dictionary).get("glb", ""))
|
||||
if glb.begins_with("res://"):
|
||||
oid = key
|
||||
sub = glb.substr(6)
|
||||
break
|
||||
if oid.is_empty():
|
||||
print(" SKIP: kein OID mit res://-glb in der Registry — verschachtelter Pfad nicht testbar")
|
||||
return
|
||||
DirAccess.make_dir_recursive_absolute(a.path_join(sub.get_base_dir()))
|
||||
DirAccess.make_dir_recursive_absolute(b)
|
||||
DirAccess.make_dir_recursive_absolute(models)
|
||||
# (1) verschachtelt in a (Registry-Pfad), (2) flach in b, (3) models/.
|
||||
var glb_a := a.path_join(sub)
|
||||
var glb_b := b.path_join(oid + ".glb")
|
||||
var glb_m := models.path_join(oid + ".glb")
|
||||
for p: String in [glb_a, glb_b, glb_m]:
|
||||
var f := FileAccess.open(p, FileAccess.WRITE)
|
||||
f.store_8(0) # Inhalt egal — probe nur file_exists, kein Parse
|
||||
f.close()
|
||||
ProjectSettings.set_setting(GlbMeshStore.QUELLEN_SETTING, PackedStringArray(
|
||||
[_relativ(basis, a), _relativ(basis, b), _relativ(basis, models)]))
|
||||
GlbMeshStore.cache_leeren()
|
||||
_ok(GlbMeshStore.probe_source_fuer(oid).simplify_path() == glb_a.simplify_path(),
|
||||
"probe: verschachtelter Registry-Pfad in Quelle 1 gewinnt",
|
||||
GlbMeshStore.probe_source_fuer(oid))
|
||||
_ok(GlbMeshStore.probe_source_fuer(oid).contains("/a_objekte/"),
|
||||
"probe: Pfad enthält Quelle 1", GlbMeshStore.probe_source_fuer(oid))
|
||||
# Nur models/ hat die Datei → Pfad aus models/.
|
||||
var oid2 := "nur_models_oid"
|
||||
var glb_m2 := models.path_join(oid2 + ".glb")
|
||||
var f2 := FileAccess.open(glb_m2, FileAccess.WRITE)
|
||||
f2.store_8(0)
|
||||
f2.close()
|
||||
_ok(GlbMeshStore.probe_source_fuer(oid2).simplify_path() == glb_m2.simplify_path(),
|
||||
"probe: models/-Pfad gefunden, wenn nur dort vorhanden",
|
||||
GlbMeshStore.probe_source_fuer(oid2))
|
||||
_ok(GlbMeshStore.probe_source_fuer(oid2).contains("/models/"),
|
||||
"probe: Pfad enthält /models/", GlbMeshStore.probe_source_fuer(oid2))
|
||||
_ok(GlbMeshStore.probe_source_fuer("gaenzlich_fehlt") == "",
|
||||
"probe: leerer String ohne Fund")
|
||||
_ok(GlbMeshStore.probe_source_fuer("") == "", "probe: leere OID → leerer String")
|
||||
ProjectSettings.set_setting(GlbMeshStore.QUELLEN_SETTING, PackedStringArray())
|
||||
GlbMeshStore.cache_leeren()
|
||||
|
||||
|
||||
## 3. Klartext-Meldung: models/-Pfad + sync_models.sh-Verweis, mehrzeilig.
|
||||
func _test_fehl_text() -> void:
|
||||
var text := GlbMeshStore.fehl_text()
|
||||
_ok(text.contains(GlbMeshStore.models_pfad()), "fehl_text: models/-Pfad drin")
|
||||
_ok(text.contains("sync_models.sh"), "fehl_text: sync_models.sh-Verweis drin")
|
||||
_ok(text.contains("Notfall-Modus"), "fehl_text: Notfall-Modus erwähnt")
|
||||
_ok(text.contains("\n"), "fehl_text: mehrzeilig (klar lesbar)")
|
||||
_ok(not text.contains("%s"), "fehl_text: kein unfüllter Platzhalter")
|
||||
|
||||
|
||||
## 4. lager_ist_leer: ohne Quellen true; false, sobald eine Quelle (auch
|
||||
## verschachtelt) eine GLB trägt. models/ zählt als Quelle.
|
||||
func _test_lager_ist_leer() -> void:
|
||||
var basis := _temp_ordner("leer")
|
||||
ProjectSettings.set_setting(GlbMeshStore.QUELLEN_SETTING, PackedStringArray())
|
||||
GlbMeshStore.cache_leeren()
|
||||
_ok(GlbMeshStore.lager_ist_leer(),
|
||||
"lager_ist_leer: true ohne GLB-Quellen (Testumgebung hat keine Siblings)")
|
||||
var tief := basis.path_join("tief/verschachtelt")
|
||||
DirAccess.make_dir_recursive_absolute(tief)
|
||||
var f := FileAccess.open(tief.path_join("eins.glb"), FileAccess.WRITE)
|
||||
f.store_8(0)
|
||||
f.close()
|
||||
ProjectSettings.set_setting(GlbMeshStore.QUELLEN_SETTING,
|
||||
PackedStringArray([_relativ(basis, basis)]))
|
||||
_ok(not GlbMeshStore.lager_ist_leer(),
|
||||
"lager_ist_leer: false mit verschachtelter GLB in Quelle")
|
||||
ProjectSettings.set_setting(GlbMeshStore.QUELLEN_SETTING, PackedStringArray())
|
||||
GlbMeshStore.cache_leeren()
|
||||
|
||||
|
||||
## 5. Notfall-Modus: fehlende OID → fehlt()=true, mesh_fuer()=null. Ohne Modelle
|
||||
## darf der Store nicht crashen (Platzhalter übernimmt der view_builder).
|
||||
func _test_notfall_modus() -> void:
|
||||
ProjectSettings.set_setting(GlbMeshStore.QUELLEN_SETTING, PackedStringArray())
|
||||
GlbMeshStore.cache_leeren()
|
||||
_ok(GlbMeshStore.fehlt("notfall_test_oid"), "notfall: fehlt()=true ohne Quellen")
|
||||
_ok(GlbMeshStore.mesh_fuer("notfall_test_oid") == null,
|
||||
"notfall: mesh_fuer()=null ohne Quellen (view_builder zeigt Platzhalter)")
|
||||
_ok(GlbMeshStore.probe_source_fuer("notfall_test_oid") == "",
|
||||
"notfall: probe_source_fuer() leer ohne Quellen")
|
||||
|
||||
|
||||
## 6. models_pfad: direkt neben der App (res://-Wurzel), endet auf /models.
|
||||
func _test_models_pfad() -> void:
|
||||
var p := GlbMeshStore.models_pfad()
|
||||
_ok(p.ends_with("/models"), "models_pfad: endet auf /models", p)
|
||||
_ok(p.begins_with(ProjectSettings.globalize_path("res://")),
|
||||
"models_pfad: neben der App (res://-Wurzel)", p)
|
||||
_ok(p == ProjectSettings.globalize_path("res://").path_join("models"),
|
||||
"models_pfad: exakt res://-Wurzel/models", p)
|
||||
|
||||
|
||||
## Relativer Pfad (vom res://-Wurzel aus) — Quellen sind basis-relativ.
|
||||
## (_basis wird ignoriert: die Funktion rechnet IMMER von der res://-Wurzel,
|
||||
## wie _relativ_zu in test_glb_view.gd — sonst zeigt der Pfad in den falschen
|
||||
## Ordner, wenn _kandidaten ihn wieder an die Wurzel hängt.)
|
||||
static func _relativ(_basis: String, pfad: String) -> String:
|
||||
var von := ProjectSettings.globalize_path("res://").simplify_path()
|
||||
var n := pfad.simplify_path()
|
||||
var v: PackedStringArray = von.split("/", false)
|
||||
var teile_n: PackedStringArray = n.split("/", false)
|
||||
var i := 0
|
||||
while i < v.size() and i < teile_n.size() and v[i] == teile_n[i]:
|
||||
i += 1
|
||||
var out := PackedStringArray()
|
||||
for _j: int in range(i, v.size()):
|
||||
out.append("..")
|
||||
for k: int in range(i, teile_n.size()):
|
||||
out.append(teile_n[k])
|
||||
if out.is_empty():
|
||||
return "."
|
||||
return "/".join(out)
|
||||
1
tests/test_model_sources.gd.uid
Normal file
1
tests/test_model_sources.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
|||
uid://bff2puoer0n1i
|
||||
26
tools/probe_gen_map.gd
Normal file
26
tools/probe_gen_map.gd
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
extends SceneTree
|
||||
## Probe-Map-Generator (Karte t_12cf94fe): erstellt eine .svmap mit Objekten,
|
||||
## die in der Bibliothek existieren, um den Eigenständigkeits-Beweis der
|
||||
## exportierten App zu fahren.
|
||||
## Aufruf: godot4 --headless --path . --script res://tools/probe_gen_map.gd \
|
||||
## -- --out=/pfad/zu/probe_map.svmap
|
||||
|
||||
|
||||
func _init() -> void:
|
||||
var out := "/tmp/scopedvr_sandbox/probe_map.svmap"
|
||||
for a in OS.get_cmdline_user_args():
|
||||
if a.begins_with("--out="):
|
||||
out = a.substr(6)
|
||||
var doc := SvmapDocument.create_new("Probe-Map Eigenständig", 64.0, "mittag")
|
||||
# baum_kiefer_1 und stein_mittel_1 existieren garantiert als flache GLBs in
|
||||
# models/assets/objekte/ (kein crate_wood — das gibt es als GLB nicht).
|
||||
(doc["objects"] as Array).append({"id": "baum_kiefer_1", "pos": [0.0, 0.6, 0.0], "rot_y_deg": 0.0, "scale": 1.0})
|
||||
(doc["objects"] as Array).append({"id": "baum_kiefer_1", "pos": [4.0, 0.6, 2.0], "rot_y_deg": 45.0, "scale": 2.0})
|
||||
(doc["objects"] as Array).append({"id": "stein_mittel_1", "pos": [8.0, 0.6, 4.0], "rot_y_deg": 90.0, "scale": 1.0})
|
||||
var result := SvmapIO.save_to_file(out, doc)
|
||||
if result.ok:
|
||||
print("PROBE_MAP_GENERATED: ", out, " objects=", SvmapDocument.object_count(doc))
|
||||
quit(0)
|
||||
else:
|
||||
print("PROBE_MAP_FEHLSCHLAG: ", str(result))
|
||||
quit(1)
|
||||
1
tools/probe_gen_map.gd.uid
Normal file
1
tools/probe_gen_map.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
|||
uid://t4og8rmlfh05
|
||||
185
tools/sync_models.sh
Executable file
185
tools/sync_models.sh
Executable file
|
|
@ -0,0 +1,185 @@
|
|||
#!/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."
|
||||
102
utils/modelle_status.gd
Normal file
102
utils/modelle_status.gd
Normal file
|
|
@ -0,0 +1,102 @@
|
|||
extends Node
|
||||
## Autoload: Modell-Status + Probe (Karte t_12cf94fe).
|
||||
##
|
||||
## Zwei Aufgaben:
|
||||
## 1. Banner/Klartext: Wenn beim App-Start KEINE Modell-Quelle irgendeine
|
||||
## GLB enthält, sagt die App deutlich, wie sie die Modelle bekommt
|
||||
## (GlbMeshStore.fehl_text()). Kein stiller Kisten-Platzhalter.
|
||||
## 2. Probe-Modus für den Eigenständigkeits-Beweis: startet die exportierte
|
||||
## App mit --probe-map=/pfad/test.svmap (NUR user-args, also nach --),
|
||||
## öffnet die Map über die Editor-Methode, sammelt die OID-Belege
|
||||
## (Quellpfad je Objekt, fehlende_glbs) und beendet sich mit Exit 0/1.
|
||||
##
|
||||
## Aufruf (exportierte App, headless):
|
||||
## ./MapEditor.x86_64 --headless -- --probe-map=/pfad/test.svmap
|
||||
##
|
||||
## main.gd wird NICHT anfasst (Kollisionsschutz U1) — alle Aufrufe gehen
|
||||
## über call() auf die laufende Szene.
|
||||
|
||||
const PROBE_ARG := "--probe-map="
|
||||
const PROBE_WARTEFRAMES_START := 2
|
||||
const PROBE_WARTEFRAMES_MAP := 20
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
var args := OS.get_cmdline_user_args()
|
||||
for a in args:
|
||||
if a.begins_with(PROBE_ARG):
|
||||
_probe(str(a.substr(PROBE_ARG.length())))
|
||||
return
|
||||
_bannern()
|
||||
|
||||
|
||||
## 1. Klartext-Banner, wenn nirgends Modelle liegen (GUI + Log).
|
||||
func _bannern() -> void:
|
||||
await get_tree().process_frame
|
||||
await get_tree().process_frame
|
||||
if GlbMeshStore.lager_ist_leer():
|
||||
var text := GlbMeshStore.fehl_text()
|
||||
print("⚠ MODELL-FEHLT-BANNER:")
|
||||
for zeile in text.split("\n"):
|
||||
print(" " + zeile)
|
||||
var szene := get_tree().current_scene
|
||||
if szene != null and szene.has_method("_set_status"):
|
||||
szene.call("_set_status", "⚠ Modelle fehlen — Terrain/Bauteile funktionieren. Details im Log.")
|
||||
if DisplayServer.get_name() != "headless":
|
||||
var dialog := AcceptDialog.new()
|
||||
dialog.title = "Modelle fehlen"
|
||||
dialog.dialog_text = text
|
||||
dialog.ok_button_text = "Verstanden"
|
||||
get_tree().root.add_child(dialog)
|
||||
dialog.popup_centered()
|
||||
|
||||
|
||||
## 2. Probe: Map öffnen, Belege sammeln, Exit 0/1.
|
||||
func _probe(map_pfad: String) -> void:
|
||||
for i in PROBE_WARTEFRAMES_START:
|
||||
await get_tree().process_frame
|
||||
var szene := get_tree().current_scene
|
||||
if szene == null or not szene.has_method("_on_open_selected"):
|
||||
print("PROBE FEHLGESCHLAGEN: keine Szene mit _on_open_selected")
|
||||
get_tree().quit(1)
|
||||
return
|
||||
var result: Variant = szene.call("_on_open_selected", map_pfad)
|
||||
for i in PROBE_WARTEFRAMES_MAP:
|
||||
await get_tree().process_frame
|
||||
|
||||
# --- Belege sammeln -------------------------------------------------------
|
||||
var fehlend: Array = []
|
||||
var view_builder: Variant = szene.get("view_builder")
|
||||
if view_builder != null:
|
||||
fehlend = view_builder.get("fehlende_glbs")
|
||||
var oids: Array = []
|
||||
var doc: Variant = szene.get("doc")
|
||||
if doc is Dictionary:
|
||||
for obj in (doc as Dictionary).get("objects", []):
|
||||
if obj is Dictionary:
|
||||
var oid := str((obj as Dictionary).get("id", ""))
|
||||
if oid != "" and not oids.has(oid):
|
||||
oids.append(oid)
|
||||
var modelle_da := DirAccess.dir_exists_absolute(GlbMeshStore.models_pfad())
|
||||
var geladen: bool = doc is Dictionary and SvmapDocument.object_count(doc) > 0
|
||||
|
||||
print("PROBE map=%s" % map_pfad)
|
||||
print("PROBE map_geladen=%s objekte=%d" % [str(geladen), oids.size()])
|
||||
print("PROBE modelle_ordinar=%s pfad=%s" % [str(modelle_da), GlbMeshStore.models_pfad()])
|
||||
print("PROBE quellen=%s" % ", ".join(PackedStringArray(GlbMeshStore.quellen())))
|
||||
var quellen_pro_oid := 0
|
||||
for oid in oids:
|
||||
var pfad := GlbMeshStore.probe_source_fuer(oid)
|
||||
if pfad.contains("/models/"):
|
||||
quellen_pro_oid += 1
|
||||
var muster: Array = []
|
||||
for oid in oids.slice(0, 5):
|
||||
muster.append("%s -> %s" % [oid, GlbMeshStore.probe_source_fuer(oid)])
|
||||
if not muster.is_empty():
|
||||
print("PROBE oid-quellen (erste %d):" % muster.size())
|
||||
for m in muster:
|
||||
print(" " + m)
|
||||
print("PROBE aus-models=%d/%d fehlende_glbs=%d" % [quellen_pro_oid, oids.size(), fehlend.size()])
|
||||
var ok := geladen and fehlend.is_empty()
|
||||
print("PROBE ERGEBNIS: %s" % ("OK" if ok else "FEHLGESCHLAGEN"))
|
||||
get_tree().quit(0 if ok else 1)
|
||||
1
utils/modelle_status.gd.uid
Normal file
1
utils/modelle_status.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
|||
uid://l32qlb021yw2
|
||||
Loading…
Reference in a new issue