scopedvr-map-core/svmap/group_state.gd
2026-11-01 19:31:33 +01:00

111 lines
4.3 KiB
GDScript

extends RefCounted
class_name GroupState
## Gruppen-ZUSTAND über das Objekt-Feld "group" hinaus (Karte t_a4bf9b8c, U3 —
## Gruppen im Szenen-Baum). GroupOps (core/editor/group_ops.gd) besitzt die
## Zugehörigkeit (Optionales Objekt-Feld "group", int ≥ 0) — diese Datei ergänzt
## NUR ADDITIV, was der Baum additionally braucht:
##
## 1. GRUPPENNAMEN: doc["group_names"] = { "<gid>": "Lager", ... } — optionaler
## Ober-Block. Alte Maps ohne diesen Key laden unverändert (V4 prüft nur
## Pflichtblöcke, unbekannte Ober-Keys wandern 1:1 durch _canonicalize in
## SvmapIO und in die sha256-Prüfsumme — gleiche Begründung wie beim
## "group"-Feld in GroupOps). Eintrag fehlt ⇒ Anzeige-Fallback "Gruppe N".
## 2. DRAG-MITGLIDSCHAFT: Objekt in Gruppe ziehen (assign) / heraus ziehen
## (clear für EINZELNE Indizes — GroupOps.ungroup löst dagegen die GANZE
## Gruppe, was beim Drag-out nur eines Objekts nicht gewollt ist).
##
## Bewusst KEINE eigene Undo-Logik: der Aufrufer macht snapshot/commit über
## ActionHistory (["objects"] bzw. ["group_names"]) — jede Aktion bleibt EIN
## Undo-Schritt.
const NAMES_KEY := "group_names"
## Rohe Namen-Tabelle (leer, wenn der Key fehlt — alte Maps).
static func names_dict(doc: Dictionary) -> Dictionary:
var raw: Variant = doc.get(NAMES_KEY, {})
return raw if raw is Dictionary else {}
## Gespeicherter Name einer Gruppe ("" = keiner gesetzt).
static func group_name(doc: Dictionary, gid: int) -> String:
var names := names_dict(doc)
var value: Variant = names.get(str(gid), "")
return str(value) if value is String else ""
## Anzeige-Name im Baum: gespeicherter Name oder Fallback "Gruppe N"
## (N = gid + 1 — gleiche Zählung wie in interaction.gd/selection_panel.gd).
static func display_name(doc: Dictionary, gid: int) -> String:
var name := group_name(doc, gid)
return name if not name.is_empty() else ("Gruppe %d" % (gid + 1))
## Namen setzen ("" oder nur Leerzeichen = Eintrag entfernen → Fallback).
## Rückgabe: true, wenn sich am Dokument etwas geändert hat.
static func set_group_name(doc: Dictionary, gid: int, new_name: String) -> bool:
var clean := new_name.strip_edges()
var names := names_dict(doc)
if clean.is_empty():
if not names.has(str(gid)):
return false
(doc[NAMES_KEY] as Dictionary).erase(str(gid))
return true
if names.get(str(gid), "") == clean:
return false
if not doc.has(NAMES_KEY) or not (doc[NAMES_KEY] is Dictionary):
doc[NAMES_KEY] = {}
(doc[NAMES_KEY] as Dictionary)[str(gid)] = clean
return true
## Namen-Einträge ohne Mitglieder entfernen (Aufräumen nach Löschen/Lösen —
## vom Aufrufer IM SELBEN Undo-Schritt mit-committet).
static func prune_orphans(doc: Dictionary) -> int:
var active := {}
for obj in (doc.get("objects", []) as Array):
var g := GroupOps.group_id(obj)
if g != GroupOps.UNGROUPED:
active[g] = true
var names := names_dict(doc)
var removed := 0
for key in names.keys():
if not active.has(int(key)):
names.erase(key)
removed += 1
if removed > 0:
if (names as Dictionary).is_empty():
doc.erase(NAMES_KEY) # sauberes Format: leerer Block fliegt raus
elif doc.has(NAMES_KEY):
doc[NAMES_KEY] = names
return removed
## Drag IN eine Gruppe: Objekt-Feld "group" auf die Ziel-ID setzen (Objekte,
## die schon diese ID tragen, zählen nicht als Änderung). Nur die GEBENEN
## Indizes — die restliche Gruppe bleibt unberührt. Rückgabe: geänderte Anzahl.
static func assign_group(doc: Dictionary, indices: Array, gid: int) -> int:
if gid < 0:
return 0
var objects: Array = doc.get("objects", [])
var changed := 0
for i in indices:
if i is int and i >= 0 and i < objects.size() and (objects[i] is Dictionary):
if GroupOps.group_id(objects[i]) != gid:
objects[i]["group"] = gid
changed += 1
return changed
## Drag AUS der Gruppe: "group"-Feld NUR der gegebenen Indizes entfernen
## (die Gruppe selbst bleibt mit ihren übrigen Mitgliedern bestehen).
## Rückgabe: geänderte Anzahl.
static func clear_group_fields(doc: Dictionary, indices: Array) -> int:
var objects: Array = doc.get("objects", [])
var changed := 0
for i in indices:
if i is int and i >= 0 and i < objects.size() and (objects[i] is Dictionary):
if GroupOps.group_id(objects[i]) != GroupOps.UNGROUPED:
(objects[i] as Dictionary).erase("group")
changed += 1
return changed