[CHANGE] JSON-Testdaten-Feldnamen aus dxf_anlage_export.py in lib/testdata_schema.py zentralisiert

dxf_anlage_export.py und dxf_scan_components.py fuehrten dieselben Feldnamen (abstand, deltaL_mm, as_seite, ...) aus doc/json-input-lisp.md unabhaengig als lose String-Literale - ein Tippfehler oder eine Schema-Aenderung in nur einer Datei waere nicht aufgefallen. Neues Modul lib/testdata_schema.py buendelt die Feldnamen/festen Werte der dokumentierten Schemas (Kreisel, Einzelblock, VF Standard/Etage, Gefaellestrecke Modus 1) als benannte Konstanten; dxf_anlage_export.py nutzt sie jetzt durchgaengig.

dxf_scan_components.py bewusst NICHT mitumgebaut: deutlich grösser/komplexer, mit zusaetzlichen Sonderfaellen (z.B. "startpunkt"/"gerade"-Form), ein vollstaendiger Umbau haette den Rahmen dieser Aenderung gesprengt und mehr Risiko als Nutzen gehabt.

Verhaltensgleich: alle 9 Tests in tests/test_dxf_anlage_export.py laufen weiterhin gruen.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-10-09 10:43:09 +02:00
parent 1e7e2c6349
commit aaf47ab3c1
3 changed files with 131 additions and 43 deletions
+44 -43
View File
@@ -63,6 +63,7 @@ import sys
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
from vf_journal_export import TRENNER, gruppen # noqa: E402
from dxf_scan_components import arrays_einzeilig, kreisel_anschluss # noqa: E402
import testdata_schema as sch # noqa: E402
PREFIX = "[dxf_anlage_export]"
RUND = 2
@@ -204,17 +205,17 @@ def kreisel_eintrag(e, block_inhalt, projekt):
quelle = "Blockdefinition (AN8 -> SP8)"
nr = int(_float(a.get("NUMMER"))) or _nummer(e["name"])
return collections.OrderedDict([
("id", "Kreisel_%s_%02d" % (projekt, nr)),
("function", "insert"),
("x", _r(e["p"][0])), ("y", _r(e["p"][1])), ("z", _r(e["p"][2])),
("abstand", _r(abstand)),
("rotation", _r(e["rot"])),
("typ", a.get("KREISELART") or "STANDARD"),
(sch.F_ID, "Kreisel_%s_%02d" % (projekt, nr)),
(sch.F_FUNCTION, sch.FUNC_INSERT),
(sch.F_X, _r(e["p"][0])), (sch.F_Y, _r(e["p"][1])), (sch.F_Z, _r(e["p"][2])),
(sch.F_ABSTAND, _r(abstand)),
(sch.F_ROTATION, _r(e["rot"])),
(sch.F_TYP, a.get("KREISELART") or sch.TYP_STANDARD),
# Umlaufsinn: bestimmt die Reihenfolge im Kreisel-Umlauf und damit den
# TRO-Fluss - ohne ihn kam jeder Nachbau als UZS heraus (Dematic 1/4:
# GUZ). hundm05:bau-kreisel reicht ihn an kreisel-insert-script weiter.
("drehrichtung", a.get("DREHRICHTUNG") or "UZS"),
("comment", "aus Zeichnung: %s (ID %s), abstand aus %s"
(sch.F_DREHRICHTUNG, a.get("DREHRICHTUNG") or sch.DREHRICHTUNG_UZS),
(sch.F_COMMENT, "aus Zeichnung: %s (ID %s), abstand aus %s"
% (e["name"], a.get("ID", "?"), quelle)),
])
@@ -251,15 +252,15 @@ def gf_eintrag(e, geo, projekt, hinweise):
r = _richtung(hz)
ende = (start[0] + deltaL * r[0], start[1] + deltaL * r[1])
eintrag = collections.OrderedDict([
("test_id", "GF_%s_%02d" % (projekt, _nummer(e["name"]))),
("modus", 1),
("deltaL_mm", _r(deltaL)),
("hz_grad", _r(hz)),
("start_mm", [_r(start[0]), _r(start[1]), _r(z_start)]),
("as_seite", a.get("SEITE_AS") or "links"),
("es_seite", a.get("SEITE_ES") or "links"),
("as_winkel", xd[2] or "90"),
("es_winkel", xd[3] or "90"),
(sch.F_TEST_ID, "GF_%s_%02d" % (projekt, _nummer(e["name"]))),
(sch.F_MODUS, sch.GF_MODUS_WERTEINGABE),
(sch.F_DELTAL_MM, _r(deltaL)),
(sch.F_HZ_GRAD, _r(hz)),
(sch.F_START_MM, [_r(start[0]), _r(start[1]), _r(z_start)]),
(sch.F_AS_SEITE, a.get("SEITE_AS") or sch.SEITE_LINKS),
(sch.F_ES_SEITE, a.get("SEITE_ES") or sch.SEITE_LINKS),
(sch.F_AS_WINKEL, xd[2] or sch.SCHLEUS_WINKEL_90),
(sch.F_ES_WINKEL, xd[3] or sch.SCHLEUS_WINKEL_90),
])
k_start = kreisel_an_punkt(start, r, geo)
k_ende = kreisel_an_punkt(ende, (-r[0], -r[1]), geo)
@@ -275,7 +276,7 @@ def gf_eintrag(e, geo, projekt, hinweise):
else:
eintrag["winkel_grad"] = round(winkel, 4)
kommentar = "kein Kreisel am Ende - gebauter Winkel uebernommen"
eintrag["comment"] = "aus Zeichnung: %s (ID %s); %s" % (
eintrag[sch.F_COMMENT] = "aus Zeichnung: %s (ID %s); %s" % (
e["name"], a.get("ID", "?"), kommentar)
return eintrag
@@ -286,18 +287,18 @@ def vf_standard_eintrag(e, projekt, typ):
xd = e["xdata"].get("SSG_VF_EDIT") or []
l_gf = [t for t in (a.get("L_GF_m") or "").split(",") if t.strip()]
eintrag = collections.OrderedDict([
("test_id", "VF_%s_%02d" % (projekt, _nummer(e["name"]))),
("typ", typ),
("richtung", a.get("ANTRIEBFAHRTRICHTUNG") or "Auf"),
("deltaL", _r(_float(a.get("DELTA_L_mm")))),
("deltaH", _r(_float(a.get("DELTA_H_mm")))),
("winkel", int(_float(a.get("VF_WINKEL")))),
("L_GF1", _r(1000.0 * _float(l_gf[0])) if l_gf else 0.0),
("seite", a.get("SEITE_AS") or "rechts"),
("motorseite", (a.get("MOTORSEITE") or "rechts").split(",")[0]),
("hz", _r(_float(xd[1]) if len(xd) > 1 else 0.0)),
("x", _r(e["p"][0])), ("y", _r(e["p"][1])), ("z", _r(e["p"][2])),
("comment", "aus Zeichnung: %s (ID %s), Hoehe %s -> %s; deltaL/"
(sch.F_TEST_ID, "VF_%s_%02d" % (projekt, _nummer(e["name"]))),
(sch.F_VF_TYP, typ),
(sch.F_RICHTUNG, a.get("ANTRIEBFAHRTRICHTUNG") or sch.RICHTUNG_AUF),
(sch.F_DELTA_L, _r(_float(a.get("DELTA_L_mm")))),
(sch.F_DELTA_H, _r(_float(a.get("DELTA_H_mm")))),
(sch.F_WINKEL, int(_float(a.get("VF_WINKEL")))),
(sch.F_L_GF1, _r(1000.0 * _float(l_gf[0])) if l_gf else 0.0),
(sch.F_SEITE, a.get("SEITE_AS") or sch.SEITE_RECHTS),
(sch.F_MOTORSEITE, (a.get("MOTORSEITE") or sch.SEITE_RECHTS).split(",")[0]),
(sch.F_HZ, _r(_float(xd[1]) if len(xd) > 1 else 0.0)),
(sch.F_X, _r(e["p"][0])), (sch.F_Y, _r(e["p"][1])), (sch.F_Z, _r(e["p"][2])),
(sch.F_COMMENT, "aus Zeichnung: %s (ID %s), Hoehe %s -> %s; deltaL/"
"deltaH aus den (ganzzahligen) Attributen"
% (e["name"], a.get("ID", "?"), a.get("HOEHE_VON_mm", "?"),
a.get("HOEHE_BIS_mm", "?"))),
@@ -311,10 +312,10 @@ def vf_journal_eintrag(e, projekt, nr):
roh = "".join(e["xdata"]["SSG_VF_EDIT"][1:])
tokens = [t for t in roh.split(TRENNER) if t]
return collections.OrderedDict([
("test_id", "VF_%s_LZ_%02d" % (projekt, nr)),
("typ", "linienzug_journal"),
(sch.F_TEST_ID, "VF_%s_LZ_%02d" % (projekt, nr)),
(sch.F_VF_TYP, "linienzug_journal"),
("journal", tokens),
("comment", "aus Zeichnung: %s (ID %s), %d Eingaben, Hoehe %s -> %s; "
(sch.F_COMMENT, "aus Zeichnung: %s (ID %s), %d Eingaben, Hoehe %s -> %s; "
"wird 1:1 abgespielt (vsp-bau-journal)"
% (e["name"], a.get("ID", "?"), len(tokens),
a.get("HOEHE_VON_mm", "?"), a.get("HOEHE_BIS_mm", "?"))),
@@ -326,11 +327,11 @@ def einzel_eintrag(e):
info = ", ".join("%s %s" % (k, a[k]) for k in ("ID", "ZUORDNUNG", "ARTINR")
if a.get(k))
return collections.OrderedDict([
("function", "insert"),
("block", DIM_SUFFIX.sub("", e["name"])),
("x", _r(e["p"][0])), ("y", _r(e["p"][1])), ("z", _r(e["p"][2])),
("rotation", _r(e["rot"])),
("comment", "aus Zeichnung: %s%s" % (e["name"],
(sch.F_FUNCTION, sch.FUNC_INSERT),
(sch.F_BLOCK, DIM_SUFFIX.sub("", e["name"])),
(sch.F_X, _r(e["p"][0])), (sch.F_Y, _r(e["p"][1])), (sch.F_Z, _r(e["p"][2])),
(sch.F_ROTATION, _r(e["rot"])),
(sch.F_COMMENT, "aus Zeichnung: %s%s" % (e["name"],
(" (%s)" % info) if info else "")),
])
@@ -390,11 +391,11 @@ def main():
eintraege, hinweise = baue_eintraege(inserts, block_inhalt, projekt)
zaehler = collections.Counter(
"Kreisel" if "abstand" in e else
"GF" if "modus" in e else
"VF Linienzug" if e.get("typ") == "linienzug_journal" else
"VF %s" % e["typ"] if "typ" in e and "test_id" in e else
e.get("block", "?") for e in eintraege)
"Kreisel" if sch.F_ABSTAND in e else
"GF" if sch.F_MODUS in e else
"VF Linienzug" if e.get(sch.F_VF_TYP) == "linienzug_journal" else
"VF %s" % e[sch.F_VF_TYP] if sch.F_VF_TYP in e and sch.F_TEST_ID in e else
e.get(sch.F_BLOCK, "?") for e in eintraege)
for k, v in sorted(zaehler.items()):
print("%s %-22s %d" % (PREFIX, k, v))
for h in hinweise:
+79
View File
@@ -0,0 +1,79 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
testdata_schema.py - Feldnamen und feste Werte der JSON-Testdaten-Schemas aus
doc/json-input-lisp.md, als einzige Quelle fuer alle Erzeuger.
Bisher standen dieselben Feldnamen ("function", "abstand", "deltaL_mm",
"as_seite", ...) als lose String-Literale unabhaengig in dxf_scan_components.py
(fremde Zeichnungen) UND dxf_anlage_export.py (eigene, mit unseren Makros
gebaute Zeichnungen) - ein Tippfehler oder eine Schema-Aenderung in nur einer
der beiden Dateien waere nicht aufgefallen. Dieses Modul buendelt sie an
einer Stelle; die LISP-Treiber (tests/test_*.lsp) lesen die JSON-Keys direkt
als Strings und sind davon nicht betroffen.
Nur Feldnamen/feste Werte, KEINE Validierung oder Default-Logik - die bleibt
bewusst in den jeweiligen Erzeugern, deren Kontext (fremde vs. eigene
Zeichnung) unterschiedliche Fallbacks braucht.
"""
# --- Gemeinsame Felder ---
F_FUNCTION = "function"
F_ID = "id"
F_TEST_ID = "test_id"
F_BLOCK = "block"
F_X = "x"
F_Y = "y"
F_Z = "z"
F_ROTATION = "rotation"
F_COMMENT = "comment"
FUNC_INSERT = "insert"
FUNC_CONNECT = "connect"
# --- Abschnitt 1 (json-input-lisp.md): Kreisel ---
F_ABSTAND = "abstand"
F_TYP = "typ"
F_DREHRICHTUNG = "drehrichtung"
TYP_STANDARD = "STANDARD"
TYP_PIN = "PIN"
DREHRICHTUNG_UZS = "UZS"
DREHRICHTUNG_GUZ = "GUZ"
# --- Abschnitt 3: VarioFoerderer Standard und Etage ---
F_VF_TYP = "typ"
F_RICHTUNG = "richtung"
F_DELTA_L = "deltaL"
F_DELTA_H = "deltaH"
F_WINKEL = "winkel"
F_L_GF1 = "L_GF1"
F_GF_VERTEILUNG = "gf_verteilung"
F_SEITE = "seite"
F_MOTORSEITE = "motorseite"
F_HZ = "hz"
VF_TYP_STANDARD = "standard"
VF_TYP_ETAGE = "etage"
RICHTUNG_AUF = "Auf"
RICHTUNG_AB = "Ab"
SEITE_LINKS = "links"
SEITE_RECHTS = "rechts"
# --- Abschnitt 4.1: Gefaellestrecke Modus 1 ---
F_MODUS = "modus"
F_DELTAL_MM = "deltaL_mm"
F_WINKEL_GRAD = "winkel_grad"
F_HZ_GRAD = "hz_grad"
F_START_MM = "start_mm"
F_AS_SEITE = "as_seite"
F_ES_SEITE = "es_seite"
F_AS_WINKEL = "as_winkel"
F_ES_WINKEL = "es_winkel"
GF_MODUS_WERTEINGABE = 1
GF_MODUS_RICHTUNG_AUS_PUNKTEN = 2
GF_MODUS_LINIENZUG = 3
SCHLEUS_WINKEL_90 = "90"
SCHLEUS_WINKEL_30 = "30"