From e7643bf1810484977d3147dc9fe0afe43cfa6fb2 Mon Sep 17 00:00:00 2001 From: Michael Stangl Date: Thu, 24 Sep 2026 23:46:41 +0200 Subject: [PATCH] export_csv: VF_n/GF_n-Einzelelemente zu Foerdereinheit/Gefaelleeinheit gruppieren Die Einzelteil-Proxy-Zeilen (Boegen, Streckenelemente, Umlenk-/Motorstation) aus der letzten Session sind fuer den Normalfall zu granular. Standardmaessig (*csv-export-alle-elemente* = nil) fasst csv:unitmark-aktualisieren sie jetzt zu je einer "Foerdereinheit"/"Gefaelleeinheit"-Zeile pro zusammenhaengendem VF-/GF-Abschnitt einer Kette zusammen, mit einem persistenten Rechteck-Marker (ZZ_UNIT_MARKER, analog ZZ_ASES_MARKER) auf der Bounding-Box-Vereinigung aller enthaltenen Elemente. Der bisherige Rohdaten-Export bleibt ueber (csv:export-alle-elemente-an) weiterhin verfuegbar. Einheiten-Grenzen kommen rein export-seitig aus der sepliste (SEP/AS/ES in echter Bau-Reihenfolge) plus dem bereits vorhandenen SSG_SEP_TYP je Separator - eine bauzeitige Markierung in vf_linienzug.lsp/vf_standard.lsp/ vf_etage.lsp waere deutlich invasiver gewesen (3 unabhaengige Baupfade ohne gemeinsamen Einstiegspunkt). csv:sepliste-partition (Lauflaengen-Kodierung ueber die typisierte sepliste) und csv:element-unit-zuordnung (Zuordnung der gesammelten Elemente per Kettenrichtungs-Projektion statt der nachweislich unzuverlaessigen csv:nested-welt-records-Listenreihenfolge) sind reine, dateifrei getestete Funktionen. Korrigiert nebenbei eine falsche Annahme: L_VF_m/L_GF_m zaehlen pro Segment, nicht pro Einheit - die tatsaechliche Einheiten-Anzahl kommt nur aus der sepliste-Typfolge. Co-Authored-By: Claude Sonnet 5 --- Lisp/export.lsp | 804 ++++++++++++++++++++++++++++++++++++++++++-- Lisp/ssg_id.lsp | 4 + cfg/export.cfg | 31 +- lib/export_csv.py | 54 +++ lib/export_sivas.py | 16 +- tests/test_unit.lsp | 81 +++++ 6 files changed, 943 insertions(+), 47 deletions(-) diff --git a/Lisp/export.lsp b/Lisp/export.lsp index 1a7d611..507f318 100644 --- a/Lisp/export.lsp +++ b/Lisp/export.lsp @@ -50,6 +50,21 @@ ;; ueberlebt damit jeden Neustart. (setq *export-test-override* nil) +;; --- Schalter: rohe Einzelteil-Zeilen statt gruppierter Foerdereinheit/ +;; Gefaelleeinheit-Zeilen exportieren --- +;; Default nil = gruppierter Modus (csv:unitmark-aktualisieren fasst Boegen/ +;; Streckenelemente/Umlenk-/Motorstation zu je einer Zeile pro +;; Foerdereinheit/Gefaelleeinheit zusammen, die vier pattern_nested_*- +;; Einzelteil-Zeilen bleiben aus dem Export ausgeschlossen). Auf T gesetzt: +;; Verhalten wie vor der Gruppierung - jedes Einzelteil bekommt seine eigene +;; Export-Zeile (csv:collect-export-blocks laesst die pattern_nested_*- +;; Klauseln dann durch). Nur per Lisp-Variable/den beiden Helfern +;; umschaltbar, kein Menuepunkt vorgesehen. +(if (not (boundp '*csv-export-alle-elemente*)) (setq *csv-export-alle-elemente* nil)) +(defun csv:export-alle-elemente-p () *csv-export-alle-elemente*) +(defun csv:export-alle-elemente-an () (setq *csv-export-alle-elemente* T) (princ)) +(defun csv:export-alle-elemente-aus () (setq *csv-export-alle-elemente* nil) (princ)) + ;; --- Feste Datei-/Skriptnamen dieses Moduls (keine Config, direkt im Code) --- (setq *export-filenames* @@ -655,6 +670,20 @@ ) +;; --- K1/K2 eines ZZ_UNIT_MARKER lesen --- +;; Fallback fuer csv:block-to-json (siehe dort): die Werte stehen bereits +;; fertig kodiert in den ATTDEFs K1/K2 (siehe csv:unitmark-aktualisieren), +;; hier nur noch als K-Liste im Format von csv:get-k-kos-strings verpackt. +(defun csv:unitmark-k-kos-strings (ename / attribs k1 k2 result) + (setq attribs (ssg-attrib-read ename)) + (setq k1 (cdr (assoc "K1" attribs))) + (setq k2 (cdr (assoc "K2" attribs))) + (setq result nil) + (if (and k1 (> (strlen k1) 0)) (setq result (cons (cons "K1" k1) result))) + (if (and k2 (> (strlen k2) 0)) (setq result (cons (cons "K2" k2) result))) + result +) + ;; --- Einen INSERT-Block als JSON-Objekt schreiben --- ;; include-bbox = T -> zusaetzlich Bounding-Box (Mitte + Ausdehnung) ermitteln ;; und als "bbox"-Objekt anhaengen (nur fuer EXPORTCSV, nicht fuer EXPORTSIVAS). @@ -718,6 +747,14 @@ (wcmatch (strcase blk-name) "VF_*,GF_*")) (setq k-list (csv:vfgf-k-kos-strings ename pt rotation)) ) + ;; ZZ_UNIT_MARKER (Foerdereinheit/Gefaelleeinheit): K1/K2 stehen bereits + ;; fertig kodiert als eigene ATTDEFs am Marker (csv:unitmark-aktualisieren + ;; berechnet sie einmal bei der Marker-Erzeugung aus dem ersten/letzten + ;; zugeordneten Element) - keine erneute KS-Suche noetig. + (if (and (null k-list) + (wcmatch (strcase blk-name) "ZZ_UNIT_MARKER*")) + (setq k-list (csv:unitmark-k-kos-strings ename)) + ) (setq bbox-json "") (if include-bbox (progn @@ -1475,6 +1512,325 @@ gesetzt ) +;; =========================================================================== +;; Foerdereinheit/Gefaelleeinheit-Gruppierung (ZZ_UNIT_MARKER) +;; =========================================================================== +;; Fasst die Einzelbausteine einer VF_n/GF_n-Kette (Boegen, Strecken, Umlenk-/ +;; Motorstation, siehe csv:element-proxies-erzeugen) wieder zu Einheiten +;; zusammen: abwechselnd Foerdereinheit (VF) / Gefaelleeinheit (GF), je EINE +;; Zeile mit K1 vom ersten, K2 vom letzten enthaltenen Element. Laeuft rein +;; export-seitig (kein Bau-Code-Eingriff) - Einheiten-Grenzen kommen aus der +;; sepliste (SEP/AS/ES in echter Bau-Reihenfolge, XDATA-lfdnr) plus dem +;; bereits vorhandenen SSG_SEP_TYP je Separator (VF/GF), NICHT aus der +;; unzuverlaessigen csv:nested-welt-records-Listenreihenfolge (siehe +;; Kommentar dort - fuer einen einzelnen Wrapper doppelt umgekehrt) und NICHT +;; aus L_VF_m/L_GF_m (die zaehlen pro Segment, nicht pro Einheit - ein VF- +;; Einheit kann mehrere L_VF_m-Eintraege haben, GF1/GF2-Rampen einer VF- +;; Einheit landen im selben L_GF_m wie eigenstaendige GF-Stuecke, siehe +;; vfl-block-erstellen-Kommentar in vf_linienzug.lsp). + +;; --- Sepliste-Eintraege um strecke-typ und verlaessliche Weltposition +;; anreichern --- +;; sepliste = ((typ lfdnr x y) ...) wie ssg-sepliste-xdata-lesen liefert +;; (XDATA-Ebene - deren x/y sind der ROHE Gruppencode-10-Punkt +;; des Bau-Primitivs und koennen gespiegelt/verschoben sein, +;; siehe die sepliste-x/y-Bereinigung im JSON-Export; hier NICHT +;; als Positionsquelle verwendet, nur zur Paarung mit dem echten +;; Proxy per csv:sepliste-paare, wie an anderer Stelle bereits +;; etabliert). +;; sep-proxies-eigene = NUR die sep-proxies-Records (siehe +;; csv:sep-proxies-erzeugen) DIESES Wrappers, in +;; Erzeugungsreihenfolge. +;; asmarks-eigene = NUR die asmark-Records (marker-ename wrapper-ename +;; typ lfdnr) DIESES Wrappers, aus csv:asmark-aktualisieren. +;; Rueckgabe: ((typ lfdnr strecke-typ welt-pt) ...) in lfdnr-Reihenfolge. +;; strecke-typ = nil fuer AS/ES (wird von csv:sepliste-partition vom +;; Nachbarn geerbt) bzw. fuer einen SEP-Eintrag ohne gefundenen Partner. +;; welt-pt = (x y z) der ECHTEN, verlaesslichen Proxy-/Marker-Position (SEP +;; ueber sep-proxies-eigene, AS/ES ueber den asmark-Entity-Einfuegepunkt), +;; oder nil, falls kein Partner gefunden wurde. +(defun csv:sepliste-mit-strecke-typ (sepliste sep-proxies-eigene asmarks-eigene + / proxies paare paar-by-lfdnr paar erg e + strecke-typ welt-pt m) + (setq proxies + (mapcar (function (lambda (r) (list (nth 2 r) (car (nth 3 r)) (cadr (nth 3 r))))) + sep-proxies-eigene)) + (setq paare (csv:sepliste-paare sepliste proxies)) + (setq paar-by-lfdnr nil) + (foreach paar paare + (setq paar-by-lfdnr (cons (cons (nth 0 paar) (nth 1 paar)) paar-by-lfdnr))) + (setq erg nil) + (foreach e sepliste + (setq strecke-typ nil welt-pt nil) + (cond + ((= (nth 0 e) "SEP") + (setq paar (assoc (nth 1 e) paar-by-lfdnr)) + (if paar + (progn + (setq strecke-typ (nth 4 (nth (cdr paar) sep-proxies-eigene))) + (setq welt-pt (nth 3 (nth (cdr paar) sep-proxies-eigene))) + ) + ) + ) + ((member (nth 0 e) (list "AS" "ES")) + (setq m nil) + (foreach kand asmarks-eigene + (if (and (null m) (= (nth 2 kand) (nth 0 e)) + (= (nth 3 kand) (itoa (nth 1 e)))) + (setq m kand))) + (if m (setq welt-pt (cdr (assoc 10 (entget (nth 0 m)))))) + ) + ) + (setq erg (cons (list (nth 0 e) (nth 1 e) strecke-typ welt-pt) erg)) + ) + (reverse erg) +) + +;; --- Ersten SEP-typ in der Liste finden (Vorschau fuer AS) --- +;; Eine Kette beginnt immer mit einer Foerdereinheit (AS haengt am ersten +;; SEP-Eintrag, falls vorhanden) - Default "VF", falls die Kette gar keinen +;; Separator hat (reine Foerdereinheit ohne jeden Zwischenhalt). +(defun csv:sepliste-erster-sep-typ (sepliste-typisiert / e) + (setq e (car sepliste-typisiert)) + (cond + ((null e) "VF") + ((and (= (nth 0 e) "SEP") (nth 2 e)) (nth 2 e)) + (t (csv:sepliste-erster-sep-typ (cdr sepliste-typisiert))) + ) +) + +;; --- Sepliste (mit strecke-typ) in Einheiten-Spannen partitionieren --- +;; sepliste-typisiert = ((typ lfdnr strecke-typ welt-pt) ...) in lfdnr- +;; Reihenfolge (Rueckgabe von csv:sepliste-mit-strecke-typ). +;; +;; Lauflaengen-Kodierung ueber den EFFEKTIVEN Typ jedes Eintrags, EIN +;; Vorwaerts-Durchlauf mit einer einzigen mitgefuehrten Zustandsvariable +;; "aktueller-typ" (das strecke-typ des zuletzt gesehenen SEP-Eintrags): +;; - AS: effektiver Typ = das strecke-typ des ERSTEN SEP-Eintrags der +;; ganzen Kette (Vorschau, csv:sepliste-erster-sep-typ) - eine Kette +;; beginnt immer mit einer Foerdereinheit. +;; - SEP: effektiver Typ = sein eigenes strecke-typ (faellt es aus - alter +;; Bestand ohne Marker -, bleibt der zuletzt bekannte Typ stehen). +;; "aktueller-typ" wird auf diesen Wert aktualisiert. +;; - ES: effektiver Typ = "aktueller-typ" (der zuletzt gesehene SEP, +;; also die Einheit, die gerade offen ist). +;; Eine neue Einheit beginnt, sobald sich der effektive Typ gegenueber dem +;; Vorgaenger aendert; gleich bleibender Typ (inkl. zwei aufeinanderfolgender +;; SEP-Eintraege desselben Typs, die GEMEINSAM eine einzige Einheit +;; bracketieren - HundM05-Beispiel: SEP3=GF,SEP4=GF sind EINE Gefaelleeinheit) +;; haengt an die offene Einheit an. Kein Sonderfall-Code fuer "erster/zweiter +;; SEP dieses Typs" noetig - die Lauflaengen-Regel deckt beides ab. +;; +;; Rueckgabe: ((einheit-typ (lfdnr ...)) ...) in Kettenreihenfolge. +;; Reine Rechenfunktion (keine Zeichnung, keine Datei) - getestet in +;; tests/test_unit.lsp. +(defun csv:sepliste-partition (sepliste-typisiert / erg e eff aktueller-typ + offen-typ offen-lfdnrs) + (setq aktueller-typ (csv:sepliste-erster-sep-typ sepliste-typisiert)) + (setq erg nil offen-typ nil offen-lfdnrs nil) + (foreach e sepliste-typisiert + (cond + ((= (nth 0 e) "SEP") + (setq aktueller-typ (cond ((nth 2 e)) (aktueller-typ))) + (setq eff aktueller-typ)) + (t (setq eff aktueller-typ)) + ) + (if (and offen-typ (/= eff offen-typ)) + (progn + (setq erg (cons (list offen-typ (reverse offen-lfdnrs)) erg)) + (setq offen-typ nil offen-lfdnrs nil) + ) + ) + (setq offen-typ eff) + (setq offen-lfdnrs (cons (nth 1 e) offen-lfdnrs)) + ) + (if offen-typ + (setq erg (cons (list offen-typ (reverse offen-lfdnrs)) erg))) + (reverse erg) +) + +;; --- Weltposition eines sepliste-lfdnr aus der typisierten Liste holen --- +(defun csv:sepliste-typisiert-pos (lfdnr sepliste-typisiert / treffer) + (setq treffer nil) + (foreach e sepliste-typisiert + (if (and (null treffer) (= (nth 1 e) lfdnr)) (setq treffer e))) + (if treffer (nth 3 treffer)) +) + +;; --- 2D-Skalarprodukt zweier Vektoren (x y) --- +(defun csv:dot2 (a b) (+ (* (car a) (car b)) (* (cadr a) (cadr b)))) + +;; --- Elemente den Einheiten-Spannen zuordnen --- +;; unit-spans = Rueckgabe von csv:sepliste-partition: +;; ((einheit-typ (lfdnr ...)) ...). +;; sepliste-typisiert = Rueckgabe von csv:sepliste-mit-strecke-typ (liefert +;; die Weltposition jedes Rand-lfdnr). +;; element-proxies-eigene = NUR die element-proxies-Records (siehe +;; csv:element-proxies-erzeugen) DIESES Wrappers, +;; (proxy-ename wrapper-ename teileart-key (x y z)). +;; +;; Vorgehen: fuer jede Spanne den ERSTEN und LETZTEN Rand-lfdnr in eine +;; Weltposition aufloesen (Start-/Endpunkt der Spanne entlang der Kette). +;; Kettenrichtung = Einheitsvektor vom allerersten zum allerletzten +;; Rand-lfdnr der GESAMTEN sepliste (AS->ES). Jedes Element wird per +;; Skalarprodukt auf diese Richtung projiziert (t-Wert) und der Spanne +;; zugeordnet, deren [t-start,t-ende]-Intervall am naechsten liegt (bei +;; eindeutiger Enthaltung: diese Spanne; sonst die mit dem kleinsten +;; Abstand zum naeheren Rand - kein Element bleibt unzugeordnet). +;; +;; Rueckgabe: Liste parallel zu unit-spans, je Spanne eine Liste +;; zugeordneter element-proxies-Records. +(defun csv:element-unit-zuordnung (unit-spans sepliste-typisiert + element-proxies-eigene / grenzen span + lfdnrs start-pos ende-pos richtung + erste-pos letzte-pos laenge t-werte + erg elem t-elem beste-i beste-abstand i + t-start t-ende abstand) + ;; Kettenrichtung ueber den allerersten/allerletzten Rand-lfdnr der + ;; gesamten sepliste bestimmen (nicht nur der ersten/letzten Spanne - bei + ;; einer Kette mit nur einer Spanne sind das dieselben Werte). + (setq erste-pos nil letzte-pos nil) + (foreach e sepliste-typisiert + (if (and (null erste-pos) (nth 3 e)) (setq erste-pos (nth 3 e)))) + (foreach e (reverse sepliste-typisiert) + (if (and (null letzte-pos) (nth 3 e)) (setq letzte-pos (nth 3 e)))) + (if (or (null erste-pos) (null letzte-pos) + (< (distance erste-pos letzte-pos) 1e-6)) + ;; Entartete Kette (kein Rand-Punkt aufloesbar, oder AS=ES-Position) - + ;; keine sinnvolle Projektion moeglich, jede Spanne bekommt keine + ;; Elemente zugeordnet (Aufrufer behandelt das wie "keine Elemente"). + (setq richtung '(1.0 0.0)) + (progn + (setq richtung (list (- (car letzte-pos) (car erste-pos)) + (- (cadr letzte-pos) (cadr erste-pos)))) + (setq laenge (sqrt (csv:dot2 richtung richtung))) + (if (> laenge 1e-6) + (setq richtung (list (/ (car richtung) laenge) (/ (cadr richtung) laenge))) + (setq richtung '(1.0 0.0)) + ) + ) + ) + ;; Je Spanne: [t-start, t-ende] aus dem ersten/letzten Rand-lfdnr. + (setq grenzen nil) + (foreach span unit-spans + (setq lfdnrs (cadr span)) + (setq start-pos (csv:sepliste-typisiert-pos (car lfdnrs) sepliste-typisiert)) + (setq ende-pos (csv:sepliste-typisiert-pos (last lfdnrs) sepliste-typisiert)) + (setq t-start + (if start-pos + (csv:dot2 (list (- (car start-pos) (car erste-pos)) + (- (cadr start-pos) (cadr erste-pos))) richtung) + 0.0)) + (setq t-ende + (if ende-pos + (csv:dot2 (list (- (car ende-pos) (car erste-pos)) + (- (cadr ende-pos) (cadr erste-pos))) richtung) + 0.0)) + (setq grenzen (cons (list (min t-start t-ende) (max t-start t-ende)) grenzen)) + ) + (setq grenzen (reverse grenzen)) + ;; Je Element: t-Wert projizieren, naechstliegende Spanne waehlen. Jeder + ;; Eintrag wird zunaechst als (t-elem . elem) gefuehrt, damit die Spanne + ;; am Ende NACH t SORTIERT werden kann - das liefert "erstes"/"letztes" + ;; Element einer Spanne entlang der Kettenrichtung (fuer K1/K2 der + ;; Einheiten-Zeile, siehe csv:unitmark-aktualisieren), ohne auf die + ;; unzuverlaessige csv:nested-welt-records-Reihenfolge angewiesen zu sein. + (setq erg nil) + (foreach span unit-spans (setq erg (cons nil erg))) + (setq erg (reverse erg)) + (foreach elem element-proxies-eigene + (setq t-elem + (csv:dot2 (list (- (car (nth 3 elem)) (car erste-pos)) + (- (cadr (nth 3 elem)) (cadr erste-pos))) richtung)) + (setq beste-i 0 beste-abstand nil i 0) + (foreach g grenzen + (setq abstand + (cond + ((and (>= t-elem (car g)) (<= t-elem (cadr g))) 0.0) + ((< t-elem (car g)) (- (car g) t-elem)) + (t (- t-elem (cadr g))) + )) + (if (or (null beste-abstand) (< abstand beste-abstand)) + (progn (setq beste-abstand abstand) (setq beste-i i))) + (setq i (1+ i)) + ) + (setq erg (csv:liste-setzen erg beste-i (cons (cons t-elem elem) (nth beste-i erg)))) + ) + ;; Je Spanne nach t sortieren, dann den t-Wert wieder abstreifen. + (mapcar + (function (lambda (paare) + (mapcar 'cdr (vl-sort paare (function (lambda (a b) (< (car a) (car b)))))))) + erg) +) + +;; --- Element einer Liste an Index i ersetzen (0-basiert) --- +;; Kleiner Hilfsbaustein, da AutoLISP kein eingebautes "setf (nth i lst)" hat. +(defun csv:liste-setzen (lst i wert / j out) + (setq out nil j 0) + (foreach e lst + (setq out (cons (if (= j i) wert e) out)) + (setq j (1+ j)) + ) + (reverse out) +) + +;; --- Bounding-Box-Vereinigung mehrerer Entities --- +;; Verallgemeinert csv:get-bbox (siehe dort) von einer Entity auf eine Liste: +;; min/max ueber alle vla-getboundingbox-Ergebnisse, gleiche Fehler- +;; behandlung/Diagnose-Zaehler (*csv-bbox-fail-count*), gleiche Rueckgabeform +;; (cx cy cz dx dy dz). Entities, deren Box nicht ermittelt werden kann, +;; werden uebersprungen (nicht die ganze Vereinigung verworfen) - eine +;; teilweise Union ist besser als gar keine. +(defun csv:get-bbox-union (enames / obj res ename minpt maxpt gmin gmax dz min-hoehe) + (setq gmin nil gmax nil) + (foreach ename enames + (setq obj (vlax-ename->vla-object ename)) + (vl-catch-all-apply 'vla-update (list obj)) + (setq res + (vl-catch-all-apply + '(lambda ( / ll ur) + (vla-getboundingbox obj 'll 'ur) + (list (csv:pt->list ll) (csv:pt->list ur))))) + (if (vl-catch-all-error-p res) + (progn + (setq *csv-bbox-fail-count* (1+ (cond (*csv-bbox-fail-count*) (0)))) + (if (null *csv-bbox-fail-first*) + (setq *csv-bbox-fail-first* + (strcat "(" (cond ((cdr (assoc 2 (entget ename)))) ("?")) ") " + (vl-catch-all-error-message res)))) + ) + (progn + (setq minpt (car res)) + (setq maxpt (cadr res)) + (setq gmin + (if gmin + (mapcar 'min gmin minpt) + minpt)) + (setq gmax + (if gmax + (mapcar 'max gmax maxpt) + maxpt)) + ) + ) + ) + (if (and gmin gmax) + (progn + (setq dz (- (caddr gmax) (caddr gmin))) + (setq min-hoehe (csv:bb-minimum-height-mm)) + (if (< dz min-hoehe) (setq dz min-hoehe)) + (list + (/ (+ (car gmin) (car gmax)) 2.0) + (/ (+ (cadr gmin) (cadr gmax)) 2.0) + (/ (+ (caddr gmin) (caddr gmax)) 2.0) + (- (car gmax) (car gmin)) + (- (cadr gmax) (cadr gmin)) + dz + ) + ) + nil + ) +) + ;; =========================================================================== ;; AS-/ES-Marker (ZZ_ASES_MARKER) ;; =========================================================================== @@ -1790,6 +2146,376 @@ (princ) ) +;; =========================================================================== +;; Foerdereinheit-/Gefaelleeinheit-Marker (ZZ_UNIT_MARKER) +;; =========================================================================== +;; Ein Rechteck, das der Export je Einheiten-Spanne (siehe +;; csv:sepliste-partition/csv:element-unit-zuordnung weiter oben) setzt und +;; dort DAUERHAFT stehen bleibt - analog zu ZZ_ASES_MARKER, aber auf die +;; Bounding-Box-Vereinigung der enthaltenen Elemente skaliert statt fest +;; quadratisch. Persistiert wie ZZ_ASES_MARKER ueber Exportlaeufe hinweg +;; (Wiederfinden ueber ZUORDNUNG+TYP+LFDNR), damit die TeileId stabil bleibt. +;; +;; Nur aktiv, wenn *csv-export-alle-elemente* NICHT gesetzt ist (siehe +;; Schalter weiter unten) - im Rohdaten-Modus werden keine neuen Marker +;; angelegt, ein Aufraeum-Durchlauf entfernt aber IMMER verwaiste Marker +;; (auch im Rohdaten-Modus - sonst blieben bei einem Wechsel gruppiert->roh +;; alte Marker als Karteileichen stehen). + +(defun csv:unitmark-blockname () "ZZ_UNIT_MARKER") + +;; Attribut-Schema. Alle ATTDEFs unsichtbar (wie *csv-asmark-attrib-defs*). +;; ID - fuellt ssg-id-check-all, ueber Exportlaeufe stabil +;; ZUORDNUNG - ID des VF_n/GF_n-Wrappers (Schluessel zum Wiederfinden) +;; TYP - "VF" (Foerdereinheit) oder "GF" (Gefaelleeinheit) +;; LFDNR - 1-basierte Einheiten-Nummer INNERHALB des Wrappers (NICHT +;; der sepliste-lfdnr - eine frische Zaehlung je Einheit) +;; LAENGE_m - Summe der LAENGE/A-Attribute aller enthaltenen +;; Streckenelement-Mitglieder (Meter, als String) +;; ANZAHL_BOEGEN - Anzahl der enthaltenen Mitglieder mit TeileArt "BOGEN" +;; K1/K2 - KS_EIN des ERSTEN bzw. KS_AUS des LETZTEN Mitglieds der +;; Spanne, bereits als csv:trans-encode-String (wie K1-K4 +;; ueberall sonst im Export) - NICHT als rtos-Text: rtos +;; haengt an DIMZIN/LUPREC/LUNITS und ist fuer persistente +;; ATTDEF-Werte ungeeignet (siehe rtos-Hinweis in anderen +;; sepliste-Serialisierungen, ssg_ks_insert.lsp). Der +;; fertig kodierte String wird unveraendert zurueckgelesen, +;; kein Decode/Re-encode-Rundlauf noetig. +(setq *csv-unitmark-attrib-defs* + '(("ID" "") ("ZUORDNUNG" "") ("TYP" "") ("LFDNR" "") + ("LAENGE_m" "") ("ANZAHL_BOEGEN" "") ("K1" "") ("K2" "")) +) + +;; --- Blockdefinition des Markers bei Bedarf anlegen --- +;; 1x1-Einheitsquadrat auf Layer "0" (wie csv:asmark-blockdef-sicherstellen) - +;; wird beim Einfuegen ueber sx/sy auf die tatsaechliche Bounding-Box- +;; Ausdehnung der Einheit skaliert (csv:block-einfuegen-welt), anders als der +;; feste ZZ_ASES_MARKER. Keine eigene DWG (reine Hilfsgeometrie). +;; Rueckgabe: Blockname oder nil. +(defun csv:unitmark-blockdef-sicherstellen ( / name lastEnt ss e typ) + (setq name (csv:unitmark-blockname)) + (if (tblsearch "BLOCK" name) + name + (progn + (setq lastEnt (entlast)) + (entmake (list '(0 . "LWPOLYLINE") '(100 . "AcDbEntity") '(8 . "0") + '(100 . "AcDbPolyline") '(90 . 4) '(70 . 1) + (cons 10 (list -0.5 -0.5)) + (cons 10 (list 0.5 -0.5)) + (cons 10 (list 0.5 0.5)) + (cons 10 (list -0.5 0.5)))) + (ssg-attrib-make-defs *csv-unitmark-attrib-defs* 0.05) + (setq ss (ssadd)) + (setq e (if lastEnt (entnext lastEnt) (entnext))) + (while e + (setq typ (cdr (assoc 0 (entget e)))) + (if (member typ '("LWPOLYLINE" "ATTDEF")) (ssadd e ss)) + (setq e (entnext e)) + ) + (command "_.-BLOCK" name (list 0.0 0.0 0.0) ss "") + (if (tblsearch "BLOCK" name) + (progn (princ (ssg-textf "exp-unitmark-blockdef" (list name))) name) + (progn (princ (ssg-textf "exp-unitmark-blockdef-fehler" (list name))) nil) + ) + ) + ) +) + +;; --- Vorhandene Marker der Zeichnung einsammeln --- +;; Rueckgabe: Liste von (ename zuordnung typ lfdnr id), analog +;; csv:asmark-sammeln. +(defun csv:unitmark-sammeln ( / ss i ename attribs out) + (setq ss (ssget "X" (list (cons 0 "INSERT") (cons 2 (csv:unitmark-blockname))))) + (if ss + (progn + (setq i 0) + (while (setq ename (ssname ss i)) + (setq attribs (ssg-attrib-read ename)) + (setq out + (cons (list ename + (cond ((cdr (assoc "ZUORDNUNG" attribs))) ("")) + (cond ((cdr (assoc "TYP" attribs))) ("")) + (cond ((cdr (assoc "LFDNR" attribs))) ("")) + (cond ((cdr (assoc "ID" attribs))) (""))) + out)) + (setq i (1+ i)) + ) + ) + ) + out +) + +;; --- Bestehenden Marker ueber (ZUORDNUNG, TYP, LFDNR) wiederfinden --- +(defun csv:unitmark-finden (bestand zuordnung typ lfdnr / m treffer) + (foreach m bestand + (if (and (null treffer) + (= (nth 1 m) zuordnung) + (= (nth 2 m) typ) + (= (nth 3 m) lfdnr)) + (setq treffer m) + ) + ) + treffer +) + +;; --- Laenge (m) eines Streckenelement-Mitglieds aus seinem LAENGE/A- +;; Attribut lesen; 0.0 falls keins vorhanden --- +(defun csv:unitmark-elem-laenge-mm (proxy-ename / attribs wert) + (setq attribs (ssg-attrib-read proxy-ename)) + (setq wert (cond ((cdr (assoc "LAENGE" attribs))) ((cdr (assoc "A" attribs))) (nil))) + (if wert (atof wert) 0.0) +) + +;; --- K1/K2 eines Mitglieds lesen, bereits als csv:trans-encode-String --- +;; Dieselbe KS_EIN/KS_AUS-Suche wie csv:get-k-kos-strings (nur EINE Ebene +;; tief in der Proxy-Blockdefinition, echte K1-K4 haben Vorrang). ks-ziel = +;; "K1" oder "K2" (Ziel-Slot wie dort). +;; Rueckgabe: 12-Zeichen Base64-String oder nil, falls das Mitglied kein +;; passendes KS traegt (z.B. das nie verwendete Vario_Kurve_*). +(defun csv:unitmark-elem-ks (proxy-ename ks-ziel / ed pt rotation extrusion + R-world psx psy psz blk-tbl sub-ent sub-ed + sub-bname sub-pt sub-extrusion sub-pt-real + is-real target treffer) + (setq ed (entget proxy-ename)) + (setq pt (cdr (assoc 10 ed))) + (if (null (caddr pt)) (setq pt (list (car pt) (cadr pt) 0.0))) + (setq rotation (cond ((cdr (assoc 50 ed))) (0.0))) + (setq extrusion (cond ((cdr (assoc 210 ed))) ('(0.0 0.0 1.0)))) + (setq pt (mat3-mul-vec3 (mat3-ocs-matrix extrusion) pt)) + (setq R-world (mat3-from-normal-rotation extrusion rotation)) + (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 blk-tbl (tblsearch "BLOCK" (cdr (assoc 2 ed)))) + (setq treffer nil) + (if blk-tbl + (progn + (setq sub-ent (cdr (assoc -2 blk-tbl))) + (while sub-ent + (setq sub-ed (entget sub-ent)) + (if (= (cdr (assoc 0 sub-ed)) "INSERT") + (progn + (setq sub-bname (strcase (cdr (assoc 2 sub-ed)))) + (setq is-real (wcmatch sub-bname "K1,K2,K3,K4")) + (setq target + (cond + (is-real sub-bname) + ((or (= sub-bname "KS_EIN") (= sub-bname "KSYS_EIN")) "K1") + ((or (= sub-bname "KS_AUS") (= sub-bname "KSYS_AUS")) "K2") + (t nil))) + (if (and target (= target ks-ziel) (or is-real (null treffer))) + (progn + (setq sub-pt (cdr (assoc 10 sub-ed))) + (if (null (caddr sub-pt)) + (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 (* psx (car sub-pt-real)) + (* psy (cadr sub-pt-real)) + (* psz (caddr sub-pt-real)))) + (setq sub-pt-real (csv:local-to-world pt R-world sub-pt-real)) + (setq treffer + (csv:trans-encode (car sub-pt-real) (cadr sub-pt-real) (caddr sub-pt-real))) + ) + ) + ) + ) + (setq sub-ent (entnext sub-ent)) + ) + ) + ) + treffer +) + +;; --- Alle Foerdereinheit-/Gefaelleeinheit-Marker der Zeichnung neu +;; aufbauen --- +;; Laeuft VOR ssg-id-check-all (csv:export-vorbereiten), analog +;; csv:asmark-aktualisieren. sep-proxies/element-proxies/asmarks = die drei +;; bereits erzeugten Proxy-/Marker-Listen dieses Exportlaufs. +;; +;; Ist *csv-export-alle-elemente* gesetzt (Rohdaten-Modus): KEINE neuen +;; Marker anlegen, nur den kompletten vorhandenen Bestand aufraeumen (sonst +;; blieben bei einem Wechsel gruppiert->roh alte Marker als Karteileichen +;; stehen). +;; +;; Rueckgabe: Liste von Records (marker-ename wrapper-ename typ lfdnr) fuer +;; csv:unitmark-ids-setzen. +(defun csv:unitmark-aktualisieren (sep-proxies element-proxies asmarks + / blockname bestand ms wrapper-enames + rec wrapper ed bname wrapper-id app + sepliste sepliste-typisiert sep-eigene + elem-eigene asmarks-eigene unit-spans + zuordnungen span span-i typ lfdnrs + zugeordnet alt alt-id mark bbox-enames + bbox laenge-mm anzahl-boegen elem + lfdnr-erstes lfdnr-letztes k1 k2 + ergebnis neu uebernommen verwaist) + (setq neu 0 uebernommen 0 verwaist 0) + (setq bestand (csv:unitmark-sammeln)) + (if (not (csv:export-alle-elemente-p)) + (progn + (setq blockname (csv:unitmark-blockdef-sicherstellen)) + (if blockname + (progn + (setq ms (vla-get-ModelSpace (vla-get-ActiveDocument (vlax-get-acad-object)))) + ;; Wrapper einsammeln (jeder nur einmal) - Vereinigung ueber alle + ;; drei Proxy-Listen, damit auch ein Wrapper OHNE Boegen/Strecken + ;; (nur AS-Separator-ES, reine Einzel-Foerdereinheit) erfasst wird. + (setq wrapper-enames nil) + (foreach rec sep-proxies + (if (not (member (nth 1 rec) wrapper-enames)) + (setq wrapper-enames (cons (nth 1 rec) wrapper-enames)))) + (foreach rec element-proxies + (if (not (member (nth 1 rec) wrapper-enames)) + (setq wrapper-enames (cons (nth 1 rec) wrapper-enames)))) + (foreach rec asmarks + (if (not (member (nth 1 rec) wrapper-enames)) + (setq wrapper-enames (cons (nth 1 rec) wrapper-enames)))) + (foreach wrapper (reverse wrapper-enames) + (setq ed (entget wrapper)) + (setq bname (cdr (assoc 2 ed))) + (setq wrapper-id (cond ((cdr (assoc "ID" (ssg-attrib-read wrapper)))) (""))) + (setq app + (cond + ((wcmatch (strcase bname) "VF_*") "SSG_VF_SEP") + ((wcmatch (strcase bname) "GF_*") "SSG_GF_SEP") + (t nil))) + (setq sepliste + (if (and app (car (atoms-family 1 '("SSG-SEPLISTE-XDATA-LESEN")))) + (ssg-sepliste-xdata-lesen wrapper app))) + (if sepliste + (progn + (setq sep-eigene nil) + (foreach rec sep-proxies + (if (eq (nth 1 rec) wrapper) (setq sep-eigene (cons rec sep-eigene)))) + (setq sep-eigene (reverse sep-eigene)) + (setq elem-eigene nil) + (foreach rec element-proxies + (if (eq (nth 1 rec) wrapper) (setq elem-eigene (cons rec elem-eigene)))) + (setq elem-eigene (reverse elem-eigene)) + (setq asmarks-eigene nil) + (foreach rec asmarks + (if (eq (nth 1 rec) wrapper) (setq asmarks-eigene (cons rec asmarks-eigene)))) + (setq asmarks-eigene (reverse asmarks-eigene)) + + (setq sepliste-typisiert + (csv:sepliste-mit-strecke-typ sepliste sep-eigene asmarks-eigene)) + (setq unit-spans (csv:sepliste-partition sepliste-typisiert)) + (setq zuordnungen + (csv:element-unit-zuordnung unit-spans sepliste-typisiert elem-eigene)) + + (setq span-i 0) + (foreach span unit-spans + (setq typ (car span)) + (setq lfdnrs (cadr span)) + ;; zugeordnet ist bereits NACH KETTENRICHTUNG SORTIERT + ;; (csv:element-unit-zuordnung) - das erste Element liefert + ;; K1, das letzte K2. + (setq zugeordnet (nth span-i zuordnungen)) + + ;; Bounding-Box-Vereinigung ueber alle zugeordneten + ;; Element-Proxies. Ist eine Spanne leer (z.B. eine ganz + ;; kurze Gefaelleeinheit ohne eigenes Bogen-/Strecken- + ;; Element zwischen zwei direkt benachbarten Separatoren), + ;; entsteht fuer sie kein Marker - eine Einheit ohne jedes + ;; zugeordnete Bauteil hat keine sinnvolle Geometrie. + (setq bbox-enames nil) + (foreach elem zugeordnet (setq bbox-enames (cons (nth 0 elem) bbox-enames))) + (setq bbox (if bbox-enames (csv:get-bbox-union bbox-enames))) + + (if bbox + (progn + ;; Laenge/Anzahl Boegen aus den zugeordneten Elementen. + (setq laenge-mm 0.0 anzahl-boegen 0) + (foreach elem zugeordnet + (if (= (nth 2 elem) "STRECKENELEMENT") + (setq laenge-mm (+ laenge-mm (csv:unitmark-elem-laenge-mm (nth 0 elem))))) + (if (= (nth 2 elem) "BOGEN") + (setq anzahl-boegen (1+ anzahl-boegen))) + ) + ;; K1/K2: eigenes KS_EIN des ERSTEN, KS_AUS des + ;; LETZTEN zugeordneten Elements (Kettenrichtungs- + ;; Sortierung, siehe oben) - fehlt bei einem Element + ;; das passende KS (z.B. das nie verwendete + ;; Vario_Kurve_*), bleibt K1/K2 leer, kein Abbruch. + (setq k1 (csv:unitmark-elem-ks (nth 0 (car zugeordnet)) "K1")) + (setq k2 (csv:unitmark-elem-ks (nth 0 (last zugeordnet)) "K2")) + + (setq mark + (csv:block-einfuegen-welt ms blockname + (list (nth 0 bbox) (nth 1 bbox) (nth 2 bbox)) + (mat3-rz 0.0) + (max (nth 3 bbox) 1.0) (max (nth 4 bbox) 1.0) 1.0 + bname)) + (if mark + (progn + (setq alt (csv:unitmark-finden bestand wrapper-id typ (itoa (1+ span-i)))) + (setq alt-id "") + (if alt + (progn + (setq alt-id (nth 4 alt)) + (entdel (nth 0 alt)) + (setq bestand (vl-remove alt bestand)) + (setq uebernommen (1+ uebernommen)) + ) + (setq neu (1+ neu)) + ) + (ssg-attrib-set-on mark + (list (cons "ID" alt-id) + (cons "ZUORDNUNG" wrapper-id) + (cons "TYP" typ) + (cons "LFDNR" (itoa (1+ span-i))) + (cons "LAENGE_m" (rtos (/ laenge-mm 1000.0) 2 3)) + (cons "ANZAHL_BOEGEN" (itoa anzahl-boegen)) + (cons "K1" (cond (k1) (""))) + (cons "K2" (cond (k2) (""))) + )) + (ssg-layer-setzen mark "ases_marker") + (setq ergebnis (cons (list mark wrapper typ (1+ span-i)) ergebnis)) + ) + ) + ) + ) + (setq span-i (1+ span-i)) + ) + ) + ) + ) + ) + ) + ) + ) + ;; Rest des Bestands (auch im Rohdaten-Modus, wo bestand nie durch obige + ;; Schleife verkleinert wurde): keine Kette/Einheit mehr dahinter. + (foreach m bestand + (entdel (nth 0 m)) + (setq verwaist (1+ verwaist)) + ) + (if (or (> neu 0) (> uebernommen 0) (> verwaist 0)) + (princ (ssg-textf "exp-unitmark-bilanz" + (list (itoa neu) (itoa uebernommen) (itoa verwaist))))) + (reverse ergebnis) +) + +;; --- Nach der ID-Vergabe: ZUORDNUNG bereits gesetzt (anders als bei +;; ZZ_ASES_MARKER, siehe csv:unitmark-aktualisieren) - hier bleibt nur die +;; Bilanz-Meldung uebrig. Kein sepliste-Bezug (Einheiten sind nicht Teil der +;; sepliste selbst). --- +(defun csv:unitmark-ids-setzen (marker-liste / rec mark id gesetzt ohne-id) + (setq gesetzt 0 ohne-id 0) + (foreach rec marker-liste + (setq mark (nth 0 rec)) + (setq id (cdr (assoc "ID" (ssg-attrib-read mark)))) + (if (and id (> (strlen id) 0)) + (setq gesetzt (1+ gesetzt)) + (setq ohne-id (1+ ohne-id)) + ) + ) + (if (> gesetzt 0) + (princ (ssg-textf "exp-unitmark-ids" (list (itoa gesetzt))))) + gesetzt +) + ;; --- Relevante Bloecke sammeln --- ;; Filtert INSERT-Entities auf exportierbare Bloecke: @@ -1828,7 +2554,7 @@ ;; Element mitgezaehlt - export_csv.py UND export_sivas.py fuehren sie ;; beide als eigene TeileArt-Zeilen. ;; Gibt Auswahlsatz zurueck oder nil. -(defun csv:collect-export-blocks ( / ss-all ss-out i ename ed bname) +(defun csv:collect-export-blocks ( / ss-all ss-out i ename ed bname ist-nested-element) (setq ss-all (ssget "X" (list (cons 0 "INSERT")))) (if (null ss-all) (setq ss-out nil) (progn @@ -1837,40 +2563,40 @@ (while (setq ename (ssname ss-all i)) (setq ed (entget ename)) (setq bname (cdr (assoc 2 ed))) - (if (or - (wcmatch bname (export:pattern "pattern_kreisel" "KR_*,KREISEL_*,ECKRAD_*")) - (wcmatch (strcase bname) (export:pattern "pattern_gerade" "AP110*,AP_110*")) - (wcmatch bname (export:pattern "pattern_strecke" - "Vario*,Staustrecke*,AUS_Element*,EIN_Element*,VF_*,GF_*")) - (wcmatch bname (export:pattern "pattern_ases_marker" "ZZ_ASES_MARKER*")) - (wcmatch bname (export:pattern "pattern_separator" "Separator_SP*,S-LP*")) - (wcmatch bname (export:pattern "pattern_scanner" "Scanner*")) - (wcmatch bname (export:pattern "pattern_btmt_beladung" "BTMT-Beladung*")) - (wcmatch bname (export:pattern "pattern_btmt_entladung" "SC_Entladung*")) - (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 - ;; cfg/export.cfg. - (wcmatch bname "#*") - ) + ;; Uebrige verpackte VF_n/GF_n-Bausteine (Boegen, gerade Foerderer-/ + ;; Gefaellestrecken-Segmente, Umlenk-/Motorstation, siehe + ;; csv:pattern-nested-element/csv:element-proxies-erzeugen): NUR im + ;; Rohdaten-Modus (*csv-export-alle-elemente* = T) als eigene Export- + ;; Zeile gesammelt. Im Default (gruppierter Modus, nil) fasst + ;; csv:unitmark-aktualisieren sie zu Foerdereinheit-/Gefaelleeinheit- + ;; Zeilen zusammen (pattern_unit_marker unten) - die Proxy-Kopien + ;; selbst werden dann trotz Treffer auf pattern_strecke (Vario*/ + ;; Staustrecke* per Praefix) explizit AUSGESCHLOSSEN, damit sie nicht + ;; doppelt (einzeln UND als Teil der Einheiten-Zeile) erscheinen. + (setq ist-nested-element + (and (not (csv:export-alle-elemente-p)) + (wcmatch bname (csv:pattern-nested-element)))) + (if (and (not ist-nested-element) + (or + (wcmatch bname (export:pattern "pattern_kreisel" "KR_*,KREISEL_*,ECKRAD_*")) + (wcmatch (strcase bname) (export:pattern "pattern_gerade" "AP110*,AP_110*")) + (wcmatch bname (export:pattern "pattern_strecke" + "Vario*,Staustrecke*,AUS_Element*,EIN_Element*,VF_*,GF_*")) + (wcmatch bname (export:pattern "pattern_ases_marker" "ZZ_ASES_MARKER*")) + (wcmatch bname (export:pattern "pattern_separator" "Separator_SP*,S-LP*")) + (wcmatch bname (export:pattern "pattern_scanner" "Scanner*")) + (wcmatch bname (export:pattern "pattern_btmt_beladung" "BTMT-Beladung*")) + (wcmatch bname (export:pattern "pattern_btmt_entladung" "SC_Entladung*")) + (wcmatch bname (export:pattern "pattern_eingang" "Eingang*")) + (wcmatch bname (export:pattern "pattern_ausgang" "Ausgang*")) + (wcmatch bname (export:pattern "pattern_geruest" "GR_*")) + (wcmatch bname (export:pattern "pattern_unit_marker" "ZZ_UNIT_MARKER*")) + ;; Rein numerische Namen = Omniflo SivasNr (Boegen/Weichen) + ;; Rein LISP-intern (Python klassifiziert Omniflo stattdessen + ;; ueber exakten Katalog-Match), daher fest im Code statt in + ;; cfg/export.cfg. + (wcmatch bname "#*") + )) (ssadd ename ss-out) ) (setq i (1+ i)) @@ -1918,7 +2644,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 element-proxies) +(defun csv:export-vorbereiten (label / sep-proxies asmarks element-proxies unitmarks) ;; 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). @@ -1935,6 +2661,11 @@ ;; dessen Lauf mit nummeriert werden (siehe ssg-id-collect-blocks, ;; ssg_id.lsp). (setq element-proxies (csv:element-proxies-erzeugen)) + ;; Foerdereinheit-/Gefaelleeinheit-Marker (ZZ_UNIT_MARKER) aus den drei + ;; obigen Proxy-/Marker-Listen bauen - MUSS nach ihnen (braucht ihre + ;; Weltpositionen) und VOR ssg-id-check-all stehen (frische Marker werden + ;; dort mit nummeriert), siehe csv:unitmark-aktualisieren. + (setq unitmarks (csv:unitmark-aktualisieren sep-proxies element-proxies asmarks)) ;; 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 @@ -1956,6 +2687,7 @@ (setq *csv-sepliste-id-by-handle* nil) (csv:asmark-ids-setzen asmarks) (csv:sepliste-ids-setzen sep-proxies) + (csv:unitmark-ids-setzen unitmarks) ;; Vor dem Export HOEHE/DREHUNG aller Omniflo-Elemente aktualisieren (princ (ssg-textf "exp-update-hoehe-drehung" (list label))) (omni:update-all-attribs) diff --git a/Lisp/ssg_id.lsp b/Lisp/ssg_id.lsp index 8bb22f1..ec88d1a 100644 --- a/Lisp/ssg_id.lsp +++ b/Lisp/ssg_id.lsp @@ -72,6 +72,10 @@ ;; und eigene JSON-Zeile) und behalten sie ueber Laeufe hinweg, ;; weil der Marker in der Zeichnung stehen bleibt. (wcmatch bname "ZZ_ASES_MARKER*") + ;; ZZ_UNIT_MARKER: die Foerdereinheit-/Gefaelleeinheit-Marker + ;; (siehe csv:unitmark-aktualisieren, export.lsp) - aus + ;; demselben Grund wie ZZ_ASES_MARKER. + (wcmatch bname "ZZ_UNIT_MARKER*") (wcmatch bname "Separator_SP*,S-LP*,Scanner*") (wcmatch bname "BTMT-Beladung*,SC_Entladung*") (wcmatch bname "Eingang*,Ausgang*") diff --git a/cfg/export.cfg b/cfg/export.cfg index 94f9e09..6599a2f 100644 --- a/cfg/export.cfg +++ b/cfg/export.cfg @@ -76,16 +76,36 @@ # 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. +# (csv:nested-welt-records). Eine TEMPORAERE Kopie des jeweiligen Original- +# Blocks wird IMMER erzeugt (csv:element-proxies-erzeugen, wie bei Separator/ +# pattern_separator - liefert die Positionsdaten fuer die Gruppierung unten) +# und nach dem Export IMMER wieder geloescht; als EIGENE Export-Zeile +# erscheinen sie aber NUR, wenn *csv-export-alle-elemente* (export.lsp) auf T +# gesetzt ist. Im Default (nil, gruppierter Modus) fasst +# csv:unitmark-aktualisieren sie stattdessen zu Foerdereinheit-/ +# Gefaelleeinheit-Zeilen zusammen (siehe pattern_unit_marker). Vario_Kurve_* +# ist laut Code-Kommentar (vf_linienzug.lsp) als "nirgends im Projekt +# tatsaechlich eingefuegt" dokumentiert - in der Praxis kommt es doch vor +# (siehe reale HundM05-Exporte), das Muster bleibt darum wirksam. # 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). +# +# pattern_unit_marker: der Marker, den der Export je zusammenhaengendem +# Foerderer-Abschnitt ("Foerdereinheit") bzw. Gefaelle-Abschnitt +# ("Gefaelleeinheit") einer VF_n/GF_n-Kette setzt (ZZ_UNIT_MARKER, ein +# Rechteck auf der Bounding-Box-Vereinigung aller enthaltenen Boegen/ +# Streckenelemente/Umlenk-/Motorstation-Mitglieder, csv:unitmark- +# aktualisieren in Lisp/export.lsp). Bleibt wie ZZ_ASES_MARKER dauerhaft in +# der Zeichnung stehen und behaelt seine ID ueber Exportlaeufe hinweg +# (Attribute ID/ZUORDNUNG/TYP/LFDNR/LAENGE_m/ANZAHL_BOEGEN/K1/K2, alle +# unsichtbar). Nur aktiv im gruppierten Modus (*csv-export-alle-elemente* = +# nil) - im Rohdaten-Modus werden keine neuen Marker angelegt, verwaiste +# Marker (z.B. nach einem Wechsel gruppiert->roh) werden aber immer +# aufgeraeumt. export_sivas.py schliesst dieses Muster wie die vier +# pattern_nested_*-Muster aus (Stueckzahl steckt in ANZAHL_VF/ANZAHL_GF). # ============================================================ [blockpattern] @@ -109,6 +129,7 @@ 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_unit_marker= ZZ_UNIT_MARKER* 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 19c7b75..71725c2 100644 --- a/lib/export_csv.py +++ b/lib/export_csv.py @@ -272,6 +272,24 @@ def build_element_merkmale(block): } +def build_einheit_merkmale(block): + """Merkmale-Dict fuer eine Foerdereinheit-/Gefaelleeinheit-Zeile (ZZ_UNIT_ + MARKER, siehe csv:unitmark-aktualisieren in export.lsp und + pattern_unit_marker in cfg/export.cfg). + + LAENGE_m/ANZAHL_BOEGEN kommen direkt aus den ATTDEFs des Markers - dort + von Lisp bereits vorberechnet (Summe der LAENGE/A-Attribute aller + zugeordneten Streckenelement-Mitglieder bzw. Anzahl der Bogen-Mitglieder). + Python hat im gruppierten Modus KEINEN Zugriff auf die Einzelteile selbst + (die werden dort gar nicht erst exportiert), daher keine eigene + Neuberechnung hier.""" + attribs = block.get("attribs", {}) + return { + "Laenge_m": attribs.get("LAENGE_m", ""), + "Anzahl_Boegen": attribs.get("ANZAHL_BOEGEN", ""), + } + + 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). @@ -948,6 +966,8 @@ def process_blocks(blocks, lookup, dbg=None): geruest_count = 0 ausschleus_count = 0 einschleus_count = 0 + foerdereinheit_count = 0 + gefaelleeinheit_count = 0 # Zaehlung fuer die Omniflo-Sum-Zeile (nur Bogen/Weiche/Gerade) cnt_wk = cnt_einzel = cnt_delta = cnt_doppel = cnt_stern = 0 @@ -1013,6 +1033,40 @@ def process_blocks(blocks, lookup, dbg=None): }) continue + # ZZ_UNIT_MARKER (siehe cfg/export.cfg pattern_unit_marker sowie + # csv:unitmark-aktualisieren in Lisp/export.lsp): der persistente + # Marker, den Lisp je zusammenhaengendem Foerderer-/Gefaelle-Abschnitt + # einer VF_n/GF_n-Kette setzt (gruppierter Modus, Default). TYP + # entscheidet VF->Foerdereinheit / GF->Gefaelleeinheit, analog zur + # AS/ES-Klassifikation oben. K1/K2 brauchen keine Sonderbehandlung - + # bbox_columns() liest sie generisch aus dem JSON-Row (csv:unitmark- + # k-kos-strings, export.lsp). + if matches_any(bname, BLOCKPATTERNS.get("pattern_unit_marker", [])): + attribs = block.get("attribs", {}) + typ = attribs.get("TYP", "") + teileart = {"VF": "ILS 2.0 Foerdereinheit", "GF": "ILS 2.0 Gefaelleeinheit"}.get(typ) + if teileart is None: + print(f"[export_csv] WARNUNG: {bname} ohne gueltiges TYP-Attribut " + f"(TYP={typ!r}) - uebersprungen") + continue + elem_nr += 1 + if typ == "VF": + foerdereinheit_count += 1 + zaehler_n = foerdereinheit_count + else: + gefaelleeinheit_count += 1 + zaehler_n = gefaelleeinheit_count + items.append({ + "nr": elem_nr, + "teileart": teileart, + "teileid": attribs.get("ID") or format_sensor_id("EINH", elem_nr), + "bezeichnung": f"{teileart} :{zaehler_n}", + "planquadrat": planquadrat_column(block, planquadrat_cfg), + "merkmale": build_einheit_merkmale(block), + **bbox_columns(block), + }) + continue + # Omniflo Bogen oder Weiche if bname in lookup: typ, eintrag = lookup[bname] diff --git a/lib/export_sivas.py b/lib/export_sivas.py index 9babd76..20573bb 100644 --- a/lib/export_sivas.py +++ b/lib/export_sivas.py @@ -413,18 +413,22 @@ SKIP_BLOCKS = set(BLOCKPATTERNS.get("pattern_ks_subblocks", ["K1", "K2", "K3", " # # 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. +# siehe csv:element-proxies-erzeugen, export.lsp; nur im Rohdaten-Modus +# *csv-export-alle-elemente*=T ueberhaupt in export_raw.json vorhanden) sowie +# fuer die gruppierten Foerdereinheit-/Gefaelleeinheit-Marker (ZZ_UNIT_MARKER, +# csv:unitmark-aktualisieren, Default-Modus): 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", []) + + BLOCKPATTERNS.get("pattern_unit_marker", []) ) # Sivas-Nummern und Bezeichnungen je ILS-Elementtyp diff --git a/tests/test_unit.lsp b/tests/test_unit.lsp index 6188708..19712dd 100644 --- a/tests/test_unit.lsp +++ b/tests/test_unit.lsp @@ -410,6 +410,87 @@ (csv:element-teileart "Vario_Motorstation_500mm_rechts")) (tu-eq "csv:element-teileart/unbekannt" nil (csv:element-teileart "AS_Element_90_links")) + + ;; --- csv:sepliste-partition: sepliste in Foerdereinheit-/Gefaelleeinheit- + ;; Spannen aufteilen (Lauflaengen-Kodierung ueber den effektiven Typ) ----- + ;; Reine Rechenfunktion auf (typ lfdnr strecke-typ welt-pt)-Tupeln, hier + ;; ohne Zeichnung testbar (welt-pt wird von der Partitionierung selbst + ;; nicht gebraucht, darum ueberall nil). + + ;; HundM05 VF_1-Beispiel (echt verifiziert): AS, SEP2=VF, SEP3=GF, SEP4=GF, + ;; SEP5=VF, ES -> 3 Einheiten VF,GF,VF. SEP3+SEP4 bracketen GEMEINSAM eine + ;; einzige Gefaelleeinheit (nicht zwei separate). + (setq _sep-hundm05 + '(("AS" 1 nil nil) ("SEP" 2 "VF" nil) ("SEP" 3 "GF" nil) + ("SEP" 4 "GF" nil) ("SEP" 5 "VF" nil) ("ES" 6 nil nil))) + (tu-eq "csv:sepliste-partition/hundm05" + '(("VF" (1 2)) ("GF" (3 4)) ("VF" (5 6))) + (csv:sepliste-partition _sep-hundm05)) + + ;; Minimalfall: nur AS-SEP(VF)-ES, kein Typwechsel -> 1 Foerdereinheit ueber + ;; die ganze Kette. + (setq _sep-minimal '(("AS" 1 nil nil) ("SEP" 2 "VF" nil) ("ES" 3 nil nil))) + (tu-eq "csv:sepliste-partition/minimal" + '(("VF" (1 2 3))) + (csv:sepliste-partition _sep-minimal)) + + ;; Einzel-GF-Fall: die Gefaelleeinheit wird nur von EINEM SEP (nicht zwei) + ;; UND dem nachfolgenden ES bracketiert. + (setq _sep-einzel-gf + '(("AS" 1 nil nil) ("SEP" 2 "VF" nil) ("SEP" 3 "GF" nil) ("ES" 4 nil nil))) + (tu-eq "csv:sepliste-partition/einzel-gf" + '(("VF" (1 2)) ("GF" (3 4))) + (csv:sepliste-partition _sep-einzel-gf)) + + ;; 3+ gleichtypige SEP-Eintraege hintereinander muessen weiterhin EINE + ;; Einheit ergeben (nicht mehrere) - Lauflaengen-Regel, kein Sonderfall. + (setq _sep-dreifach-gf + '(("AS" 1 nil nil) ("SEP" 2 "VF" nil) ("SEP" 3 "GF" nil) + ("SEP" 4 "GF" nil) ("SEP" 5 "GF" nil) ("SEP" 6 "VF" nil) ("ES" 7 nil nil))) + (tu-eq "csv:sepliste-partition/dreifach-gf" + '(("VF" (1 2)) ("GF" (3 4 5)) ("VF" (6 7))) + (csv:sepliste-partition _sep-dreifach-gf)) + + ;; Kette beginnend mit GF (AS erbt den Typ des ersten SEP-Eintrags, hier GF + ;; statt des sonstigen Default "VF"). + (setq _sep-startet-gf + '(("AS" 1 nil nil) ("SEP" 2 "GF" nil) ("ES" 3 nil nil))) + (tu-eq "csv:sepliste-partition/startet-gf" + '(("GF" (1 2 3))) + (csv:sepliste-partition _sep-startet-gf)) + + ;; --- csv:dot2: 2D-Skalarprodukt --- + (tu-eqf "csv:dot2/achsen" 0.0 (csv:dot2 '(1.0 0.0) '(0.0 1.0))) + (tu-eqf "csv:dot2/parallel" 6.0 (csv:dot2 '(2.0 0.0) '(3.0 0.0))) + + ;; --- csv:element-unit-zuordnung: Elemente per Kettenrichtungs-Projektion + ;; den Einheiten-Spannen zuordnen und INNERHALB jeder Spanne nach der + ;; Projektion sortieren ----------------------------------------------- + ;; Gerade Kette AS(0,0) -SEP2(VF, 10,0)- SEP3(GF, 20,0) -ES(30,0)- entlang + ;; der X-Achse. unit-spans: VF=[1,2], GF=[2,3] (SEP2 gehoert zu beiden + ;; Spannen als Grenzmarker, siehe csv:sepliste-partition). + (setq _sep-proj + '(("AS" 1 nil (0.0 0.0 0.0)) ("SEP" 2 "VF" (10.0 0.0 0.0)) + ("SEP" 3 "GF" (20.0 0.0 0.0)) ("ES" 4 nil (30.0 0.0 0.0)))) + (setq _spans-proj (csv:sepliste-partition _sep-proj)) + (tu-eq "csv:sepliste-partition/proj-spans" '(("VF" (1 2)) ("GF" (3 4))) _spans-proj) + ;; Zwei Elemente: eines bei x=5 (klar in der VF-Spanne [0,10]), eines bei + ;; x=17 (in der Luecke zwischen den Spannen-Grenzpunkten [10,20], aber + ;; naeher an der GF-Grenze 20 als an der VF-Grenze 10 - eindeutig GF, kein + ;; Gleichstand wie bei x=15 in der Luecken-Mitte). + (setq _elem-vf (list 'PROXY1 'WRAPPER "STRECKENELEMENT" '(5.0 0.0 0.0))) + (setq _elem-gf (list 'PROXY2 'WRAPPER "STRECKENELEMENT" '(17.0 0.0 0.0))) + (setq _zuord (csv:element-unit-zuordnung _spans-proj _sep-proj (list _elem-vf _elem-gf))) + (tu-eq "csv:element-unit-zuordnung/vf-treffer" (list _elem-vf) (nth 0 _zuord)) + (tu-eq "csv:element-unit-zuordnung/gf-treffer" (list _elem-gf) (nth 1 _zuord)) + + ;; Sortierung innerhalb einer Spanne: zwei Elemente in der VF-Spanne, + ;; absichtlich in "falscher" Reihenfolge uebergeben - das Ergebnis muss + ;; nach x sortiert sein (frueheres x zuerst). + (setq _elem-a (list 'PROXY_A 'WRAPPER "BOGEN" '(8.0 0.0 0.0))) + (setq _elem-b (list 'PROXY_B 'WRAPPER "BOGEN" '(2.0 0.0 0.0))) + (setq _zuord2 (csv:element-unit-zuordnung _spans-proj _sep-proj (list _elem-a _elem-b))) + (tu-eq "csv:element-unit-zuordnung/sortiert" (list _elem-b _elem-a) (nth 0 _zuord2)) )