export_csv: VF_n/GF_n-Einzelelemente (Boegen, Streckenelemente, Umlenk-/Motorstation) als Proxy-Zeilen exportieren

Bisher tauchten nur der Wrapper selbst, verpackte Separatoren und AS/ES-
Kettenenden als eigene CSV-Zeilen auf - die uebrigen Kettenbausteine
(Boegen, gerade Foerderer-/Gefaellestrecken-Segmente, Umlenk-/Motorstation)
blieben nur in der Wrapper-Blockdefinition verpackt. csv:element-proxies-
erzeugen (analog zu csv:sep-proxies-erzeugen) fuegt fuer jeden Fund eine
temporaere Kopie ein, K1/K2 kommen aus dem EIGENEN KS_EIN/KS_AUS jedes
Elements (nicht dem Gesamt-AS/ES der Kette) und bleiben leer, wo keines
existiert (z.B. das nie tatsaechlich eingefuegte Vario_Kurve_*).

Zusaetzlicher Fix: csv:get-k-kos-strings ignorierte bisher die Skalierung
des Top-Level-Inserts - fuer Staustrecke_SP_1000_mm (das per Laenge nicht-
uniform skaliert wird) waere K2 damit falsch gewesen, unabhaengig von der
tatsaechlich gebauten Segmentlaenge.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Michael Stangl
2026-09-24 13:08:35 +02:00
co-authored by Claude Sonnet 5
parent 4f1a0e0b5b
commit f751a90076
8 changed files with 445 additions and 18 deletions
+146 -10
View File
@@ -352,7 +352,17 @@
;; Weltpunkt (realer Bug: K1 landete beim Vorzeichen von X/Y gespiegelt weit
;; ausserhalb der eigenen Boundingbox, sichtbar als fehlendes K-Kreuz in
;; sps_skel/HundM05_bbs.svg trotz gefuelltem K1 in der CSV).
(defun csv:get-k-kos-strings (ename pt parent-R / ed bname blk-tbl sub-ent sub-ed
;; sx/sy/sz: Skalierung des Top-Level-Inserts ename selbst (Gruppencodes
;; 41/42/43), auf sub-pt-real anzuwenden BEVOR in Weltkoordinaten gedreht
;; wird - noetig fuer nicht-uniform skalierte Bloecke wie Staustrecke_SP_
;; 1000_mm (per Laenge auf der X-Achse gestreckt, siehe insert-inclined-
;; scaled-block, Lisp/ssg_ks_insert.lsp:260). Ohne diese Skalierung wuerde
;; K2 dort so berichtet, als waere der Block unskaliert (1:1) eingefuegt -
;; derselbe Fehler, den csv:vfgf-ks-loc fuer AS-/ES-Elemente schon per
;; elem-sx/elem-sy/elem-sz vermeidet (siehe dort). Fuer alle bisherigen
;; Aufrufer (Omniflo/Kreisel/AS-ES-Marker, durchweg uniform mit 1.0 skaliert
;; eingefuegt) ist die Multiplikation ein No-Op - unveraendertes Verhalten.
(defun csv:get-k-kos-strings (ename pt parent-R sx sy sz / ed bname blk-tbl sub-ent sub-ed
sub-bname sub-pt sub-extrusion sub-pt-real world-pt
target is-real result)
(setq ed (entget ename))
@@ -389,6 +399,9 @@
(setq sub-pt (list (car sub-pt) (cadr sub-pt) 0.0)))
(setq sub-extrusion (cond ((cdr (assoc 210 sub-ed))) ('(0.0 0.0 1.0))))
(setq sub-pt-real (mat3-mul-vec3 (mat3-ocs-matrix sub-extrusion) sub-pt))
(setq sub-pt-real (list (* sx (car sub-pt-real))
(* sy (cadr sub-pt-real))
(* sz (caddr sub-pt-real))))
(setq world-pt (csv:local-to-world pt parent-R sub-pt-real))
(setq result (cons
(cons target
@@ -650,7 +663,7 @@
;; csv:get-k-kos-strings) werden immer geschrieben, unabhaengig von
;; include-bbox -- Kodierung ueber csv:kos-encode (siehe dort).
(defun csv:block-to-json (ename include-bbox / ed blk-name layer pt rotation extrusion R-world
handle attribs bbox bbox-json
handle attribs bbox bbox-json psx psy psz
insert-quat insert-kos k-list warnung-json warnung-eintrag
zuordnung-fix-json zuordnung-fix-eintrag
strecke-typ-json strecke-typ-eintrag
@@ -683,7 +696,10 @@
(setq insert-kos (csv:kos-encode
(car pt) (cadr pt) (if (caddr pt) (caddr pt) 0.0)
(nth 0 insert-quat) (nth 1 insert-quat) (nth 2 insert-quat) (nth 3 insert-quat)))
(setq k-list (csv:get-k-kos-strings ename pt R-world))
(setq psx (cond ((cdr (assoc 41 ed))) (1.0)))
(setq psy (cond ((cdr (assoc 42 ed))) (1.0)))
(setq psz (cond ((cdr (assoc 43 ed))) (1.0)))
(setq k-list (csv:get-k-kos-strings ename pt R-world psx psy psz))
;; Omniflo-Geraden fuehren keine echten K-Bloecke - K1/K2 aus der Geometrie
;; (Anfang/Ende ueber Laenge + Rotation) synthetisieren, damit sie im Export
;; wie echte Anschluss-Koordinatensysteme erscheinen und in der Nachbar-
@@ -856,6 +872,24 @@
"Separator_SP_2D,Separator_SP_3D"
)
;; --- Muster der uebrigen verpackten VF_n/GF_n-Bausteine (Boegen, gerade
;; Foerderer-/Gefaellestrecken-Segmente, Umlenk-/Motorstation) ---
;; Wie csv:pattern-nested-separator/-schleus: diese Bausteine stecken als
;; Sub-INSERTs in der Wrapper-Blockdefinition und sind fuer (ssget "X" ...)
;; unsichtbar. EIN kombiniertes Muster fuer alle vier Familien (statt vier
;; getrennter csv:nested-welt-records-Aufrufe) haelt den zusaetzlichen
;; Scan-Aufwand auf EINEN weiteren Durchlauf der ganzen Zeichnung beschraenkt
;; - csv:element-teileart klassifiziert danach anhand des Blocknamens, in
;; welche TeileArt-Familie ein Treffer faellt.
(defun csv:pattern-nested-element ()
(strcat
(export:pattern "pattern_nested_bogen" "Gefaellebogen_*,Vario_Bogen_*,Vario_Kurve_*")
"," (export:pattern "pattern_nested_streckenelement" "Staustrecke_SP_1000_mm*")
"," (export:pattern "pattern_nested_umlenkstation" "Vario_Umlenkstation_*")
"," (export:pattern "pattern_nested_motorstation" "Vario_Motorstation_*")
)
)
;; --- Muster der verpackten AS-/ES-Schleuselemente ---
;; AS_Element_*/ES_Element_* sind die beiden Kettenenden einer VF_n/GF_n-
;; Strecke (Ein-/Ausschleusung zum Kreisel). Sie stecken - wie die Separator-
@@ -881,6 +915,30 @@
)
)
;; --- TeileArt-Familie eines ausgepackten Elements aus seinem Blocknamen ---
;; Reiner Namensklassifikator (parallel zu csv:proxy-typ), file-frei testbar
;; (tests/test_unit.lsp). Vario_Bogen_* deckt sowohl die _TEF_-Variante (VF-
;; Vertikalboegen) als auch die reine Variante ohne _TEF_ (GF-eigene Vertikal-
;; boegen) ab - beide auf denselben TeileArt-Bucket "BOGEN", darum ist -
;; anders als bei vfl-kette-typ (vf_linienzug.lsp) - keine _TEF_-zuerst-
;; Reihenfolge noetig. Vario_Kurve_* ist laut Code-Kommentar (vf_linienzug.lsp)
;; aktuell nirgends im Projekt tatsaechlich eingefuegt, aber zur Vollstaendig-
;; keit mit aufgenommen. Liefert nil fuer alles andere (sollte bei einem
;; ueber csv:pattern-nested-element gefundenen Treffer nie vorkommen -
;; defensive Absicherung, falls Muster und cond je auseinanderlaufen).
(defun csv:element-teileart (bname / bn)
(setq bn (strcase bname))
(cond
((wcmatch bn "GEFAELLEBOGEN_*") "BOGEN")
((wcmatch bn "VARIO_KURVE_*") "BOGEN")
((wcmatch bn "VARIO_BOGEN_*") "BOGEN")
((wcmatch bn "STAUSTRECKE_SP_1000_MM*") "STRECKENELEMENT")
((wcmatch bn "VARIO_UMLENKSTATION_*") "UMLENKSTATION")
((wcmatch bn "VARIO_MOTORSTATION_*") "MOTORSTATION")
(t nil)
)
)
;; --- Muster der Compound-("Wrapper"-)Bloecke, deren Definition ueberhaupt
;; auf verpackte Separator_SP-Symbole durchsucht wird (KR_n/KREISEL_n/
;; ECKRAD_n/VF_n/GF_n) - alle anderen INSERTs (Omniflo, eigenstaendige
@@ -1120,6 +1178,57 @@
(reverse proxy-liste)
)
;; --- Temporaere Kopie je verpacktem uebrigen VF_n/GF_n-Baustein (Bogen/
;; Streckenelement/Umlenkstation/Motorstation) ---
;; Analog zu csv:sep-proxies-erzeugen, aber ohne die dortige "aussen"/XDATA-
;; Zusatzlogik (die existiert nur fuer Separator wegen SSG_SEP_TYP) und ohne
;; ZUORDNUNG-Zuweisung: diese Elemente sind keine einem Carrier zugeordneten
;; Sensoren, sondern die Geometrie des Carriers (VF_n/GF_n) selbst - ARTINR/
;; LAYER kommen direkt aus den ATTDEFs der kopierten Original-Blockdefinition
;; (ssg-attrib-read auf die Kopie), wie bei Separator.
;;
;; proxy-liste-Eintrag: (proxy-ename wrapper-ename teileart-key (x y z)).
;; teileart-key (aus csv:element-teileart) wird NICHT ins JSON geschrieben -
;; Python klassifiziert die TeileArt ohnehin ueber block_name + die vier
;; pattern_nested_*-Keys aus cfg/export.cfg, wie jede andere Familie auch.
(defun csv:element-proxies-erzeugen ( / ms recs rec proxy proxy-liste)
(setq recs (csv:nested-welt-records (csv:pattern-nested-element)))
(if recs
(progn
(setq ms (vla-get-ModelSpace (vla-get-ActiveDocument (vlax-get-acad-object))))
(foreach rec recs
(setq proxy (csv:block-einfuegen-welt ms (nth 2 rec) (nth 3 rec) (nth 4 rec)
(nth 5 rec) (nth 6 rec) (nth 7 rec)
(cdr (assoc 2 (entget (nth 0 rec))))))
(if proxy
(setq proxy-liste
(cons (list proxy (nth 0 rec) (csv:element-teileart (nth 2 rec)) (nth 3 rec))
proxy-liste)))
)
)
)
(reverse proxy-liste)
)
;; --- Temporaere Element-Kopien nach dem Export wieder entfernen ---
;; Eigene, minimale Loeschfunktion statt csv:sep-proxies-loeschen wieder-
;; zuverwenden: *cs-sep-fix-by-handle*/*cs-sep-typ-by-handle*/
;; *csv-sepliste-id-by-handle* bleiben separator-/AS-/ES-spezifisch, diese
;; Elemente haben keine Entsprechung davon. Nutzt denselben Debug-Schalter
;; "csv_leave_proxies" wie Separator, fuer konsistentes Debugging.
(defun csv:element-proxies-loeschen (proxy-liste / behalten)
(setq behalten (and (car (atoms-family 1 '("DBG-SCHALTER-P")))
(dbg-schalter-p "csv_leave_proxies")))
(if behalten
(princ (strcat "\n[EXPORT] csv_leave_proxies=1: "
(itoa (length proxy-liste))
" Element-Proxy-Kopie(n) bleiben in der Zeichnung"
" (Abschalten: (dbg-schalter-off \"csv_leave_proxies\"))"))
(foreach rec proxy-liste (entdel (car rec)))
)
(princ)
)
;; --- ZUORDNUNG der temporaeren Separator-Kopien aus ihrem Wrapper setzen ---
;; MUSS nach ssg-id-check-all laufen: erst dann steht die ID des Wrapper-
;; Blocks (VF_n/GF_n/KREISEL_n) fest - ein frisch gebauter Wrapper kann beim
@@ -1741,6 +1850,21 @@
(wcmatch bname (export:pattern "pattern_eingang" "Eingang*"))
(wcmatch bname (export:pattern "pattern_ausgang" "Ausgang*"))
(wcmatch bname (export:pattern "pattern_geruest" "GR_*"))
;; Uebrige verpackte VF_n/GF_n-Bausteine (Boegen, gerade
;; Foerderer-/Gefaellestrecken-Segmente, Umlenk-/Motorstation) -
;; siehe csv:element-proxies-erzeugen. pattern_strecke oben
;; wuerde die meisten davon schon per Praefix (Vario*/
;; Staustrecke*) miterfassen, NUR Gefaellebogen_* nicht - die
;; vier expliziten Klauseln schliessen diese Luecke und machen
;; die Zugehoerigkeit klar. Kein Duplikate-Risiko: eine ssadd-
;; Selektion kann dieselbe Entity nicht zweimal enthalten, egal
;; wie viele Klauseln hier matchen.
(wcmatch bname (export:pattern "pattern_nested_bogen"
"Gefaellebogen_*,Vario_Bogen_*,Vario_Kurve_*"))
(wcmatch bname (export:pattern "pattern_nested_streckenelement"
"Staustrecke_SP_1000_mm*"))
(wcmatch bname (export:pattern "pattern_nested_umlenkstation" "Vario_Umlenkstation_*"))
(wcmatch bname (export:pattern "pattern_nested_motorstation" "Vario_Motorstation_*"))
;; Rein numerische Namen = Omniflo SivasNr (Boegen/Weichen)
;; Rein LISP-intern (Python klassifiziert Omniflo stattdessen
;; ueber exakten Katalog-Match), daher fest im Code statt in
@@ -1794,7 +1918,7 @@
;; die bbox-Fehlerdiagnose zuruecksetzen. Rueckgabe: die Liste ALLER erzeugten
;; Kopien - Separatoren UND AS/ES-Schleuselemente (der Aufrufer entfernt sie am
;; Ende wieder). label dient nur den Meldungen.
(defun csv:export-vorbereiten (label / sep-proxies asmarks)
(defun csv:export-vorbereiten (label / sep-proxies asmarks element-proxies)
;; Verpackte Separator_SP-Symbole (siehe csv:sep-proxies-erzeugen) VOR der
;; ID-Vergabe durch temporaere, echte Kopien ersetzen - sie werden am Ende
;; von csv:run-export wieder entfernt (csv:sep-proxies-loeschen).
@@ -1804,6 +1928,13 @@
;; (ZZ_ASES_MARKER): er macht das Kettenende in der Zeichnung sichtbar und
;; traegt dessen ID ueber Exportlaeufe hinweg (siehe csv:asmark-*).
(setq asmarks (csv:asmark-aktualisieren))
;; Die uebrigen Kettenbausteine (Boegen, gerade Foerderer-/Gefaellestrecken-
;; Segmente, Umlenk-/Motorstation) ebenso wie die Separatoren durch
;; temporaere Kopien ersetzen - siehe csv:element-proxies-erzeugen. MUSS wie
;; sep-proxies VOR ssg-id-check-all stehen, damit die frischen Kopien in
;; dessen Lauf mit nummeriert werden (siehe ssg-id-collect-blocks,
;; ssg_id.lsp).
(setq element-proxies (csv:element-proxies-erzeugen))
;; Vor dem Export: IDs aller Bloecke sicherstellen und Duplikate korrigieren.
;; Erst hier - also erst nachdem ALLE Kopien aus allen Wrapper-Bloecken
;; stehen - wird ueberhaupt eine ID vergeben. ssg-id-check-all ermittelt
@@ -1862,7 +1993,7 @@
(setq *csv-bbox-fail-first* nil)
;; Nur die TEMPORAEREN Kopien zurueckgeben - die AS/ES-Marker bleiben
;; bewusst in der Zeichnung stehen und duerfen nicht mitgeloescht werden.
sep-proxies)
(list sep-proxies element-proxies))
;; --- Python-Export-Skript aufrufen und Ergebnis auswerten ---------------
;; Aus csv:run-export ausgelagert: das fertige raw-JSON (out-pfad) an das
@@ -1927,11 +2058,14 @@
;; include-bbox = T -> Bounding-Box je Block ermitteln und mit exportieren
;; (siehe csv:block-to-json); fuer EXPORTSIVAS nil.
(defun csv:run-export (label py-name csv-name include-bbox / ss i ename fh out-pfad
first-block count out-dir sep-proxies)
first-block count out-dir sep-proxies element-proxies
vorbereitung-ergebnis)
;; Zeichnungszustand fuer den Export herrichten (Proxies, IDs, Zuordnung,
;; Attribut-Update, Sep/Scan-Zaehlung, REGENALL, bbox-Reset) - ausgelagert.
;; Rueckgabe: alle erzeugten Kopien (unten wieder zu entfernen).
(setq sep-proxies (csv:export-vorbereiten label))
;; Rueckgabe: (sep-proxies element-proxies), beide unten wieder zu entfernen.
(setq vorbereitung-ergebnis (csv:export-vorbereiten label))
(setq sep-proxies (nth 0 vorbereitung-ergebnis))
(setq element-proxies (nth 1 vorbereitung-ergebnis))
(princ (ssg-textf "exp-collect-blocks" (list label)))
(setq ss (csv:collect-export-blocks))
@@ -1997,10 +2131,12 @@
)
)
)
;; Temporaere Separator-/AS-/ES-Kopien wieder entfernen - sie haben ihren
;; Temporaere Separator-/Element-Kopien wieder entfernen - sie haben ihren
;; Zweck (ID-Vergabe + JSON-Zeile ueber die obigen, unveraenderten
;; Sammel-Funktionen) erfuellt.
;; Sammel-Funktionen) erfuellt. Die AS-/ES-Marker (ZZ_ASES_MARKER) bleiben
;; bewusst stehen (siehe csv:export-vorbereiten).
(csv:sep-proxies-loeschen sep-proxies)
(csv:element-proxies-loeschen element-proxies)
(princ)
)
+7 -1
View File
@@ -45,6 +45,12 @@
;; Spalte (auch auf Kreiseln) bekommen.
;; GR_* (Geruest-Baugruppen, siehe pattern_geruest in cfg/export.cfg) ebenso -
;; das ID-ATTDEF steht seit *geruest-attrib-defs* zur Verfuegung.
;; Gefaellebogen_* (siehe pattern_nested_bogen in cfg/export.cfg): einer der
;; Bausteine einer GF-Kette, verpackt in VF_n/GF_n; wird beim Export ebenso
;; als temporaere Kopie ausgepackt wie die uebrigen Vario_*/Staustrecke_*-
;; Bausteine (csv:element-proxies-erzeugen, export.lsp) - anders als diese
;; faengt "Gefaellebogen" aber KEINES der obigen Praefixe ab und braucht
;; daher eine eigene Klausel.
(defun ssg-id-collect-blocks ( / ss-all ss-out i ename ed bname)
(dbgf "ssg-id-collect-blocks")
(setq ss-all (ssget "X" (list (cons 0 "INSERT"))))
@@ -59,7 +65,7 @@
(if (or
(wcmatch bname "KR_*,KREISEL_*,ECKRAD_*")
(wcmatch (strcase bname) "AP110*,AP_110*,AP60*,AP_60*,APG110*")
(wcmatch bname "Vario*,Staustrecke*,AUS_Element*,EIN_Element*,VF_*,GF_*")
(wcmatch bname "Vario*,Staustrecke*,Gefaellebogen*,AUS_Element*,EIN_Element*,VF_*,GF_*")
;; ZZ_ASES_MARKER: die Marker der AS-/ES-Kettenenden (siehe
;; csv:asmark-aktualisieren, export.lsp). Sie brauchen eine ID -
;; sie vertreten das Kettenende im Export ("sepliste"-Feld "id"
+20
View File
@@ -70,6 +70,22 @@
# pattern_geruest: Geruest-Baugruppen (GeruestInsert.lsp, GR_<id>_<timestamp>).
# Eigenstaendig wie BTMT, kein ANZAHL_*-Attribut an einem Carrier - export_csv.py
# UND export_sivas.py fuehren sie als eigene TeileArt-Zeilen.
#
# pattern_nested_bogen/_streckenelement/_umlenkstation/_motorstation: die
# uebrigen Bausteine einer VF_n/GF_n-Kette (Boegen, gerade Foerderer-/
# Gefaellestrecken-Segmente, Umlenk-/Motorstation). Wie pattern_schleuselement
# liegen sie NICHT als eigener top-level INSERT vor, sondern verpackt in der
# Wrapper-Blockdefinition - die Muster dienen der SUCHE dort
# (csv:nested-welt-records). Exportiert wird eine TEMPORAERE Kopie des
# jeweiligen Original-Blocks (csv:element-proxies-erzeugen, wie bei
# Separator/pattern_separator), die nach dem Export wieder geloescht wird.
# Vario_Kurve_* ist laut Code-Kommentar (vf_linienzug.lsp) aktuell nirgends
# im Projekt tatsaechlich eingefuegt - nur zur Vollstaendigkeit gelistet.
# Staustrecke_SP_1000_mm wird beim Bau per Laenge NICHT-UNIFORM skaliert
# (insert-inclined-scaled-block) - csv:get-k-kos-strings beruecksichtigt das
# beim Lesen von K1/K2 (sx/sy/sz-Parameter). export_sivas.py schliesst diese
# vier Muster wie Separator/Scanner aus (Stueckzahl steckt schon in den
# GF_Bogen_*/VF_Bogen_*/ANZAHL_VF/ANZAHL_GF-Attributen des Wrappers).
# ============================================================
[blockpattern]
@@ -89,6 +105,10 @@ pattern_btmt_entladung= SC_Entladung*
pattern_eingang= Eingang*
pattern_ausgang= Ausgang*
pattern_geruest= GR_*
pattern_nested_bogen= Gefaellebogen_*, Vario_Bogen_*, Vario_Kurve_*
pattern_nested_streckenelement= Staustrecke_SP_1000_mm*
pattern_nested_umlenkstation= Vario_Umlenkstation_*
pattern_nested_motorstation= Vario_Motorstation_*
pattern_ks_subblocks= KS_EIN, KS_AUS, K1, K2, K3, K4
[python]
+88
View File
@@ -253,6 +253,25 @@ def build_sensor_merkmale(block):
}
def build_element_merkmale(block):
"""Merkmale-Dict fuer eine ausgepackte VF_n/GF_n-Sub-Komponente (Bogen/
Streckenelement/Umlenkstation/Motorstation) - siehe csv:element-proxies-
erzeugen (export.lsp) und pattern_nested_bogen/_streckenelement/
_umlenkstation/_motorstation in cfg/export.cfg.
ARTINR/LAYER kommen direkt aus den ATTDEFs der ausgepackten Original-
Blockdefinition (ssg-attrib-read auf der temporaeren Kopie) - dieselbe
Quelle wie bei Separator/Scanner (build_sensor_merkmale), aber OHNE
"Zuordnung"-Feld: diese Elemente sind keine einem Carrier zugeordneten
Sensoren, sondern die Geometrie des Carriers selbst."""
attribs = block.get("attribs", {})
return {
"ARTINR": attribs.get("ARTINR", ""),
"Höhe": get_hoehe(block),
"Drehung": get_drehung(block),
}
def build_btmt_merkmale(block):
"""Merkmale-Dict fuer eine eigenstaendige BTMT-Beladung/SC_Entladung-Station
(siehe pattern_btmt_beladung/pattern_btmt_entladung in cfg/export.cfg).
@@ -916,6 +935,10 @@ def process_blocks(blocks, lookup, dbg=None):
vf_count = 0
gf_count = 0
strecke_modul_count = 0
strecke_bogen_count = 0
streckenelement_count = 0
umlenkstation_count = 0
motorstation_count = 0
separator_count = 0
scanner_count = 0
btmt_beladung_count = 0
@@ -1130,6 +1153,71 @@ def process_blocks(blocks, lookup, dbg=None):
})
continue
# Ausgepackte VF_n/GF_n-Sub-Komponenten (Boegen, gerade Foerderer-/
# Gefaellestrecken-Segmente, Umlenk-/Motorstation) - temporaere Kopien,
# siehe csv:element-proxies-erzeugen (export.lsp). MUESSEN vor
# pattern_strecke_module geprueft werden: dessen Muster ("Vario*,
# Staustrecke*, ...") wuerde diese Bloecke sonst zuerst abfangen und
# als generische "ILS 2.0 Strecke - Modul"-Zeile ohne K1/K2 ausgeben.
# k1/k2 brauchen hier keine Sonderbehandlung - bbox_columns() liest
# sie bereits generisch aus dem "k1"/"k2"-Feld des JSON-Rows
# (csv:get-k-kos-strings), genau wie bei jedem anderen Block.
if matches_any(bname, BLOCKPATTERNS.get("pattern_nested_bogen", [])):
elem_nr += 1
strecke_bogen_count += 1
items.append({
"nr": elem_nr,
"teileart": "ILS 2.0 Bogen",
"teileid": block.get("attribs", {}).get("ID") or format_sensor_id("BOG", strecke_bogen_count),
"bezeichnung": f"Bogen :{strecke_bogen_count}",
"planquadrat": planquadrat_column(block, planquadrat_cfg),
"merkmale": build_element_merkmale(block),
**bbox_columns(block),
})
continue
if matches_any(bname, BLOCKPATTERNS.get("pattern_nested_streckenelement", [])):
elem_nr += 1
streckenelement_count += 1
items.append({
"nr": elem_nr,
"teileart": "ILS 2.0 Streckenelement",
"teileid": block.get("attribs", {}).get("ID") or format_sensor_id("STR", streckenelement_count),
"bezeichnung": f"Streckenelement :{streckenelement_count}",
"planquadrat": planquadrat_column(block, planquadrat_cfg),
"merkmale": build_element_merkmale(block),
**bbox_columns(block),
})
continue
if matches_any(bname, BLOCKPATTERNS.get("pattern_nested_umlenkstation", [])):
elem_nr += 1
umlenkstation_count += 1
items.append({
"nr": elem_nr,
"teileart": "ILS 2.0 Umlenkstation",
"teileid": block.get("attribs", {}).get("ID") or format_sensor_id("UML", umlenkstation_count),
"bezeichnung": f"Umlenkstation :{umlenkstation_count}",
"planquadrat": planquadrat_column(block, planquadrat_cfg),
"merkmale": build_element_merkmale(block),
**bbox_columns(block),
})
continue
if matches_any(bname, BLOCKPATTERNS.get("pattern_nested_motorstation", [])):
elem_nr += 1
motorstation_count += 1
items.append({
"nr": elem_nr,
"teileart": "ILS 2.0 Motorstation",
"teileid": block.get("attribs", {}).get("ID") or format_sensor_id("MOT", motorstation_count),
"bezeichnung": f"Motorstation :{motorstation_count}",
"planquadrat": planquadrat_column(block, planquadrat_cfg),
"merkmale": build_element_merkmale(block),
**bbox_columns(block),
})
continue
# ILS Strecke-Module (Vario/Staustrecke-Einzelkomponenten)
if matches_any(bname, BLOCKPATTERNS.get("pattern_strecke_module", [])):
elem_nr += 1
+13
View File
@@ -410,8 +410,21 @@ SKIP_BLOCKS = set(BLOCKPATTERNS.get("pattern_ks_subblocks", ["K1", "K2", "K3", "
# ueber die ANZAHL_SEPARATOR/ANZAHL_SCANNER-Attribute des zugehoerigen
# Kreisel/GF/VF-Blocks gezaehlt (siehe kreisel_signature/build_*_details
# oben) - ein zusaetzlicher eigener Eintrag wuerde doppelt zaehlen.
#
# Dieselbe Ueberlegung gilt fuer die ausgepackten VF_n/GF_n-Sub-Komponenten
# (Boegen, gerade Foerderer-/Gefaellestrecken-Segmente, Umlenk-/Motorstation -
# siehe csv:element-proxies-erzeugen, export.lsp): ihre Stueckzahl steckt
# bereits in den GF_Bogen_*/VF_Bogen_*/ANZAHL_VF/ANZAHL_GF-Attributen des
# Wrappers (*strecke-attr-gruppe*, ssg_core.lsp) - ein eigener Sivas-Eintrag
# wuerde ebenso doppelt zaehlen. Muss VOR der pattern_strecke_module-Pruefung
# unten greifen, sonst faengt deren generisches "ILS 2.0 Strecke"-Sammeln
# diese Kopien zuerst ab.
SEP_SCANNER_PATTERNS = (
BLOCKPATTERNS.get("pattern_separator", []) + BLOCKPATTERNS.get("pattern_scanner", [])
+ BLOCKPATTERNS.get("pattern_nested_bogen", [])
+ BLOCKPATTERNS.get("pattern_nested_streckenelement", [])
+ BLOCKPATTERNS.get("pattern_nested_umlenkstation", [])
+ BLOCKPATTERNS.get("pattern_nested_motorstation", [])
)
# Sivas-Nummern und Bezeichnungen je ILS-Elementtyp
+15 -7
View File
@@ -20,13 +20,21 @@ import os
import pytest
# TeileArt-Werte, die einen SENSOR beschreiben statt einer Vario-Strecke -
# csv:sep-proxies-erzeugen (export.lsp, Commit e2f0384) erfasst seither auch
# Separator_SP-Symbole, die als Sub-INSERT in der VF_n-Blockdefinition
# verpackt sind, und exportiert je Fund eine eigene Zeile. Foerderer kennt
# nur diese eine Sensorart (kein Scanner); die vollstaendige Liste steht in
# test_export_ids.py:SENSOR_TEILEARTEN.
NEBEN_TEILEARTEN = {"ILS 2.0 Separator"}
# TeileArt-Werte, die einen SENSOR bzw. ein Streckenbauteil beschreiben statt
# einer Vario-Strecke selbst - csv:sep-proxies-erzeugen (export.lsp, Commit
# e2f0384) erfasst auch Separator_SP-Symbole, die als Sub-INSERT in der
# VF_n-Blockdefinition verpackt sind, und exportiert je Fund eine eigene
# Zeile. csv:element-proxies-erzeugen (export.lsp) erfasst ebenso die
# uebrigen verpackten Bausteine (Boegen, gerade Foerderer-/Gefaellestrecken-
# Segmente, Umlenk-/Motorstation) als je eigene Zeile. Foerderer kennt kein
# Scanner; die vollstaendige Liste steht in test_export_ids.py:SENSOR_TEILEARTEN.
NEBEN_TEILEARTEN = {
"ILS 2.0 Separator",
"ILS 2.0 Bogen",
"ILS 2.0 Streckenelement",
"ILS 2.0 Umlenkstation",
"ILS 2.0 Motorstation",
}
# ---------------------------------------------------------------------------
+135
View File
@@ -10,8 +10,10 @@ Prueft:
Modus 3 uebernimmt deltaH/deltaL aus Modus 2)
"""
import csv
import json
import os
import sys
import pytest
@@ -232,3 +234,136 @@ class TestGefaellestreckeModusKonsistenz:
f'von {quelle_id}={m2["delta_h_mm"]} ab '
f'(Toleranz {self.DELTA_H_TOLERANZ_MM} mm)'
)
# ---------------------------------------------------------------------------
# Klasse 4: CSV-Export-Pruefung (gefaellestrecke_tests_export.csv)
# ---------------------------------------------------------------------------
# TeileArt-Werte, die einen Sensor bzw. ein Streckenbauteil beschreiben statt
# einer GF-Strecke selbst - csv:element-proxies-erzeugen (export.lsp) erfasst
# die verpackten Bausteine (Boegen, gerade Foerderer-/Gefaellestrecken-
# Segmente, Umlenk-/Motorstation) als je eigene Zeile, wie schon
# csv:sep-proxies-erzeugen fuer Separator_SP-Symbole; siehe
# test_foerderer.py:NEBEN_TEILEARTEN (analoge Konstante dort, erste GF-eigene
# CSV-Struktur-Pruefung hier - vorher gab es fuer GF-Ketten noch keine).
NEBEN_TEILEARTEN = {
"ILS 2.0 Separator",
"ILS 2.0 Bogen",
"ILS 2.0 Streckenelement",
"ILS 2.0 Umlenkstation",
"ILS 2.0 Motorstation",
}
class TestGefaellestreckeExportCSV:
"""Prueft den CSV-Export aus BricsCAD (gefaellestrecke_tests_export.csv,
aus TEST_EXPORT_ALL)."""
@pytest.fixture(autouse=True)
def setup(self):
csv_path = os.path.join(_output_dir(), "gefaellestrecke_tests_export.csv")
if not os.path.exists(csv_path):
pytest.skip(
"gefaellestrecke_tests_export.csv nicht vorhanden - "
"TEST_GEFAELLESTRECKE und anschliessend TEST_EXPORT_ALL in BricsCAD ausfuehren"
)
self.rows = []
with open(csv_path, "r", encoding="utf-8") as f:
reader = csv.reader(f, delimiter=";")
self.header = next(reader)
for row in reader:
self.rows.append(row)
self.col = {name: i for i, name in enumerate(self.header)}
def _cell(self, row, spalte):
i = self.col.get(spalte)
if i is None or i >= len(row):
return None
return row[i]
def test_teileart_bekannt(self):
"""Jede Zeile ist entweder eine GF-Strecke oder eine der bekannten
Neben-TeileArten - keine unerwartete dritte Art."""
bekannt = {"ILS 2.0 Gefaellestrecke", "ILS 2.0 Strecke"} | NEBEN_TEILEARTEN
for i, row in enumerate(self.rows):
teileart = self._cell(row, "TeileArt")
if teileart is None:
continue
assert teileart.strip('"') in bekannt, (
f"Zeile {i + 2}: unbekannte TeileArt '{teileart}'"
)
def test_element_zeilen_haben_k1_oder_k2(self):
"""Bogen/Umlenkstation/Motorstation-Zeilen sollten zuverlaessig ein
eigenes KS_EIN/KS_AUS tragen (anders als Vario_Kurve_*, das laut
Code-Kommentar nie tatsaechlich eingefuegt wird und daher hier auch
nie auftaucht) - K1 ODER K2 muss gefuellt sein. Streckenelement wird
hier NICHT geprueft (dafuer siehe
test_streckenelement_laenge_stimmt_mit_k1_k2_ueberein unten)."""
pruef_teilearten = {"ILS 2.0 Bogen", "ILS 2.0 Umlenkstation", "ILS 2.0 Motorstation"}
for i, row in enumerate(self.rows):
teileart = self._cell(row, "TeileArt")
if teileart is None or teileart.strip('"') not in pruef_teilearten:
continue
k1 = self._cell(row, "K1")
k2 = self._cell(row, "K2")
assert (k1 and k1.strip('"')) or (k2 and k2.strip('"')), (
f"Zeile {i + 2}: TeileArt='{teileart}' hat weder K1 noch K2 gefuellt"
)
def test_streckenelement_laenge_stimmt_mit_k1_k2_ueberein(self):
"""Regressionstest fuer den csv:get-k-kos-strings-Skalierungsfix:
Staustrecke_SP_1000_mm wird beim Bau per Laenge NICHT-UNIFORM skaliert
(insert-inclined-scaled-block) - ohne die sx/sy/sz-Beruecksichtigung
in csv:get-k-kos-strings waere K2 dort so berichtet, als waere der
Block unskaliert (1000mm) eingefuegt, unabhaengig von der tatsaechlich
gebauten Segmentlaenge. Der K1->K2-Abstand einer Streckenelement-Zeile
muss daher der Boundingbox-Ausdehnung (die reale, gebaute Laenge)
entsprechen, NICHT literal 1000mm, sofern das Segment gestreckt wurde."""
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "lib"))
from export_neighbors import _decode_trans_xy
gefunden = 0
for i, row in enumerate(self.rows):
teileart = self._cell(row, "TeileArt")
if teileart is None or teileart.strip('"') != "ILS 2.0 Streckenelement":
continue
k1_str = self._cell(row, "K1")
k2_str = self._cell(row, "K2")
if not k1_str or not k2_str:
continue
p1 = _decode_trans_xy(k1_str.strip('"'))
p2 = _decode_trans_xy(k2_str.strip('"'))
if p1 is None or p2 is None:
continue
k1_k2_laenge = ((p2[0] - p1[0]) ** 2 + (p2[1] - p1[1]) ** 2) ** 0.5
# Nur Segmente pruefen, deren reale Laenge sichtbar von 1000mm
# abweicht - sonst waere ein (durch den Bug) falsch auf 1000mm
# zurueckfallender Wert zufaellig nicht von einem korrekten zu
# unterscheiden.
if abs(k1_k2_laenge - 1000.0) < 50.0:
continue
gefunden += 1
boundingbox = self._cell(row, "Boundingbox")
assert boundingbox, f"Zeile {i + 2}: Boundingbox fehlt"
dx, dy, dz = (float(v) for v in boundingbox.strip('"').split(","))
bbox_diagonale = (dx ** 2 + dy ** 2) ** 0.5
# Toleranz: die Boundingbox-Diagonale ist ein Oberwert (das
# Element kann geneigt/gedreht sein), der K1-K2-Abstand darf sie
# nicht wesentlich ueberschreiten - waere er trotz Streckung immer
# ~1000mm (der Bug), wuerde er bei stark gestreckten Segmenten
# deutlich UNTER der Bounding-Box-Diagonale liegen.
assert k1_k2_laenge <= bbox_diagonale + 50.0, (
f"Zeile {i + 2}: K1->K2-Abstand={k1_k2_laenge:.0f}mm groesser als "
f"Boundingbox-Diagonale={bbox_diagonale:.0f}mm"
)
assert k1_k2_laenge >= 50.0, (
f"Zeile {i + 2}: K1->K2-Abstand={k1_k2_laenge:.0f}mm verdaechtig klein "
f"(Skalierungsfix eventuell nicht wirksam)"
)
if gefunden == 0:
pytest.skip(
"keine Streckenelement-Zeile mit von 1000mm abweichender "
"K1-K2-Laenge gefunden - Testdaten enthalten kein gestrecktes Segment"
)
+21
View File
@@ -389,6 +389,27 @@
(tu-eq "csv:proxy-typ/as" "AS" (csv:proxy-typ "AS_Element_90_links_3D"))
(tu-eq "csv:proxy-typ/es" "ES" (csv:proxy-typ "ES_Element_30_rechts_2D"))
(tu-eq "csv:proxy-typ/sep" "SEP" (csv:proxy-typ "Separator_SP_3D"))
;; csv:element-teileart: TeileArt-Familie eines ausgepackten VF_n/GF_n-
;; Bausteins aus dem Blocknamen (Boegen/Streckenelement/Umlenk-/Motor-
;; station). Vario_Bogen_* deckt sowohl die _TEF_- als auch die reine
;; GF-Variante ab, beide auf denselben Bucket "BOGEN".
(tu-eq "csv:element-teileart/gefaellebogen" "BOGEN"
(csv:element-teileart "Gefaellebogen_links_90_R500"))
(tu-eq "csv:element-teileart/vario-bogen-tef" "BOGEN"
(csv:element-teileart "Vario_Bogen_auf_3_TEF_rechts"))
(tu-eq "csv:element-teileart/vario-bogen-gf" "BOGEN"
(csv:element-teileart "Vario_Bogen_ab_6_rechts"))
(tu-eq "csv:element-teileart/vario-kurve" "BOGEN"
(csv:element-teileart "Vario_Kurve_links_30_TEF_innen"))
(tu-eq "csv:element-teileart/streckenelement" "STRECKENELEMENT"
(csv:element-teileart "Staustrecke_SP_1000_mm"))
(tu-eq "csv:element-teileart/umlenkstation" "UMLENKSTATION"
(csv:element-teileart "Vario_Umlenkstation_500mm_links"))
(tu-eq "csv:element-teileart/motorstation" "MOTORSTATION"
(csv:element-teileart "Vario_Motorstation_500mm_rechts"))
(tu-eq "csv:element-teileart/unbekannt" nil
(csv:element-teileart "AS_Element_90_links"))
)