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); Auswahl-Orange gewinnt weiter ## (material_override), nach dem Abwählen ist die Farbe wieder sichtbar. ## 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) _check(mi.material_override != null, "T3 markiertes Objekt bleibt orange (material_override an)") # 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)") # Orange gewinnt weiter, 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 _check(_marker_mi(1).material_override != null, "T3 Auswahl-Orange gewinnt weiter") 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)