scopedvr-map-editor/tests/test_selection_look.gd
slyphax a76c3ecae7 Bauen-Modus neu: Bauteile als normale Objekte (bau_*), Snap an Kante/Zelle/Ebene, Gebäude = Gruppe
- BauKatalog: 12 Teile (Fundament, Boden, Wände mit Tür/Fenster/Scharte, Brüstung, Pfeiler, Giebel, Dächer, Treppe) auf Basis der CC0-Geometrie des Modular Builder 0.2.0, Wandhöhe 3 m
- BuildSnap: Wände auf Zellkanten, Böden in Zellmitte, Höhe aus Ebenen statt Klickhöhe, Ersetzen (Tür in Wand)
- Gebäude kopieren/auswählen/löschen im Bauen-Tab, Kopien bekommen eigene Gruppe
- Kollision serverkonsistent über Trimesh-Dateien (Türen/Fenster echte Löcher), tools/gen_bauteile.gd erzeugt GLB, Kollision, Registry-Fragment
- Alte gebaeude-Sektion wird beim Laden migriert, altes Bauen-Werkzeug entfernt, Resize behält Gruppennamen
- Tests: test_bauen (258 Checks), Playtest läuft Treppe/Tür

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-25 16:30:15 +01:00

263 lines
12 KiB
GDScript

