Neu shaders/selection_outline.gdshader: Inverted-Hull-Außenhülle (unshaded, cull_front, Grow entlang der Normalen; Streifen-Uniform für den Zwilling). Markierung jetzt über Outline-Kind am Mesh (Auswahl/Hover/Partner); Farben + Breiten aus theme/editor_theme.tres (selection_outline*, *_width_mm) — eine Quelle. material_override wird von der Markierung nicht mehr angefasst: GLB-Texturen bleiben im Auswahl-Zustand voll sichtbar, der Tint bleibt beim Markieren sichtbar (kein "Orange gewinnt" mehr). Spawn-Zonen: konstante Disc-Deckkraft + dezenter Kontur-Ring (Torus) statt Aufhellung/Emission; Label-Farbe aus dem Theme statt hartkodiertem Gelb. Bau-Teile: Kontur um das Bauteil-Mesh statt halbtransparenter Vollbox. Neu: set_hover()-API (schwächste Stufe) — Anschluss an die Mausbewegung liegt in interaction.gd (Grenze dieser Karte) und folgt als eigene Karte. Suite tests/test_selection_look.gd (41 checks: Theme-Quelle, Grundmaterial unangetastet, Outline nur bei Markierung, restlos weg bei Abwahl, Zwilling dünner+gestrichelt, Spawn-Ring, Bau-Teil-Kontur). Altsuiten an die neue Semantik angepasst (test_glb_view: GLB-Auswahl ohne Override; test_map_tint T3: Kontur statt Orange-gewinnt; test_object_mirror: Partner per Kontur). Beweise: 4 Renderproben (fass, echtes GLB — vorher Vollorange / nachher Kontur, je auf Sand und Fels) in /opt/data/scopedvr/work/u6_auswahl_beweise/. Voll-Batterie 32/32 grün (EXIT 0) inkl. der drei angepassten Suiten.
246 lines
10 KiB
GDScript
246 lines
10 KiB
GDScript
extends SceneTree
|
|
## Editor-Test (headless): Farbe je Objekt (Tint) im Inspektor + doc + View —
|
|
## Karte t_b0edb807.
|
|
## Ausführen: godot --headless --path . --script res://tests/test_map_tint.gd
|
|
## Exit 0 = alles grün.
|
|
##
|
|
## T1 Palette: 7 feste Farben mit den vereinbarten Namen.
|
|
## T2 Farbfeld-Klick → doc-Feld "tint" ([r,g,b], 0..1) + Status „Farbe: Oliv“;
|
|
## gleiche Farbe erneut = KEIN Leer-Undo-Schritt.
|
|
## T3 Editor-Ansicht: unmarkiertes Objekt zeigt tint (Surface-Override-
|
|
## Material albedo = doc-Wert); Markierung = Auswahl-Kontur (U6), die
|
|
## Farbe bleibt sichtbar und liegt trotzdem im doc.
|
|
## T4 Mehrfachauswahl: eine Farbe auf beide Objekte = GENAU EIN Undo-Schritt.
|
|
## T5 Zurücksetzen (Pack-Farbe): Feld entfernt, Override weg, EIN Undo-Schritt.
|
|
## T6 save→load-Roundtrip: tint überlebt; alte Karte ohne Feld lädt
|
|
## unverändert (Validierung ohne Fehler, kein Feld erfunden).
|
|
|
|
var failures := 0
|
|
var checks := 0
|
|
var main
|
|
var insp
|
|
|
|
|
|
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 _undo_steps_to_clear() -> int:
|
|
var n := 0
|
|
while main.history.can_undo() and n < 50:
|
|
main.call("_do_undo")
|
|
n += 1
|
|
return n
|
|
|
|
|
|
func _sel_obj(idx: int) -> Dictionary:
|
|
return (main.doc["objects"] as Array)[idx]
|
|
|
|
|
|
func _swatch(name: String) -> Button:
|
|
for kind in insp.get("_farben_grid").get_children():
|
|
if kind is Button and str(kind.tooltip_text).begins_with(name):
|
|
return kind
|
|
return null
|
|
|
|
|
|
func _marker_mi(idx: int) -> MeshInstance3D:
|
|
var marker = main.view_builder.object_markers[idx]
|
|
return marker.get_child(0) as MeshInstance3D
|
|
|
|
|
|
func _run_all() -> void:
|
|
var scene: PackedScene = load("res://scenes/main.tscn")
|
|
_check(scene != null, "scenes/main.tscn laedt")
|
|
if scene == null:
|
|
_finish()
|
|
return
|
|
main = scene.instantiate()
|
|
root.add_child(main)
|
|
await process_frame
|
|
await process_frame
|
|
|
|
insp = main.get("inspector")
|
|
_check(insp != null, "Inspektor ist in main eingebaut")
|
|
if insp == null:
|
|
_finish()
|
|
return
|
|
|
|
# Zwei Objekte ins Doc: barrel_001 (echtes GLB) + crate_wood (Platzhalter).
|
|
(main.doc["objects"] as Array).append({
|
|
"id": "barrel_001", "pos": [4.0, 0.0, 4.0], "scale": 1.0, "rot_y_deg": 0.0})
|
|
(main.doc["objects"] as Array).append({
|
|
"id": "crate_wood", "pos": [-6.0, 0.0, 2.0], "scale": 2.0, "rot_y_deg": 10.0})
|
|
main.call("_after_structure_change")
|
|
main.history.clear()
|
|
await process_frame
|
|
|
|
await _t1_palette()
|
|
await _t2_farbe_doc()
|
|
await _t3_editor_ansicht()
|
|
await _t4_mehrfach()
|
|
await _t5_reset()
|
|
await _t6_roundtrip()
|
|
_finish()
|
|
|
|
|
|
func _t1_palette() -> void:
|
|
var palette: Array = SvmapTint.PALETTE
|
|
_check(palette.size() == 7, "T1 Palette hat 7 Farben (ist %d)" % palette.size())
|
|
var namen := PackedStringArray()
|
|
for e in palette:
|
|
namen.append(str(e[0]))
|
|
var soll := ["Wüstensand", "Oliv", "Tarn-Grün", "Stahlgrau", "Holzbraun", "Dunkelbraun", "Weiß"]
|
|
var alle := true
|
|
for n in soll:
|
|
if not namen.has(n):
|
|
alle = false
|
|
_check(alle, "T1 Paletten-Namen wie auf der Karte (%s)" % str(namen))
|
|
# Farbstatus ohne Auswahl/ohne Feld.
|
|
main.interaction.call("select_object", 0)
|
|
await process_frame
|
|
_check(str(insp.get("_farbe_status").text) == "Farbe: Pack-Farbe",
|
|
"T1 Status ohne tint = „Farbe: Pack-Farbe“ (ist „%s“)" % str(insp.get("_farbe_status").text))
|
|
_check(insp.get("_farben_grid").get_child_count() == 7, "T1 Farbfeld-Raster zeigt 7 Felder")
|
|
|
|
|
|
func _t2_farbe_doc() -> void:
|
|
main.interaction.call("select_object", 0)
|
|
await process_frame
|
|
var oliv: Color = SvmapTint.PALETTE[1][1]
|
|
insp.call("_on_farbe_gewaehlt", oliv, "Oliv")
|
|
var t: Variant = _sel_obj(0).get("tint", null)
|
|
_check(SvmapTint.gueltig(t), "T2 Klick auf Oliv schreibt tint-Feld ins doc")
|
|
var arr: Array = t
|
|
_check(absf(float(arr[0]) - oliv.r) < 0.001 and absf(float(arr[1]) - oliv.g) < 0.001
|
|
and absf(float(arr[2]) - oliv.b) < 0.001,
|
|
"T2 tint = Anzeige-Farbwerte der Palette (%s)" % str(arr))
|
|
_check(str(insp.get("_farbe_status").text) == "Farbe: Oliv", "T2 Status zeigt „Farbe: Oliv“")
|
|
# Gleiche Farbe erneut → kein Leer-Undo-Schritt.
|
|
main.history.clear()
|
|
insp.call("_on_farbe_gewaehlt", oliv, "Oliv")
|
|
_check(not main.history.can_undo(), "T2 gleiche Farbe erneut = KEIN Leer-Schritt")
|
|
# Anderer Farbe → EIN Schritt; Undo stellt die VORHERIGE Farbe (Oliv) her.
|
|
insp.call("_on_farbe_gewaehlt", SvmapTint.PALETTE[6][1], "Weiß")
|
|
_check(_undo_steps_to_clear() == 1, "T2 Farbwechsel = GENAU EIN Undo-Schritt")
|
|
_check(SvmapTint.gleich(SvmapTint.von(_sel_obj(0)), oliv),
|
|
"T2 Undo stellt die vorherige Farbe (Oliv) wieder her")
|
|
|
|
|
|
func _t3_editor_ansicht() -> void:
|
|
# Farbe setzen (Objekt ist markiert → orange gewinnt, doc hat das Feld).
|
|
main.interaction.call("select_object", 0)
|
|
await process_frame
|
|
insp.call("_on_farbe_gewaehlt", SvmapTint.PALETTE[3][1], "Stahlgrau")
|
|
var c: Color = SvmapTint.von(_sel_obj(0))
|
|
var mi := _marker_mi(0)
|
|
# U6 (t_639135dd): Markierung = Kontur am Marker — die doc-Farbe bleibt
|
|
# sichtbar (kein „Orange gewinnt“ mehr), das Feld liegt trotzdem im doc.
|
|
var out0: Variant = main.view_builder.object_markers[0].get_meta("outline", null)
|
|
_check(out0 is MeshInstance3D, "T3 markiertes Objekt zeigt Auswahl-Kontur (U6)")
|
|
# Abwählen → Farbe sichtbar: Surface-Override mit albedo = doc-Wert.
|
|
main.interaction.call("clear_selection", true)
|
|
await process_frame
|
|
var ov := mi.get_surface_override_material(0)
|
|
_check(ov is BaseMaterial3D, "T3 abgewählt: Surface-Override-Material gesetzt")
|
|
if ov is BaseMaterial3D:
|
|
var ab: Color = (ov as BaseMaterial3D).albedo_color
|
|
_check(SvmapTint.gleich(ab, c), "T3 albedo des Overrides = doc-Farbe (%s vs %s)" % [str(ab), str(c)])
|
|
# Platzhalter-Objekt (crate_wood) bekommt die Farbe ebenfalls.
|
|
main.interaction.call("select_object", 1)
|
|
await process_frame
|
|
insp.call("_on_farbe_gewaehlt", SvmapTint.PALETTE[4][1], "Holzbraun")
|
|
main.interaction.call("clear_selection", true)
|
|
await process_frame
|
|
var mi1 := _marker_mi(1)
|
|
var ov1 := mi1.get_surface_override_material(0)
|
|
_check(ov1 is BaseMaterial3D and SvmapTint.gleich((ov1 as BaseMaterial3D).albedo_color,
|
|
SvmapTint.von(_sel_obj(1))), "T3 Platzhalter zeigt doc-Farbe (Holzbraun)")
|
|
# Auswahl-Kontur (U6) statt Orange; FARBE KOMMT ZURÜCK beim Abwählen
|
|
# (Auswahl des Objekts 0 lässt 1 unberührt).
|
|
main.interaction.call("select_object", 1)
|
|
await process_frame
|
|
var out1: Variant = main.view_builder.object_markers[1].get_meta("outline", null)
|
|
_check(out1 is MeshInstance3D, "T3 Auswahl-Kontur am markierten Objekt (U6)")
|
|
|
|
|
|
func _t4_mehrfach() -> void:
|
|
main.interaction.call("clear_selection", true)
|
|
main.interaction.call("select_many", [0, 1])
|
|
await process_frame
|
|
main.history.clear()
|
|
insp.call("_on_farbe_gewaehlt", SvmapTint.PALETTE[0][1], "Wüstensand")
|
|
_check(SvmapTint.hat(_sel_obj(0)) and SvmapTint.hat(_sel_obj(1)),
|
|
"T4 Mehrfach: beide Objekte haben tint")
|
|
_check(SvmapTint.gleich(SvmapTint.von(_sel_obj(0)), SvmapTint.von(_sel_obj(1))),
|
|
"T4 Mehrfach: beide dieselbe Farbe")
|
|
_check(_undo_steps_to_clear() == 1, "T4 Mehrfach-Farbe = GENAU EIN Undo-Schritt")
|
|
# Undo stellt die vorherigen Farben wieder her (T3: Stahlgrau / Holzbraun).
|
|
_check(SvmapTint.gleich(SvmapTint.von(_sel_obj(0)), SvmapTint.PALETTE[3][1])
|
|
and SvmapTint.gleich(SvmapTint.von(_sel_obj(1)), SvmapTint.PALETTE[4][1]),
|
|
"T4 Undo stellt die vorherigen Farben wieder her")
|
|
|
|
|
|
func _t5_reset() -> void:
|
|
main.interaction.call("select_many", [0, 1])
|
|
await process_frame
|
|
insp.call("_on_farbe_gewaehlt", SvmapTint.PALETTE[2][1], "Tarn-Grün")
|
|
insp.call("_on_farbe_reset")
|
|
_check(not SvmapTint.hat(_sel_obj(0)) and not SvmapTint.hat(_sel_obj(1)),
|
|
"T5 Reset entfernt tint-Felder")
|
|
_check(str(insp.get("_farbe_status").text) == "Farbe: Pack-Farbe",
|
|
"T5 Status zeigt „Farbe: Pack-Farbe“")
|
|
# Farbe setzen und Zurücksetzen sind ZWEI getrennte Nutzeraktionen
|
|
# (= 2 Undo-Schritte); die FARBÄNDERUNG selbst ist genau 1 Schritt.
|
|
_check(_undo_steps_to_clear() == 2, "T5 Farbe + Reset = 2 getrennte Undo-Schritte")
|
|
# Reset ohne vorhandene Farbe = kein Leer-Schritt: Feld neu setzen,
|
|
# History leeren, Reset → ohne Änderung kein Commit.
|
|
main.interaction.call("select_object", 0)
|
|
await process_frame
|
|
insp.call("_on_farbe_gewaehlt", SvmapTint.PALETTE[5][1], "Dunkelbraun")
|
|
main.history.clear()
|
|
insp.call("_on_farbe_reset")
|
|
_check(not SvmapTint.hat(_sel_obj(0)), "T5 Reset entfernt das Dunkelbraun-Feld")
|
|
main.history.clear()
|
|
insp.call("_on_farbe_reset")
|
|
_check(not main.history.can_undo(), "T5 Reset ohne Farbe = KEIN Leer-Schritt")
|
|
main.interaction.call("clear_selection", true)
|
|
await process_frame
|
|
_check(_marker_mi(0).get_surface_override_material(0) == null,
|
|
"T5 keine Rest-Overrides nach Reset (Pack-Farbe sichtbar)")
|
|
|
|
|
|
func _t6_roundtrip() -> void:
|
|
# tint in doc → save → load: Feld überlebt bitgenau (gerundet).
|
|
insp.call("_on_farbe_gewaehlt", SvmapTint.PALETTE[1][1], "Oliv")
|
|
var pfad: String = ProjectSettings.globalize_path("user://tint_test.svmap")
|
|
var save_res: Dictionary = SvmapIO.save_to_file("user://tint_test.svmap", main.doc)
|
|
_check(save_res.ok, "T6 save_to_file ok (%s)" % str(save_res.get("errors", [])))
|
|
var geladen: Dictionary = SvmapIO.load_from_file("user://tint_test.svmap")
|
|
_check(bool(geladen.get("ok", false)), "T6 load_from_file ok (%s)" % str(geladen.get("errors", [])))
|
|
if bool(geladen.get("ok", false)):
|
|
var objs: Array = (geladen["data"] as Dictionary)["objects"]
|
|
_check(SvmapTint.gleich(SvmapTint.von(objs[0]), SvmapTint.von(_sel_obj(0))),
|
|
"T6 tint überlebt den Roundtrip unverändert")
|
|
# Alte Karte ohne Feld: Validierung ohne Fehler, View ohne Override.
|
|
var alte: Dictionary = main.doc.duplicate(true)
|
|
for obj in (alte["objects"] as Array):
|
|
SvmapTint.entferne(obj)
|
|
SvmapIO.sign_map(alte)
|
|
var fehler: PackedStringArray = SvmapIO.validate_map(alte)
|
|
_check(fehler.is_empty(), "T6 Karte ohne tint validiert ohne Fehler (%s)" % str(fehler))
|
|
if FileAccess.file_exists(pfad):
|
|
DirAccess.remove_absolute(pfad)
|
|
|
|
|
|
func _finish() -> void:
|
|
print("--------------------------------------------------")
|
|
print("CHECKS: %d FAILURES: %d" % [checks, failures])
|
|
quit(0 if failures == 0 else 1)
|