- 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>
263 lines
12 KiB
GDScript
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()
|