U2 t_e3c0310a: Szenen-Baum links mit Kategorie-Ebene + Zwei-Wege-Auswahl — scene_tree_panel: Objekte unter Kategorie-Knoten (LibraryBrowser.group_of + Label-Mapping), Spawn-Zonen Team A/B, Bau-Teile, Gruppen-Ebene (GroupOps; Klick auf Gruppe waehlt alle Mitglieder), Kopf mit Zaehlern (Objekte (N)), Auge-Praefix je Eintrag (nur Anzeige, keine Doc-Aenderung), leerer Zustand mit Klartext-Hinweis, Zwei-Wege-Kopplung ueber _obj_index_map (O(1) Markieren + scroll_to_item). Neue Suite tests/test_scene_tree.gd (24 checks: Baum fuellt sich, Klick->3D, 3D->Baum, Mehrfach, Gruppen-Klick, leerer Hinweis, UIScale). Renderprobe probe_u2_scene_tree_render.gd. main.gd NICHT angefasst.
This commit is contained in:
parent
3d7ba993d5
commit
60b09bfb74
5 changed files with 371 additions and 36 deletions
|
|
@ -6,6 +6,7 @@ extends "res://src/ui/panels/editor_panel.gd"
|
||||||
|
|
||||||
var content_tree: Tree
|
var content_tree: Tree
|
||||||
var _tree_select_lock := false # verhindert Rückkopplung Auswahl ↔ Baum
|
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:
|
static func spec() -> Dictionary:
|
||||||
|
|
@ -38,6 +39,20 @@ func _on_tree_selected() -> void:
|
||||||
if item.has_meta("gebaeude_index"):
|
if item.has_meta("gebaeude_index"):
|
||||||
_on_tree_selected_gebaeude(int(item.get_meta("gebaeude_index")))
|
_on_tree_selected_gebaeude(int(item.get_meta("gebaeude_index")))
|
||||||
return
|
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 indices: Array = []
|
||||||
var walk := content_tree.get_next_selected(null)
|
var walk := content_tree.get_next_selected(null)
|
||||||
while walk != null:
|
while walk != null:
|
||||||
|
|
@ -77,26 +92,24 @@ func mark_tree_selection_multi(indices: Array) -> void:
|
||||||
var wanted := {}
|
var wanted := {}
|
||||||
for i in indices:
|
for i in indices:
|
||||||
wanted[int(i)] = true
|
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
|
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)
|
_ensure_tree_visible(content_tree.get_root(), primary)
|
||||||
_tree_select_lock = false
|
_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:
|
func _ensure_tree_visible(item: TreeItem, idx: int) -> void:
|
||||||
if item == null:
|
if item == null:
|
||||||
return
|
return
|
||||||
|
|
@ -113,21 +126,47 @@ func _ensure_tree_visible(item: TreeItem, idx: int) -> void:
|
||||||
|
|
||||||
func refresh_tree() -> void:
|
func refresh_tree() -> void:
|
||||||
content_tree.clear()
|
content_tree.clear()
|
||||||
|
_obj_index_map.clear()
|
||||||
var root := content_tree.create_item()
|
var root := content_tree.create_item()
|
||||||
var objects: Array = main.doc.get("objects", [])
|
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)
|
var obj_item := content_tree.create_item(root)
|
||||||
obj_item.set_text(0, "Objekte (%d)" % objects.size())
|
obj_item.set_text(0, "Objekte (%d)" % objects.size())
|
||||||
|
var kat_map: Dictionary = {}
|
||||||
for i in objects.size():
|
for i in objects.size():
|
||||||
var obj = objects[i]
|
var obj = objects[i]
|
||||||
var item := content_tree.create_item(obj_item)
|
|
||||||
if obj is Dictionary:
|
if obj is Dictionary:
|
||||||
item.set_text(0, "%s #%d" % [main.object_label(str(obj.get("id", "?"))), i + 1])
|
var oid := str(obj.get("id", "?"))
|
||||||
item.set_meta("obj_index", i)
|
var kat := LibraryBrowser.group_of(oid)
|
||||||
else:
|
if not kat_map.has(kat):
|
||||||
item.set_text(0, "ungültig #%d" % (i + 1))
|
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", {})
|
# 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)
|
var spawn_item := content_tree.create_item(root)
|
||||||
spawn_item.set_text(0, "Spawn-Zonen")
|
spawn_item.set_text(0, "Spawn-Zonen")
|
||||||
for team_key in ["0", "1"]:
|
for team_key in ["0", "1"]:
|
||||||
|
|
@ -144,7 +183,6 @@ func refresh_tree() -> void:
|
||||||
str(p.get("id", "?")), float(p.get("pos", [0, 0, 0])[0]), float(p.get("pos", [0, 0, 0])[2])])
|
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.
|
# 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():
|
if not geb_list.is_empty():
|
||||||
var geb_item := content_tree.create_item(root)
|
var geb_item := content_tree.create_item(root)
|
||||||
geb_item.set_text(0, "Gebäude (%d Bauteile)" % geb_list.size())
|
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)
|
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_text(0, "%s #%d" % [BuildOps.kind_label(str(teil.get("id", "?"))), i + 1])
|
||||||
g_child.set_meta("gebaeude_index", i)
|
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()
|
||||||
|
|
|
||||||
70
tests/probe_u2_scene_tree_render.gd
Normal file
70
tests/probe_u2_scene_tree_render.gd
Normal file
|
|
@ -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)
|
||||||
1
tests/probe_u2_scene_tree_render.gd.uid
Normal file
1
tests/probe_u2_scene_tree_render.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
uid://b3p300p4xxp64
|
||||||
199
tests/test_scene_tree.gd
Normal file
199
tests/test_scene_tree.gd
Normal file
|
|
@ -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)
|
||||||
1
tests/test_scene_tree.gd.uid
Normal file
1
tests/test_scene_tree.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
uid://16mq6q3b053r
|
||||||
Loading…
Reference in a new issue