Diagnose-Skripte fuer VF/GF-Kettenzusammensetzung ergaenzen

Zeigen die Spec- bzw. Blockdefinitions-Struktur einer VF-/GF-Linienzugkette
in der Konsole, um sichtbar zu machen, dass ein einzelner VF_n-Wrapper
intern zwischen VF- und GF-Abschnitten wechseln kann (Grundlage fuer den
noch ausstehenden Separator-TYP-Fix).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Michael Stangl
2026-09-23 17:14:03 +02:00
co-authored by Claude Sonnet 5
parent 3f89f36e4a
commit c95a94c175
2 changed files with 241 additions and 0 deletions
+157
View File
@@ -0,0 +1,157 @@
"""
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 <spec.json> [<export_raw.json>]
<spec.json> z.B. tests/testdata/hundm05.json (vf_spec_export.py-Format)
<export_raw.json> 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 sepliste_zusammenfassung(wrapper_block):
sepliste = wrapper_block.get("sepliste", [])
if not sepliste:
return " (keine sepliste-XDATA am Wrapper - Altbestand oder Fehler)"
zeilen = []
for s in sepliste:
zeilen.append(
f" lfdnr={s['lfdnr']:2d} typ={s['typ']:3s} id={s.get('id','?'):6s} "
f"x={s['x']:10.2f} y={s['y']:10.2f}"
)
return "\n".join(zeilen)
def main():
if len(sys.argv) not in (2, 3):
print(f"Aufruf: python {sys.argv[0]} <spec.json> [<export_raw.json>]")
sys.exit(1)
spec = lade_json(sys.argv[1])
ketten = ketten_aus_spec(spec)
export_raw = None
wrapper_liste = []
if len(sys.argv) == 3:
export_raw = lade_json(sys.argv[2])
wrapper_liste = wrapper_aus_export_raw(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]))
if __name__ == "__main__":
main()
+84
View File
@@ -0,0 +1,84 @@
"""
Diagnose: Inhalt jedes VF_n/GF_n-Wrapper-Blocks in Baureihenfolge ausgeben.
Zeigt fuer jeden Wrapper (VF_n/GF_n) alle direkt in seiner Blockdefinition
enthaltenen Sub-INSERTs (rekursiv, wie ssg-collect-nested-inserts es tut) in
der Reihenfolge, in der sie in der Blockdefinition stehen - inkl. Blockname
und lokalem Einfuegepunkt. Damit sichtbar wird, welche Foerderer-/
Gefaellestrecke-Segmente (VF-Baustein-Bloecke vs. GF-Baustein-Bloecke)
tatsaechlich zwischen den verpackten Separator_SP-Symbolen liegen - ein
einzelner VF_n-Wrapper kann intern mehrfach zwischen Foerderer- und
Gefaellestrecke-Abschnitten wechseln, sein Blockname (VF_n) sagt darueber
nichts aus.
Aufruf: python dump_vf_wrapper_inhalt.py <dxf-oder-dwg>
"""
import sys
import ezdxf
# Muster grob nach Herkunft eingeteilt, rein zur Anzeige - keine
# Export-Klassifikation. GF-Baustein-Bloecke tragen "Staustrecke"/"Gefaelle"
# im Namen (siehe Gefaellestrecke.lsp/vf_linienzug.lsp GF-Zweige), reine
# VF-Bausteine div. Praefixe (siehe vf_standard.lsp/vf_etage.lsp/
# vf_linienzug.lsp horizontale/geneigte Foerderer-Bloecke).
def klassifiziere(block_name):
bn = block_name.upper()
if bn.startswith("STAUSTRECKE_SEPARATOR"):
return "SEP (Trennsteg)"
if "SEPARATOR_SP" in bn:
return "SEP (Symbol)"
if bn.startswith(("AS_ELEMENT", "ES_ELEMENT")):
return "AS/ES"
if "GEFAELLE" in bn or bn.startswith("STAUSTRECKE"):
return "GF-Baustein"
if bn.startswith(("VARIO_", "VF_", "UMLENK", "MOTOR", "ANTRIEB")):
return "VF-Baustein"
return ""
def dump_block(doc, block_name, tiefe=0):
block = doc.blocks.get(block_name)
if block is None:
print(" " * tiefe + f"[Blockdefinition {block_name} nicht gefunden]")
return
for e in block:
if e.dxftype() != "INSERT":
continue
name = e.dxf.name
x, y, z = e.dxf.insert
klasse = klassifiziere(name)
klasse_str = f" <- {klasse}" if klasse else ""
print(" " * tiefe + f"{name:40s} x={x:10.2f} y={y:10.2f}{klasse_str}")
# rekursiv absteigen (verschachtelte Wrapper/Zwischenbloecke)
if doc.blocks.get(name) is not None:
dump_block(doc, name, tiefe + 1)
def main():
if len(sys.argv) != 2:
print(f"Aufruf: python {sys.argv[0]} <dxf-oder-dwg>")
sys.exit(1)
pfad = sys.argv[1]
doc = ezdxf.readfile(pfad)
msp = doc.modelspace()
wrapper = [
e for e in msp.query("INSERT")
if e.dxf.name.startswith(("VF_", "GF_"))
]
wrapper.sort(key=lambda e: e.dxf.name)
if not wrapper:
print("Keine VF_n/GF_n-Wrapper-Bloecke im Modellraum gefunden.")
return
for e in wrapper:
print(f"\n=== {e.dxf.name} (handle={e.dxf.handle}) ===")
dump_block(doc, e.dxf.name)
if __name__ == "__main__":
main()