""" Diagnose: Inhalt jeder VF_n/GF_n-Linienzug-Kette aus Spec + sepliste gegenueberstellen. Zeigt fuer jede Kette (spec_id, z.B. VF_hundm05_LZ_01 = VF_1): - die "glied"-Folge aus der Spec (Horizontal-VF/Linie-GF/... in Baureihenfolge, siehe vf_spec.lsp/vsp-journal) - das ist die DOMAENEN-Wahrheit ueber VF- vs. GF-Abschnitte, - die tatsaechliche sepliste (AS/SEP/ES in Baureihenfolge) aus dem zugehoerigen export_raw.json/export.csv-Eintrag des gleichnamigen VF_n-Wrappers. Zweck: sichtbar machen, wie viele Separatoren tatsaechlich je Glied-Uebergang entstehen und ob ein einzelner VF_n-Wrapper (ein Blockname!) intern zwischen VF- und GF-Abschnitten wechselt - der Blockname allein (VF_n/GF_n) sagt darueber NICHTS aus, wie sich beim csv:wrapper-strecke-typ-Versuch gezeigt hat (alle Separatoren eines VF_n-Wrappers bekamen "VF", obwohl mehrere davon zwischen echten GF-Abschnitten liegen). Aufruf: python dump_vf_kette_inhalt.py [] z.B. tests/testdata/hundm05.json (vf_spec_export.py-Format) optional, z.B. results/export_raw.json - wenn angegeben, wird die sepliste des gleichnamigen VF_n/GF_n-Wrappers daneben gestellt (Zuordnung ueber Reihenfolge: die N-te Kette der Spec-Datei = der N-te VF_n/GF_n-Wrapper in export_raw.json, aufsteigend nach Blockname sortiert - die einzige verlaessliche Zuordnung ohne eigene ID-Bruecke, siehe Hinweis in der Ausgabe). """ import json import re import sys def lade_json(pfad): with open(pfad, encoding="utf-8") as f: return json.load(f) def ketten_aus_spec(spec_liste): """Gruppiert die flache Spec-Liste in Ketten (ein 'spec_id'-Kopf + seine 'glied'/'sub'-Eintraege bis zum naechsten Kopf). Objekte ohne 'spec_id' UND ohne 'glied'/'sub' (z.B. von Hand ergaenzte Anlagenteile mit 'function') werden uebersprungen - siehe vf_spec_export.py.""" ketten = [] aktuelle = None for obj in spec_liste: if "spec_id" in obj: aktuelle = {"kopf": obj, "eintraege": []} ketten.append(aktuelle) elif aktuelle is not None and ("glied" in obj or "sub" in obj): aktuelle["eintraege"].append(obj) return ketten def glied_typ(eintrag): """VF/GF-Familie eines 'glied'-Eintrags (siehe vfl-letzter-typ-familie, vf_linienzug.lsp:5979). 'sub'-Eintraege gehoeren zum zuletzt genannten 'glied' und werden nicht separat eingeordnet.""" name = eintrag.get("glied", "") if name.startswith("Linie-GF"): return "GF" if name.startswith(("Horizontal-VF", "Linie-VF")): return "VF" if name == "kettenende": return "ES" return name or "?" def kette_zusammenfassung(kette): kopf = kette["kopf"] zeilen = [] zeilen.append( f" Kopf: as_winkel={kopf.get('as_winkel')} as_seite={kopf.get('as_seite')} " f"start_hoehe={kopf.get('start_hoehe')}" ) for e in kette["eintraege"]: if "glied" in e: typ = glied_typ(e) sep_vor = e.get("sep_vor", e.get("vf_sep_vor", "")) sep_nach = e.get("sep_nach", e.get("vf_sep_nach", "")) zeilen.append( f" [{typ:2s}] glied={e['glied']:15s} nr={e.get('nr','')!s:3s} " f"dl={e.get('dl','')!s:8s} sep_vor={sep_vor!s:2s} sep_nach={sep_nach!s:2s}" ) elif e.get("sub") == "kettenende": zeilen.append( f" [ES] kettenende es_winkel={e.get('es_winkel')} " f"es_seite={e.get('es_seite')} dl={e.get('dl')}" ) else: zeilen.append(f" sub={e.get('sub')}") return "\n".join(zeilen) def wrapper_aus_export_raw(export_raw): wrapper = [ b for b in export_raw if re.match(r"^(VF|GF)_\d+$", b.get("block_name", "")) ] wrapper.sort(key=lambda b: (b["block_name"].split("_")[0], int(b["block_name"].split("_")[1]))) return wrapper def bauteile_nach_id(export_raw): """Lookup ID-Attribut -> Bauteil-Record. Die sepliste selbst traegt seit 2026-09-25 kein x/y mehr (die dortigen Koordinaten waren der rohe, teils gespiegelte/verschobene Gruppencode-10-Punkt des Bau-Primitivs, siehe export.lsp-Kommentar bei sepliste-teile) - die verlaessliche Position steht an der eigenen Export-Zeile des Bauteils (x/y/z dort kommen aus der echten Proxy-Kopie).""" out = {} for b in export_raw: bid = b.get("attribs", {}).get("ID") if bid: out[bid] = b return out def sepliste_zusammenfassung(wrapper_block, bauteile): sepliste = wrapper_block.get("sepliste", []) if not sepliste: return " (keine sepliste-XDATA am Wrapper - Altbestand oder Fehler)" zeilen = [] for s in sepliste: teil = bauteile.get(s.get("id")) if teil: pos = f"x={teil['x']:10.2f} y={teil['y']:10.2f}" typ = teil.get("strecke_typ") if typ: pos += f" strecke_typ={typ}" else: pos = "(kein Bauteil mit dieser ID gefunden)" zeilen.append( f" lfdnr={s['lfdnr']:2d} typ={s['typ']:3s} id={s.get('id','?'):6s} {pos}" ) return "\n".join(zeilen) def main(): if len(sys.argv) not in (2, 3): print(f"Aufruf: python {sys.argv[0]} []") sys.exit(1) spec = lade_json(sys.argv[1]) ketten = ketten_aus_spec(spec) export_raw = None wrapper_liste = [] bauteile = {} if len(sys.argv) == 3: export_raw = lade_json(sys.argv[2]) wrapper_liste = wrapper_aus_export_raw(export_raw) bauteile = bauteile_nach_id(export_raw) if len(wrapper_liste) != len(ketten): print( f"[WARNUNG] {len(ketten)} Ketten in der Spec, aber " f"{len(wrapper_liste)} VF_n/GF_n-Wrapper in export_raw.json - " f"Zuordnung nach Reihenfolge ist damit UNSICHER.\n" ) for i, kette in enumerate(ketten): kopf = kette["kopf"] print(f"\n{'=' * 70}") print(f"Kette {i + 1}: {kopf['spec_id']}" + (f" <-> {wrapper_liste[i]['block_name']} " f"(handle={wrapper_liste[i].get('handle')})" if i < len(wrapper_liste) else " <-> (kein Wrapper zugeordnet)")) print(f"{'=' * 70}") print("--- Spec (Domaenen-Wahrheit VF/GF) ---") print(kette_zusammenfassung(kette)) if i < len(wrapper_liste): print("--- sepliste (tatsaechlich exportierte Separator-Reihenfolge) ---") print(sepliste_zusammenfassung(wrapper_liste[i], bauteile)) if __name__ == "__main__": main()