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://core/viewport/map_view_builder.gd")) root.add_child(builder) builder.call("rebuild", doc, manifest) return builder func _manifest() -> Dictionary: return {"objects": {"test_ohne_mesh": {"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://core/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("test_ohne_mesh", [4.0, 0.0, 2.0])) (doc["objects"] as Array).append(_obj("test_ohne_mesh", [-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("test_ohne_mesh", [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("test_ohne_mesh", [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("test_ohne_mesh", [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://core/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()