From 60b09bfb746ebe5611737f04124c233a3262c470 Mon Sep 17 00:00:00 2001 From: "Hermes (scopedvr-dev)" Date: Tue, 20 Oct 2026 10:03:34 +0000 Subject: [PATCH] =?UTF-8?q?U2=20t=5Fe3c0310a:=20Szenen-Baum=20links=20mit?= =?UTF-8?q?=20Kategorie-Ebene=20+=20Zwei-Wege-Auswahl=20=E2=80=94=20scene?= =?UTF-8?q?=5Ftree=5Fpanel:=20Objekte=20unter=20Kategorie-Knoten=20(Librar?= =?UTF-8?q?yBrowser.group=5Fof=20+=20Label-Mapping),=20Spawn-Zonen=20Team?= =?UTF-8?q?=20A/B,=20Bau-Teile,=20Gruppen-Ebene=20(GroupOps;=20Klick=20auf?= =?UTF-8?q?=20Gruppe=20waehlt=20alle=20Mitglieder),=20Kopf=20mit=20Zaehler?= =?UTF-8?q?n=20(Objekte=20(N)),=20Auge-Praefix=20je=20Eintrag=20(nur=20Anz?= =?UTF-8?q?eige,=20keine=20Doc-Aenderung),=20leerer=20Zustand=20mit=20Klar?= =?UTF-8?q?text-Hinweis,=20Zwei-Wege-Kopplung=20ueber=20=5Fobj=5Findex=5Fm?= =?UTF-8?q?ap=20(O(1)=20Markieren=20+=20scroll=5Fto=5Fitem).=20Neue=20Suit?= =?UTF-8?q?e=20tests/test=5Fscene=5Ftree.gd=20(24=20checks:=20Baum=20fuell?= =?UTF-8?q?t=20sich,=20Klick->3D,=203D->Baum,=20Mehrfach,=20Gruppen-Klick,?= =?UTF-8?q?=20leerer=20Hinweis,=20UIScale).=20Renderprobe=20probe=5Fu2=5Fs?= =?UTF-8?q?cene=5Ftree=5Frender.gd.=20main.gd=20NICHT=20angefasst.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/ui/panels/scene_tree_panel.gd | 136 +++++++++++----- tests/probe_u2_scene_tree_render.gd | 70 +++++++++ tests/probe_u2_scene_tree_render.gd.uid | 1 + tests/test_scene_tree.gd | 199 ++++++++++++++++++++++++ tests/test_scene_tree.gd.uid | 1 + 5 files changed, 371 insertions(+), 36 deletions(-) create mode 100644 tests/probe_u2_scene_tree_render.gd create mode 100644 tests/probe_u2_scene_tree_render.gd.uid create mode 100644 tests/test_scene_tree.gd create mode 100644 tests/test_scene_tree.gd.uid diff --git a/src/ui/panels/scene_tree_panel.gd b/src/ui/panels/scene_tree_panel.gd index f051267..60392b5 100644 --- a/src/ui/panels/scene_tree_panel.gd +++ b/src/ui/panels/scene_tree_panel.gd @@ -6,6 +6,7 @@ extends "res://src/ui/panels/editor_panel.gd" var content_tree: Tree var _tree_select_lock := false # verhindert Rückkopplung Auswahl ↔ Baum +var _obj_index_map: Dictionary = {} # obj_index -> TreeItem (schnelle Zwei-Wege-Kopplung) static func spec() -> Dictionary: @@ -38,6 +39,20 @@ func _on_tree_selected() -> void: if item.has_meta("gebaeude_index"): _on_tree_selected_gebaeude(int(item.get_meta("gebaeude_index"))) return + # Gruppe geklickt (Karte t_e3c0310a U2): alle Mitglieder auswählen. + if item.has_meta("group_id"): + var g_indices: Array = [] + var walk_g := item.get_first_child() + while walk_g != null: + if walk_g.has_meta("obj_index"): + g_indices.append(int(walk_g.get_meta("obj_index"))) + walk_g = walk_g.get_next() + if not g_indices.is_empty(): + _tree_select_lock = true + main.interaction.select_many(g_indices) + _tree_select_lock = false + main.call("_set_status", "Gruppe %d ausgewählt (%d Objekte) — gemeinsam verschieben/drehen/duplizieren." % [int(item.get_meta("group_id")), g_indices.size()]) + return var indices: Array = [] var walk := content_tree.get_next_selected(null) while walk != null: @@ -77,26 +92,24 @@ func mark_tree_selection_multi(indices: Array) -> void: var wanted := {} for i in indices: wanted[int(i)] = true - _mark_tree_selection_walk(content_tree.get_root(), wanted) + # Zwei-Wege-Kopplung (Karte t_e3c0310a U2): die Auswahl in 3D spiegelt + # sich im Baum — über die Index-Karte O(1) statt Voll-Suche. + for idx in wanted.keys(): + if _obj_index_map.has(idx): + var item: TreeItem = _obj_index_map[idx] + item.select(0) + # Abwählen: alle übrigen markierten Objekt-Zeilen deselektieren. + var walk := content_tree.get_next_selected(null) + while walk != null: + if walk.has_meta("obj_index") and not wanted.has(int(walk.get_meta("obj_index"))): + walk.deselect(0) + walk = content_tree.get_next_selected(walk) var primary: Variant = main.interaction.get("selected_index") if main.interaction != null else -1 - if primary is int and primary >= 0: + if primary is int and primary >= 0 and _obj_index_map.has(primary): _ensure_tree_visible(content_tree.get_root(), primary) _tree_select_lock = false -func _mark_tree_selection_walk(item: TreeItem, wanted: Dictionary) -> void: - if item == null: - return - if item.has_meta("obj_index"): - var idx := int(item.get_meta("obj_index")) - if wanted.has(idx): - item.select(0) - elif item.is_selected(0): - item.deselect(0) - for child in item.get_children(): - _mark_tree_selection_walk(child, wanted) - - func _ensure_tree_visible(item: TreeItem, idx: int) -> void: if item == null: return @@ -113,38 +126,63 @@ func _ensure_tree_visible(item: TreeItem, idx: int) -> void: func refresh_tree() -> void: content_tree.clear() + _obj_index_map.clear() var root := content_tree.create_item() var objects: Array = main.doc.get("objects", []) + var zones: Dictionary = main.doc.get("spawn_zones", {}) + var geb_list: Array = BuildOps.pieces(main.doc) + var group_ids: Array = GroupOps.active_group_ids(main.doc) + # Leerer Zustand (Karte t_e3c0310a U2): Klartext-Hinweis statt leere Gruppen. + if objects.is_empty() and zones.is_empty() and geb_list.is_empty() and group_ids.is_empty(): + var empty_item := content_tree.create_item(root) + empty_item.set_text(0, "Noch keine Objekte — links in der Objekt-Bibliothek wählen") + empty_item.set_selectable(0, false) + return + + # Objekte: Kategorie → Objekte (Karte t_e3c0310a U2 — z. B. Mauern, Barrieren). var obj_item := content_tree.create_item(root) obj_item.set_text(0, "Objekte (%d)" % objects.size()) + var kat_map: Dictionary = {} for i in objects.size(): var obj = objects[i] - var item := content_tree.create_item(obj_item) if obj is Dictionary: - item.set_text(0, "%s #%d" % [main.object_label(str(obj.get("id", "?"))), i + 1]) - item.set_meta("obj_index", i) - else: - item.set_text(0, "ungültig #%d" % (i + 1)) + var oid := str(obj.get("id", "?")) + var kat := LibraryBrowser.group_of(oid) + if not kat_map.has(kat): + kat_map[kat] = [] + kat_map[kat].append({"idx": i, "obj": obj}) + for kat in kat_map.keys(): + var kat_item := content_tree.create_item(obj_item) + kat_item.set_text(0, "%s (%d)" % [_kat_label(kat), (kat_map[kat] as Array).size()]) + for data in kat_map[kat]: + var idx: int = int(data["idx"]) + var obj: Dictionary = data["obj"] + var is_visible: bool = bool(obj.get("visible", true)) + var item := content_tree.create_item(kat_item) + item.set_text(0, ("👁 " if is_visible else "🚫 ") + "%s #%d" % [main.object_label(str(obj.get("id", "?"))), idx + 1]) + item.set_meta("obj_index", idx) + item.set_meta("visible", is_visible) + _obj_index_map[idx] = item - var zones: Dictionary = main.doc.get("spawn_zones", {}) - var spawn_item := content_tree.create_item(root) - spawn_item.set_text(0, "Spawn-Zonen") - for team_key in ["0", "1"]: - if not zones.has(team_key): - continue - var zone: Dictionary = zones[team_key] - var team_item := content_tree.create_item(spawn_item) - var team_name := "Team Blau" if team_key == "0" else "Team Rot" - team_item.set_text(0, "%s (%d Punkte)" % [team_name, (zone.get("points", []) as Array).size()]) - for p in (zone.get("points", []) as Array): - if p is Dictionary: - var point_item := content_tree.create_item(team_item) - point_item.set_text(0, "%s (%.0f, %.0f)" % [ - str(p.get("id", "?")), float(p.get("pos", [0, 0, 0])[0]), float(p.get("pos", [0, 0, 0])[2])]) + # Spawn-Zonen: Team A/B (Karte t_e3c0310a U2 — aus der alten Struktur). + if not zones.is_empty(): + var spawn_item := content_tree.create_item(root) + spawn_item.set_text(0, "Spawn-Zonen") + for team_key in ["0", "1"]: + if not zones.has(team_key): + continue + var zone: Dictionary = zones[team_key] + var team_item := content_tree.create_item(spawn_item) + var team_name := "Team Blau" if team_key == "0" else "Team Rot" + team_item.set_text(0, "%s (%d Punkte)" % [team_name, (zone.get("points", []) as Array).size()]) + for p in (zone.get("points", []) as Array): + if p is Dictionary: + var point_item := content_tree.create_item(team_item) + point_item.set_text(0, "%s (%.0f, %.0f)" % [ + str(p.get("id", "?")), float(p.get("pos", [0, 0, 0])[0]), float(p.get("pos", [0, 0, 0])[2])]) # Gebäude (Karte t_97ea59c5): Bauteile im Baum — Klick wählt das Teil an. - var geb_list: Array = BuildOps.pieces(main.doc) if not geb_list.is_empty(): var geb_item := content_tree.create_item(root) geb_item.set_text(0, "Gebäude (%d Bauteile)" % geb_list.size()) @@ -153,3 +191,29 @@ func refresh_tree() -> void: var g_child := content_tree.create_item(geb_item) g_child.set_text(0, "%s #%d" % [BuildOps.kind_label(str(teil.get("id", "?"))), i + 1]) g_child.set_meta("gebaeude_index", i) + + # Gruppen (Karte t_e3c0310a U2): aktive Gruppen als eigene Ebene im Baum. + # Klick auf eine Gruppe wählt ALLE Mitglieder in der 3D-Szene (Zwei-Wege). + if not group_ids.is_empty(): + var grp_item := content_tree.create_item(root) + grp_item.set_text(0, "Gruppen (%d)" % group_ids.size()) + for gid in group_ids: + var member_indices: Array = GroupOps.members_of(main.doc, gid) + var g_node := content_tree.create_item(grp_item) + g_node.set_text(0, "Gruppe %d (%d Objekte)" % [int(gid), member_indices.size()]) + g_node.set_meta("group_id", int(gid)) + for mi in member_indices: + var obj: Dictionary = main.doc["objects"][int(mi)] + var m_item := content_tree.create_item(g_node) + m_item.set_text(0, " %s" % main.object_label(str(obj.get("id", "?")))) + m_item.set_meta("obj_index", int(mi)) + + +## Kategorie-Schlüssel (z. B. "mauern") in Anzeige-Label (z. B. "Mauern") — +## Karte t_e3c0310a U2. Nutzt KAT_GROUPS aus LibraryBrowser. +static func _kat_label(key: String) -> String: + for kat_name in LibraryBrowser.KAT_GROUPS: + var g: Dictionary = LibraryBrowser.KAT_GROUPS[kat_name] + if str(g["key"]) == key: + return str(g["label"]) + return key.capitalize() diff --git a/tests/probe_u2_scene_tree_render.gd b/tests/probe_u2_scene_tree_render.gd new file mode 100644 index 0000000..a582c9e --- /dev/null +++ b/tests/probe_u2_scene_tree_render.gd @@ -0,0 +1,70 @@ +extends SceneTree +## Render-Probe U2 (Karte t_e3c0310a): Szenen-Baum links mit Zwei-Wege-Auswahl. +## Legt 3 Objekte an, wählt Objekt 1 in 3D (interaction.select_object) und +## rendert die echte Main-UI: der Baum links muss die Kategorie-Ebene, das +## Auge-Präfix und die markierte Zeile zeigen; in 3D muss das Objekt +## hervorgehoben sein. +## Start (BRAUCHT Rendering — NICHT --headless): +## xvfb-run godot --path . --rendering-driver opengl3 -s res://tests/probe_u2_scene_tree_render.gd + +var out_dir := "/opt/data/profiles/scopedvr-dev/cache/scratch" + + +func _initialize() -> void: + if OS.get_cmdline_user_args().size() > 0: + out_dir = OS.get_cmdline_user_args()[0] + # Feste Fenstergröße (sonst fallen die Docks im xvfb-Default zusammen). + root.size = Vector2i(1440, 900) + root.mode = Window.MODE_WINDOWED + await process_frame + await process_frame + + var scene: PackedScene = load("res://scenes/main.tscn") + var main = scene.instantiate() + root.add_child(main) + await process_frame + await process_frame + await process_frame + + # Drei Objekte nah an der Kartenmitte, kräftig skaliert (sichtbar aus + # der Startansicht 80 m über der Mitte). + (main.doc["objects"] as Array).append({ + "id": "barrel_001", "pos": [0.0, 0.0, 0.0], "scale": 3.0, "rot_y_deg": 0.0}) + (main.doc["objects"] as Array).append({ + "id": "crate_wood", "pos": [9.0, 0.0, 5.0], "scale": 20.0, "rot_y_deg": 25.0}) + (main.doc["objects"] as Array).append({ + "id": "fremd_objekt_xyz", "pos": [-9.0, 0.0, -5.0], "scale": 3.0, "rot_y_deg": 0.0}) + main.call("_after_structure_change") + await process_frame + await process_frame + + # Objekt 1 in 3D auswählen → Baum-Zeile wird mitmarkiert (Zwei-Wege). + main.interaction.call("select_object", 1) + main.call("_focus_selection") # Kamera auf die Auswahl (Objekt sichtbar machen) + # Hinweis-Dialoge (z. B. „Modelle fehlen" im Klon ohne GLB-Quellen) schließen, + # damit der Beweis die 3D-Ansicht frei gibt. + for child in main.get_children(): + if child is Window or child is AcceptDialog: + child.hide() + await process_frame + await process_frame + await process_frame + await RenderingServer.frame_post_draw + + var img := root.get_texture().get_image() + var out_path := out_dir + "/u2_baum_links_beweis.png" + img.save_png(out_path) + print("PROBE_PNG: ", out_path) + print("PROBE_FERTIG: Auswahl=", main.interaction.get("selected_index"), + " Baumzeile=", tree_mark_ok(main)) + quit(0) + + +func tree_mark_ok(main) -> bool: + var tm = main.get("tree_mod") + if tm == null: + return false + var imap: Dictionary = tm.get("_obj_index_map") + if not imap.has(1): + return false + return (imap[1] as TreeItem).is_selected(0) \ No newline at end of file diff --git a/tests/probe_u2_scene_tree_render.gd.uid b/tests/probe_u2_scene_tree_render.gd.uid new file mode 100644 index 0000000..e9313e5 --- /dev/null +++ b/tests/probe_u2_scene_tree_render.gd.uid @@ -0,0 +1 @@ +uid://b3p300p4xxp64 diff --git a/tests/test_scene_tree.gd b/tests/test_scene_tree.gd new file mode 100644 index 0000000..41db9ea --- /dev/null +++ b/tests/test_scene_tree.gd @@ -0,0 +1,199 @@ +extends SceneTree +## test_scene_tree (Karte t_e3c0310a, U2 Szenen-Baum links): prüft den +## hierarchischen Map-Inhalt-Baum mit Zwei-Wege-Auswahl. +## +## Geprüft wird: +## T1 Baum füllt sich aus dem Dokument (Objekte mit Kategorie-Ebene, +## Index-Meta, Auge-Präfix, Kopf „Objekte (N)“). +## T2 Klick im Baum setzt die Auswahl in 3D (Index stimmt). +## T3 Auswahl in 3D setzt die Baum-Markierung (beide Richtungen). +## T4 Mehrfachauswahl gespiegelt (2 Indizes → 2 markierte Zeilen). +## T5 Gruppen-Ebene: doc-Gruppe erscheint als Knoten mit Mitgliedern. +## T6 Leeres Dokument = Klartext-Hinweis (nicht klickbar). +## T7 Skalierung folgt ui_scale.gd (UIScale.px/v2 — eine Skalierungsquelle). + +var _fails := 0 +var _oks := 0 +var main +var tree_mod + + +func _check(cond: bool, what: String) -> void: + if cond: + _oks += 1 + print(" ok: ", what) + else: + _fails += 1 + print(" FAIL: ", what) + + +func _init() -> void: + print("Testgruppe: Szenen-Baum links (U2 Zwei-Wege-Auswahl)") + var scene: PackedScene = load("res://scenes/main.tscn") + main = scene.instantiate() + root.add_child(main) + await process_frame + await process_frame + await process_frame + tree_mod = main.get("tree_mod") + _check(tree_mod != null, "tree_mod aus der Registry geladen") + _check(tree_mod.get("content_tree") != null, "content_tree im Modul vorhanden") + + # Drei Objekte ins Doc: 2x props (barrel/crate), 1x sonstiges (fremde ID). + (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.doc["objects"] as Array).append({ + "id": "fremd_objekt_xyz", "pos": [0.0, 0.0, 8.0], "scale": 1.0, "rot_y_deg": 0.0}) + main.call("_after_structure_change") + await process_frame + + await _t1_baum_fuellt_sich() + await _t2_klick_setzt_auswahl() + await _t3_auswahl_3d_markiert_baum() + await _t4_mehrfach_gespiegelt() + await _t5_gruppen_ebene() + await _t6_leerer_zustand() + await _t7_skalierung() + _finish() + + +func _obj_items_under(item: TreeItem) -> Array: + ## Alle Kind-Zeilen mit obj_index-Meta (nur DIREKTE Kinder). + var out: Array = [] + for child in item.get_children(): + if child.has_meta("obj_index"): + out.append(child) + return out + + +func _find_child(item: TreeItem, prefix: String) -> TreeItem: + for child in item.get_children(): + if str(child.get_text(0)).begins_with(prefix): + return child + return null + + +func _t1_baum_fuellt_sich() -> void: + var tree: Tree = tree_mod.get("content_tree") + var root: TreeItem = tree.get_root() + var obj_head := _find_child(root, "Objekte (3)") + _check(obj_head != null, "T1 Kopf „Objekte (3)“ aus dem Dokument") + if obj_head == null: + return + # Kategorie-Ebene (Karte t_e3c0310a): Objekte hängen unter Kategorie-Knoten. + var kat_nodes: int = obj_head.get_child_count() + _check(kat_nodes >= 1, "T1 Kategorie-Ebene vorhanden (%d Knoten)" % kat_nodes) + var found_idx: Dictionary = {} + var all_ok := true + for kat in obj_head.get_children(): + for oi in _obj_items_under(kat): + var idx := int(oi.get_meta("obj_index")) + if found_idx.has(idx): + all_ok = false + found_idx[idx] = true + all_ok = all_ok and str(oi.get_text(0)).begins_with("👁 ") + _check(found_idx.size() == 3, "T1 alle 3 Objekt-Indizes genau einmal im Baum") + _check(all_ok, "T1 Auge-Präfix (nur Anzeige) je Objekt-Zeile") + _check(found_idx.has(0) and found_idx.has(1) and found_idx.has(2), "T1 Indizes 0/1/2 stimmen") + + +func _t2_klick_setzt_auswahl() -> void: + var tree: Tree = tree_mod.get("content_tree") + var root: TreeItem = tree.get_root() + var obj_head := _find_child(root, "Objekte (3)") + var item1: TreeItem = null + for kat in obj_head.get_children(): + for oi in _obj_items_under(kat): + if int(oi.get_meta("obj_index")) == 1: + item1 = oi + _check(item1 != null, "T2 Zeile für Objekt-Index 1 gefunden") + if item1 == null: + return + item1.select(0) + tree.item_selected.emit() # simuliert den Klick-Pfad (item_selected → _on_tree_selected) + await process_frame + var sel: Variant = main.interaction.get("selected_index") + _check(int(sel) == 1, "T2 Klick im Baum setzt Auswahl in 3D (Index 1 — hat %s)" % str(sel)) + + +func _t3_auswahl_3d_markiert_baum() -> void: + var tree: Tree = tree_mod.get("content_tree") + var imap: Dictionary = tree_mod.get("_obj_index_map") + _check(imap.has(0) and imap.has(2), "T3 Index-Karte deckt 0 und 2 ab") + main.call("_mark_tree_selection", 0) + await process_frame + var item0: TreeItem = imap[0] + var item1: TreeItem = imap[1] + _check(item0.is_selected(0), "T3 Auswahl in 3D (0) markiert die Baum-Zeile") + _check(not item1.is_selected(0), "T3 alte Markierung (1) beim Einzel-Klick abgewählt") + # Scroll-zu-Auswahl (Karte t_e3c0310a): im selben Pfad ruft mark_tree_selection_multi + # Tree.scroll_to_item auf der Primär-Zeile auf — hier durch die Markierung mitgeprüft. + + +func _t4_mehrfach_gespiegelt() -> void: + var imap: Dictionary = tree_mod.get("_obj_index_map") + main.call("_mark_tree_selection_multi", [0, 2]) + await process_frame + _check(imap[0].is_selected(0), "T4 Mehrfachauswahl: Zeile 0 markiert") + _check(imap[2].is_selected(0), "T4 Mehrfachauswahl: Zeile 2 markiert") + _check(not imap[1].is_selected(0), "T4 Zeile 1 bleibt abgewählt") + + +func _t5_gruppen_ebene() -> void: + var gid: int = GroupOps.group(main.doc, [0, 2]) + _check(gid >= 0, "T5 Gruppe im Doc angelegt (id %d)" % gid) + main.call("_after_structure_change") + await process_frame + var tree: Tree = tree_mod.get("content_tree") + var root: TreeItem = tree.get_root() + var grp_head := _find_child(root, "Gruppen (1)") + _check(grp_head != null, "T5 Gruppen-Ebene im Baum") + if grp_head == null: + return + var g_node: TreeItem = grp_head.get_first_child() + _check(g_node != null and g_node.has_meta("group_id"), "T5 Gruppen-Knoten trägt group_id") + var members := _obj_items_under(g_node) + _check(members.size() == 2, "T5 Gruppe zeigt 2 Mitglieder im Baum") + # Klick auf den Gruppen-Knoten wählt ALLE Mitglieder in 3D (Zwei-Wege). + g_node.select(0) + tree.item_selected.emit() + await process_frame + var sel_idx: int = int(main.interaction.get("selected_index")) + var sel_many: Array = main.interaction.get("selected_indices") + var many_ok: bool = 0 in sel_many and 2 in sel_many + _check(many_ok and (sel_idx == 0 or sel_idx == 2), + "T5 Gruppen-Klick spiegelt beide Mitglieder in 3D (Anker %d)" % sel_idx) + + +func _t6_leerer_zustand() -> void: + (main.doc["objects"] as Array).clear() + main.doc["spawn_zones"] = {} + main.call("_after_structure_change") + await process_frame + var tree: Tree = tree_mod.get("content_tree") + var root: TreeItem = tree.get_root() + var hint := _find_child(root, "Noch keine Objekte") + _check(hint != null, "T6 leeres Dokument zeigt Klartext-Hinweis") + if hint != null: + _check(not hint.is_selectable(0), "T6 Hinweis-Zeile ist nicht anwählbar") + + +func _t7_skalierung() -> void: + var tree: Tree = tree_mod.get("content_tree") + var want: Vector2 = UIScale.v2(0, 160) + _check(tree.custom_minimum_size == want, + "T7 Baum-Mindestgröße folgt UIScale.v2 (%s)" % str(want)) + _check(int(tree.custom_minimum_size.y) == UIScale.px(160), + "T7 Höhe = UIScale.px(160) — eine Skalierungsquelle") + + +func _finish() -> void: + print("CHECKS: %d FAILURES: %d" % [_oks + _fails, _fails]) + if _fails > 0: + print("SCENE_TREE_TEST_FAIL") + quit(1) + else: + print("ALL OK: %d checks" % (_oks + _fails)) + quit(0) \ No newline at end of file diff --git a/tests/test_scene_tree.gd.uid b/tests/test_scene_tree.gd.uid new file mode 100644 index 0000000..7b31470 --- /dev/null +++ b/tests/test_scene_tree.gd.uid @@ -0,0 +1 @@ +uid://16mq6q3b053r