diff --git a/Lisp/export.lsp b/Lisp/export.lsp index e792d61..1a7d611 100644 --- a/Lisp/export.lsp +++ b/Lisp/export.lsp @@ -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) ) diff --git a/Lisp/ssg_id.lsp b/Lisp/ssg_id.lsp index 5041677..8bb22f1 100644 --- a/Lisp/ssg_id.lsp +++ b/Lisp/ssg_id.lsp @@ -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" diff --git a/cfg/export.cfg b/cfg/export.cfg index 7918897..94f9e09 100644 --- a/cfg/export.cfg +++ b/cfg/export.cfg @@ -70,6 +70,22 @@ # pattern_geruest: Geruest-Baugruppen (GeruestInsert.lsp, GR__). # 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] diff --git a/lib/export_csv.py b/lib/export_csv.py index b23c6d8..19c7b75 100644 --- a/lib/export_csv.py +++ b/lib/export_csv.py @@ -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 diff --git a/lib/export_sivas.py b/lib/export_sivas.py index 6b5725a..9babd76 100644 --- a/lib/export_sivas.py +++ b/lib/export_sivas.py @@ -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 diff --git a/tests/test_foerderer.py b/tests/test_foerderer.py index f39bfb8..2ef3796 100644 --- a/tests/test_foerderer.py +++ b/tests/test_foerderer.py @@ -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", +} # --------------------------------------------------------------------------- diff --git a/tests/test_gefaellestrecke.py b/tests/test_gefaellestrecke.py index 59fb4d5..963eebd 100644 --- a/tests/test_gefaellestrecke.py +++ b/tests/test_gefaellestrecke.py @@ -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" + ) diff --git a/tests/test_unit.lsp b/tests/test_unit.lsp index b031f1d..6188708 100644 --- a/tests/test_unit.lsp +++ b/tests/test_unit.lsp @@ -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")) )