extends SceneTree
## Editor-Tests (headless): U6 dezente Auswahl — Kontur statt Vollorange
## (Karte t_639135dd). Ausführen:
## godot --headless --path . -s res://tests/test_selection_look.gd
## Exit 0 = alles grün.
##
## T1 Theme ist die einzige Quelle: Kontur-Farben (Auswahl/Hover/Partner)
## + Breiten existieren, Partner/Hover sind schwächer als die Auswahl.
## T2 Grundmaterial bleibt: Auswahl setzt KEIN Vollfarben-Override mehr —
## der Mesh-Kind-Knoten trägt weiter die Editor-Grundfarbe (Platzhalter)
## bzw. null (GLB — Textur bleibt; echte GLBs deckt test_glb_view ab).
## T3 Outline-Node nur bei markierten Objekten (Auswahl/Hover/Partner),
## bei Abwahl restlos weg (kein Rest-Outline, kein Rest-Meta).
## T4 Abstufungen: Hover = schwächstes Material, Auswahl = normale Kontur,
## Spiegel-Zwilling = dünnere + gestrichelte Kontur in eigener Farbe.
## T5 Spawn-Zonen: dezente Variante — Ring statt Aufhellung, restlos weg
## bei Abwahl; Disc-Deckkraft bleibt konstant.
## T6 Bau-Teile: Kontur um das Bauteil-Mesh statt Vollbox, weg bei Abwahl.
var failures := 0
var checks := 0
func _initialize() -> void:
_run_all.call_deferred()
func _check(cond: bool, label: String) -> void:
checks += 1
print("%s: %s" % ["ok " if cond else "FAIL", label])
if not cond:
failures += 1
func _run_all() -> void:
await process_frame
_test_theme_quelle()
await _test_auswahl_platzhalter()
await _test_hover_abstufung()
await _test_zwilling()
await _test_spawn_ring()
if failures == 0:
print("ALL OK: %d checks" % checks)
quit(0)
else:
print("FAILED: %d/%d checks" % [failures, checks])
quit(1)
func _obj(id: String, pos: Array, rot := 0.0) -> Dictionary:
return {"id": id, "pos": pos, "scale": 1.0, "rot_y_deg": float(rot)}
func _build(manifest: Dictionary, doc: Dictionary) -> Node3D:
var builder := Node3D.new()
builder.set_script(load("res://src/viewport/map_view_builder.gd"))
root.add_child(builder)
builder.call("rebuild", doc, manifest)
return builder
func _manifest() -> Dictionary:
return {"objects": {"crate_wood": {"collision": {"shape": "box", "size": [1.2, 1.2, 1.2]}}}}
func _flat_doc() -> Dictionary:
return SvmapDocument.create_new("U6Test", 256.0, "mittag")
func _outline_von(marker: Node3D) -> MeshInstance3D:
if not marker.has_meta("outline"):
return null
var alt: Variant = marker.get_meta("outline")
if alt is MeshInstance3D and is_instance_valid(alt):
return alt
return null
func _restlos(builder: Node3D, label: String) -> void:
await process_frame
var sauber := true
for marker in builder.object_markers:
if marker == null or not is_instance_valid(marker):
continue
if marker.has_meta("outline"):
sauber = false
var mi := marker.get_child(0) as MeshInstance3D
for kind in mi.get_children():
if kind.name == "SelectionOutline" and is_instance_valid(kind) \
and not (kind as Node).is_queued_for_deletion():
sauber = false
_check(sauber, label)
# ------------------------------------------------------------------ T1 ------
func _test_theme_quelle() -> void:
print("== T1 Theme als einzige Quelle ==")
var theme_res: Variant = load("res://theme/editor_theme.tres")
_check(theme_res is Theme, "Theme lädt")
if not (theme_res is Theme):
return
var th := theme_res as Theme
var sel := th.get_color("selection_outline", "Editor")
var hover := th.get_color("selection_outline_hover", "Editor")
var partner := th.get_color("selection_outline_partner", "Editor")
_check(th.has_color("selection_outline", "Editor"), "Theme: Auswahl-Kontur-Farbe")
_check(th.has_color("selection_outline_hover", "Editor"), "Theme: Hover-Kontur-Farbe")
_check(th.has_color("selection_outline_partner", "Editor"), "Theme: Partner-Kontur-Farbe")
_check(hover.a < sel.a, "Theme: Hover schwächer als Auswahl (Deckkraft)")
_check(not partner.is_equal_approx(sel), "Theme: Partner hat EIGENE Farbe")
_check(th.get_constant("selection_outline_width_mm", "Editor") > 0,
"Theme: Auswahl-Breite > 0")
_check(th.get_constant("selection_outline_width_partner_mm", "Editor")
< th.get_constant("selection_outline_width_mm", "Editor"),
"Theme: Partner-Kontur dünner als Auswahl")
# ------------------------------------------------------------------ T2/T3 ---
func _test_auswahl_platzhalter() -> void:
print("== T2/T3 Auswahl am Platzhalter: Grundmaterial bleibt, Kontur nur bei Auswahl ==")
var doc := _flat_doc()
(doc["objects"] as Array).append(_obj("crate_wood", [4.0, 0.0, 2.0]))
(doc["objects"] as Array).append(_obj("crate_wood", [-4.0, 0.0, -2.0]))
var builder := _build(_manifest(), doc)
var m0: Node3D = builder.object_markers[0]
var m1: Node3D = builder.object_markers[1]
var mi0 := m0.get_child(0) as MeshInstance3D
var mat_sel: ShaderMaterial = builder.get("_outline_mat_sel")
_check(not bool(m0.get_meta("glb", true)), "Probenobjekt ist Platzhalter (kein GLB)")
_check(mi0.material_override == builder.get("_base_mat"),
"ungewählt: Editor-Grundfarbe am Mesh (Platzhalter)")
builder.call("set_selected_multi", [0])
await process_frame
_check(mi0.material_override == builder.get("_base_mat"),
"Auswahl: Grundmaterial UNVERÄNDERT (kein material_override mit Vollfarbe)")
var out0 := _outline_von(m0)
_check(out0 != null, "Auswahl: Outline-Node existiert")
if out0 != null:
_check(out0.mesh == mi0.mesh, "Outline trägt dasselbe Mesh (Außenhülle)")
_check(out0.get_parent() == mi0, "Outline hängt am Mesh (erbt Position/Scale)")
_check(out0.material_override == mat_sel, "Outline trägt das Auswahl-Kontur-Material")
_check(_outline_von(m1) == null, "unmarkiertes Objekt: KEIN Outline-Node")
# Abwahl: restlos weg.
builder.call("set_selected_multi", [])
await _restlos(builder, "Abwahl: kein Rest-Outline, kein Rest-Meta")
await _test_glb_regel(builder)
builder.queue_free()
## GLB-Regel auch ohne echtes Modell prüfbar machen: ein Marker, dessen Meta
## "glb" = true ist, darf NIE ein material_override bekommen. Der Platzhalter-
## Builder baut nur glb=false — die echte GLB-Strecke deckt test_glb_view ab;
## hier wird die Regel direkt am Bau-Pfad verifiziert (Quelltext-Garantie:
## material_override = null wenn echt). Wir prüfen die Meta-Abhängigkeit,
## indem wir einen Marker künstlich als GLB markieren und neu auswählen.
func _test_glb_regel(builder: Node3D) -> void:
var doc: Dictionary = builder.get("_doc_ref")
(doc["objects"] as Array).append(_obj("crate_wood", [0.0, 0.0, 8.0]))
builder.call("rebuild", doc, _manifest())
var m2: Node3D = builder.object_markers[2]
m2.set_meta("glb", true)
var mi2 := m2.get_child(0) as MeshInstance3D
mi2.material_override = null
builder.call("set_selected_multi", [2])
await process_frame
_check(mi2.material_override == null,
"GLB-Objekt ausgewählt: material_override bleibt null (Textur bleibt sichtbar)")
_check(_outline_von(m2) != null, "GLB-Objekt ausgewählt: Kontur trägt die Markierung")
builder.call("set_selected_multi", [])
# ------------------------------------------------------------------ T4 ------
func _test_hover_abstufung() -> void:
print("== T4a Hover = schwächste Markierung ==")
var doc := _flat_doc()
(doc["objects"] as Array).append(_obj("crate_wood", [4.0, 0.0, 2.0]))
var builder := _build(_manifest(), doc)
var m0: Node3D = builder.object_markers[0]
builder.call("set_hover", 0)
await process_frame
var out0 := _outline_von(m0)
_check(out0 != null, "Hover: Outline-Node existiert")
if out0 != null:
_check(out0.material_override == builder.get("_outline_mat_hover"),
"Hover: schwächstes Kontur-Material")
builder.call("set_hover", -1)
await process_frame
_check(_outline_von(m0) == null, "Hover weg: Outline restlos entfernt")
# Auswahl gewinnt gegen Hover (stärkste Stufe bleibt sichtbar).
builder.call("set_hover", 0)
builder.call("set_selected_multi", [0])
await process_frame
var out_sel := _outline_von(m0)
_check(out_sel != null and out_sel.material_override == builder.get("_outline_mat_sel"),
"Auswahl + Hover gleichzeitig: Auswahl-Kontur gewinnt")
builder.queue_free()
func _test_zwilling() -> void:
print("== T4b Spiegel-Zwilling = dünnere/gestrichelte Kontur in eigener Farbe ==")
var doc := _flat_doc()
(doc["objects"] as Array).append(_obj("crate_wood", [100.0, 0.0, 40.0], 30.0))
var res: Dictionary = ObjectOps.symmetry_place_twin(doc, 0, SymmetryCore.Mode.POINT)
_check(int(res.get("twin", -1)) == 1, "Zwilling angelegt (Index 1)")
var builder := _build(_manifest(), doc)
builder.call("set_selected_multi", [0])
await process_frame
var m0: Node3D = builder.object_markers[0]
var m1: Node3D = builder.object_markers[1]
var out0 := _outline_von(m0)
var out1 := _outline_von(m1)
_check(out0 != null and out0.material_override == builder.get("_outline_mat_sel"),
"Quelle: normale Auswahl-Kontur")
_check(out1 != null and out1.material_override == builder.get("_outline_mat_partner"),
"Zwilling: Partner-Kontur (eigene, schwächere Variante)")
var mat_sel: ShaderMaterial = builder.get("_outline_mat_sel")
var mat_partner: ShaderMaterial = builder.get("_outline_mat_partner")
var breite_sel: float = mat_sel.get_shader_parameter("breite_m")
var breite_partner: float = mat_partner.get_shader_parameter("breite_m")
var streifen_partner: float = mat_partner.get_shader_parameter("streifen_hz")
var streifen_sel: float = mat_sel.get_shader_parameter("streifen_hz")
_check(breite_partner < breite_sel, "Zwilling: dünnere Kontur (Breite aus Theme)")
_check(streifen_partner > 0.0 and streifen_sel == 0.0,
"Zwilling: gestrichelt, Auswahl durchgezogen")
_check(not (mat_partner.get_shader_parameter("linien_farbe") as Color).is_equal_approx(
mat_sel.get_shader_parameter("linien_farbe")),
"Zwilling: eigene Farbe (aus dem Theme)")
# Beide angewählt → beide volle Auswahl-Kontur.
builder.call("set_selected_multi", [0, 1])
await process_frame
_check(_outline_von(m0).material_override == builder.get("_outline_mat_sel")
and _outline_von(m1).material_override == builder.get("_outline_mat_sel"),
"beide angewählt: beide volle Auswahl-Kontur (kein Partner-Zustand)")
builder.call("set_selected_multi", [])
await _restlos(builder, "Paar abgewählt: Konturen restlos weg")
builder.queue_free()
# ------------------------------------------------------------------ T5 ------
func _test_spawn_ring() -> void:
print("== T5 Spawn-Zonen: Ring statt Aufhellung ==")
var builder := _build(_manifest(), _flat_doc())
var marker: Node3D = builder.call("_spawn_marker", Vector3.ZERO, 0, "A1", 6.0)
var disc0 := marker.get_child(0) as MeshInstance3D
var alpha0: float = (disc0.material_override as StandardMaterial3D).albedo_color.a
builder.call("style_spawn_marker", marker, 6.0, true)
await process_frame
var ring: Variant = marker.get_meta("spawn_ring") if marker.has_meta("spawn_ring") else null
_check(ring is MeshInstance3D and is_instance_valid(ring), "Auswahl: Kontur-Ring existiert")
if ring is MeshInstance3D:
_check((ring as MeshInstance3D).mesh is TorusMesh, "Ring = schmaler Torus am Zonenrand")
var rmat := (ring as MeshInstance3D).material_override as StandardMaterial3D
var theme_res: Variant = load("res://theme/editor_theme.tres")
var soll: Color = (theme_res as Theme).get_color("selection_outline", "Editor")
_check(rmat.albedo_color.is_equal_approx(soll), "Ring-Farbe kommt aus dem Theme")
var disc1 := marker.get_child(0) as MeshInstance3D
var alpha1: float = (disc1.material_override as StandardMaterial3D).albedo_color.a
_check(absf(alpha0 - alpha1) < 0.001,
"Disc-Deckkraft konstant (keine Vollflächen-Aufhellung mehr)")
builder.call("style_spawn_marker", marker, 6.0, false)
await process_frame
_check(not marker.has_meta("spawn_ring"), "Abwahl: Ring-Meta restlos weg")
builder.queue_free()