diff --git a/Lisp/ssg_core.lsp b/Lisp/ssg_core.lsp index d344015..53af850 100644 --- a/Lisp/ssg_core.lsp +++ b/Lisp/ssg_core.lsp @@ -1264,6 +1264,13 @@ ("L_VF_m" "0.000") ("ANTRIEBFAHRTRICHTUNG" "Auf") ("VF_WINKEL" "0") + ;; Hoehe je VF-SEGMENT in Metern (3 Nachkommastellen), kommagetrennt und + ;; Eintrag fuer Eintrag parallel zu L_VF_m/VF_WINKEL: Mittelwert aus KS_EIN + ;; und KS_AUS des jeweiligen geraden VF-Abschnitts (vf-vfhoehe-segment in + ;; vf_core.lsp, erfasst in vfs-vf-koerper). Zwischen Umlenk- und + ;; Motorstation koennen mehrere Abschnitte liegen (auf/ab/horizontal). + ;; Sivas-Merkmale 4433-4437 (bis zu fuenf Abschnitte je Gruppe). + ("VF_HOEHE_m" "0.000") ("VF_Bogen_A_90" "0") ("VF_Bogen_A_60" "0") ("VF_Bogen_A_30" "0") diff --git a/Lisp/vf_core.lsp b/Lisp/vf_core.lsp index 4afba48..63cf9f1 100644 --- a/Lisp/vf_core.lsp +++ b/Lisp/vf_core.lsp @@ -218,6 +218,102 @@ (defun vf-motorseite-aktuell ( / ) (if (member *vf-motorseite* '("links" "rechts")) *vf-motorseite* "rechts")) +;; ============================================================ +;; VF-HOEHE JE SEGMENT (Attribut VF_HOEHE_m, Sivas 4433-4437) +;; ============================================================ +;; Sivas erwartet je Streckengruppe bis zu fuenf Variofoerderer-Hoehen. Ein +;; Eintrag gehoert zu EINEM VF-Segment (Koerper) - nicht zu einer ganzen +;; Foerdereinheit und nicht zur ganzen Kette (Nutzer-Korrektur 2026-10-21): +;; +;; Hoehe = Mittelwert aus KS_EIN und KS_AUS DIESES Segments. +;; +;; Eine Kette, die durch Vario-Boegen in drei Abschnitte geteilt ist, bekommt +;; dadurch drei Werte - die Liste laeuft Eintrag fuer Eintrag parallel zu +;; L_VF_m, VF_WINKEL und ANTRIEBFAHRTRICHTUNG. +;; +;; Erfasst wird an genau EINER Stelle: vfs-vf-koerper baut ein Segment, kennt +;; dessen KS_EIN als Eingangspunkt und liefert dessen KS_AUS zurueck. Alle +;; Modi (Standard, Etage, Linienzug) bauen ihre VF-Koerper darueber, deshalb +;; muss der verzweigte Bau-Pfad selbst nicht angefasst werden. +;; +;; Einheit: Meter mit 3 Nachkommastellen (wie L_VF_m), kommagetrennt ueber +;; vfl-join-komma - Reihenfolge = Bau-Reihenfolge. +(if (not (boundp '*vf-acc-vfhoehe*)) (setq *vf-acc-vfhoehe* '())) + +(defun vf-vfhoehe-reset () + (setq *vf-acc-vfhoehe* '())) + +;; Ein VF-Segment erfassen: z-ein = Z des KS_EIN, z-aus = Z des KS_AUS (mm). +;; Fehlt einer der Werte, wird NICHTS angehaengt - sonst verschoebe sich die +;; Liste gegen L_VF_m/VF_WINKEL und im Export landete eine Hoehe beim falschen +;; Foerderer. +(defun vf-vfhoehe-segment (z-ein z-aus) + (if (and (numberp z-ein) (numberp z-aus)) + (setq *vf-acc-vfhoehe* + (append *vf-acc-vfhoehe* (list (vf-meter-3 (/ (+ z-ein z-aus) 2.0))))))) + +;; Bequemes Paar fuer die Bau-Pfade: vor dem Einbau den Startpunkt merken, +;; danach mit dem Endpunkt abschliessen. Beide geben ihren Punkt zurueck, +;; lassen sich also ohne zusaetzliche lokale Variable einsetzen: +;; +;; (vf-vfhoehe-merke pt) +;; (setq pt (insert-inclined-scaled-block ...)) +;; (vf-vfhoehe-schliesse pt) +;; +;; Noetig, weil die VF-Segmente NICHT alle ueber vfs-vf-koerper laufen: die +;; horizontalen Zwischenstuecke und die Modus-2-Fuellstuecke bauen direkt mit +;; insert-inclined-scaled-block. Wurde dort nicht erfasst, fehlten ihre +;; Hoehen und VF_HOEHE_m war kuerzer als L_VF_m (realer Befund 2026-10-21: +;; L_VF_m hatte drei Eintraege, VF_HOEHE_m nur einen). +;; +;; REGEL: Jede Stelle, die (vfl-acc-vf-seg ...) aufruft, MUSS auch eine Hoehe +;; erfassen - sonst verschiebt sich die Liste gegen L_VF_m/VF_WINKEL und im +;; Sivas-Export landet eine Hoehe beim falschen Foerderer. +(if (not (boundp '*vf-vfhoehe-merk*)) (setq *vf-vfhoehe-merk* nil)) + +(defun vf-vfhoehe-merke (pt) + (setq *vf-vfhoehe-merk* (if (listp pt) (caddr pt) nil)) + pt) + +(defun vf-vfhoehe-schliesse (pt) + (vf-vfhoehe-segment *vf-vfhoehe-merk* (if (listp pt) (caddr pt) nil)) + (setq *vf-vfhoehe-merk* nil) + pt) + +;; SONDERFALL Standard/Etage: Hoehe ueber die GANZE Kette. +;; +;; Diese beiden Typen haben nur EINEN Motor und nur EINEN geraden +;; VF-Abschnitt (Nutzer-Vorgabe 2026-10-21). Statt des Segmentwerts gilt dort +;; der Mittelwert der ganzen Anlage: +;; Anfang = KS_EIN des AS-Elements (Startpunkt der Anlage) +;; Ende = KS_AUS des ES-Elements (Rueckgabewert der Einfuegefunktion) +;; +;; ERSETZT die Liste bewusst, statt anzuhaengen: vfs-vf-koerper hat beim Bau +;; bereits den Segmentwert gesammelt, der hier nicht gelten soll. Ergebnis ist +;; genau ein Eintrag. +;; +;; Gerechnet wird aus der GEBAUTEN Geometrie, nicht aus der Soll-Vorgabe +;; (anders als MONTAGEHOEHE_m, dessen hoehe-bis aus deltaH abgeleitet ist). +;; Beide Hoehen in mm. +(defun vf-vfhoehe-kette (z-anfang z-ende) + (if (and (numberp z-anfang) (numberp z-ende)) + (setq *vf-acc-vfhoehe* (list (vf-meter-3 (/ (+ z-anfang z-ende) 2.0)))) + (setq *vf-acc-vfhoehe* '()))) + +;; Millimeter -> Meter als String mit GENAU 3 Nachkommastellen. +;; +;; (rtos x 2 3) allein genuegt nicht: je nach DIMZIN schneidet es nachfolgende +;; Nullen ab und liefert "2.5" statt "2.500" bzw. "-1" statt "-1.000" (real +;; beobachtet im Testlauf). Fuer eine Merkmalsliste ist die feste Stellenzahl +;; aber gewollt, darum wird hier aufgefuellt - unabhaengig von der +;; Zeichnungseinstellung. +(defun vf-meter-3 (mm / s pos) + (setq s (rtos (/ (float mm) 1000.0) 2 3)) + (if (not (vl-string-search "." s)) (setq s (strcat s "."))) + (setq pos (vl-string-search "." s)) + (while (< (- (strlen s) pos 1) 3) (setq s (strcat s "0"))) + s) + ;; ============================================================ ;; TEIL 5: KS_EIN/KS_AUS EXTRAKTION ;; ============================================================ @@ -977,6 +1073,9 @@ (cons "L_VF_m" (rtos (/ L_VF 1000.0) 2 3)) (cons "ANTRIEBFAHRTRICHTUNG" richtung) (cons "VF_WINKEL" (itoa best-winkel)) + ;; Hoehe der (einen) Foerdereinheit - beim Standard-/Etage-Typ + ;; fuellen vfs-vf-entry/-exit genau einen Listeneintrag. + (cons "VF_HOEHE_m" (vfl-join-komma *vf-acc-vfhoehe*)) (cons "ANZAHL_SEPARATOR" "2") (cons "ANZAHL_SCANNER" (itoa n-scanner)) (cons "GERUEST_EINZELMODUL" geruest-einzelmodul) @@ -1013,6 +1112,10 @@ horizontal-info winkelwahl best-winkel L_GF L_VF L_GF1 L_GF2 verteilung-modus n-scanner hoehe-von hoehe-bis lastEnt vf-insert vf-endpunkt staustrecke-basis) + ;; VF-Hoehen-Akkumulator pro Anlage zuruecksetzen - sonst erbte die naechste + ;; Anlage die Hoehen der vorherigen. (Im Linienzug erledigt das + ;; vfl-acc-reset; Standard/Etage laufen nicht darueber.) + (vf-vfhoehe-reset) (setq berechne-fn (vf-registry-berechne-fn typ)) (setq einfuege-fn (vf-registry-einfuege-fn typ)) (setq ergebnis (apply berechne-fn (list deltaL deltaH richtung seite))) @@ -1064,6 +1167,15 @@ (setq vf-endpunkt (apply einfuege-fn (list deltaL deltaH richtung best-winkel L_GF1 L_GF2 L_VF startpunkt seite hz))) + ;; VF_HOEHE_m fuer Standard/Etage: Mittelwert ueber die GANZE Kette + ;; (AS-KS_EIN = startpunkt, ES-KS_AUS = Rueckgabe der Einfuegefunktion), + ;; weil es hier nur einen Motor und nur einen geraden VF-Abschnitt + ;; gibt. Ersetzt den beim Bau gesammelten Segmentwert - siehe + ;; vf-vfhoehe-kette. Der Linienzug laeuft NICHT hier durch und behaelt + ;; daher seine Segmentliste. + (vf-vfhoehe-kette (caddr startpunkt) + (if (and vf-endpunkt (listp vf-endpunkt)) + (caddr vf-endpunkt) nil)) (setq vf-insert (vf-block-erstellen typ seite vf-nummer n-scanner hoehe-von hoehe-bis deltaH deltaL L_VF L_GF1 L_GF2 diff --git a/Lisp/vf_linienzug.lsp b/Lisp/vf_linienzug.lsp index b7127b8..2034576 100644 --- a/Lisp/vf_linienzug.lsp +++ b/Lisp/vf_linienzug.lsp @@ -82,7 +82,12 @@ ;; sichern): nil (normal) / "zurueck" (letztes Glied verwerfen und neu ;; fragen) / "abbrechen" (Bau hier stoppen, Baustand bleibt stehen). *vfl-wizard-abbruch-grund* nil - *vfl-abbruch-letztes-ent* nil)) + *vfl-abbruch-letztes-ent* nil) + ;; VF-Hoehen je SEGMENT (Attribut VF_HOEHE_m, Sivas 4433-4437). + ;; Die Liste fuellt vfs-vf-koerper beim Bau jedes geraden VF-Abschnitts + ;; (siehe vf-vfhoehe-segment in vf_core.lsp); hier wird sie - wie alle + ;; anderen Akkumulatoren - pro Kette zurueckgesetzt. + (vf-vfhoehe-reset)) ;; Alist-Zaehler erhoehen / lesen (defun vfl-inc-count (al key / e) @@ -857,10 +862,22 @@ (set_tile "setzen" "1") (set_tile "winkel_90" "1") (set_tile "seite_links" "1") + ;; Seitenauswahl ist nur beim 90-Grad-Element wirksam: beim 30-Grad- + ;; Element folgt die Seite aus der Zufuehrungsachse, die direkt nach + ;; diesem Dialog ueber einen zweiten Punkt abgefragt wird (Nutzer-Vorgabe + ;; 2026-10-14). Sie wird darum ausgegraut - und unten auch NICHT in die + ;; Pending-Queue gelegt, sonst zoege die Punktfrage sie als vermeintlichen + ;; Punkt heraus und erschiene gar nicht. (action_tile "setzen" "(mode_tile \"winkel_90\" (if (= (get_tile \"setzen\") \"1\") 0 1)) (mode_tile \"winkel_30\" (if (= (get_tile \"setzen\") \"1\") 0 1)) - (mode_tile \"seite_links\" (if (= (get_tile \"setzen\") \"1\") 0 1)) + (mode_tile \"seite_links\" (if (and (= (get_tile \"setzen\") \"1\") (/= (get_tile \"winkel_30\") \"1\")) 0 1)) + (mode_tile \"seite_rechts\" (if (and (= (get_tile \"setzen\") \"1\") (/= (get_tile \"winkel_30\") \"1\")) 0 1))") + (action_tile "winkel_30" + "(mode_tile \"seite_links\" 1) + (mode_tile \"seite_rechts\" 1)") + (action_tile "winkel_90" + "(mode_tile \"seite_links\" (if (= (get_tile \"setzen\") \"1\") 0 1)) (mode_tile \"seite_rechts\" (if (= (get_tile \"setzen\") \"1\") 0 1))") (action_tile "accept" "(setq gsetzen (get_tile \"setzen\")) @@ -877,7 +894,13 @@ ;; Winkel-Slot wird von vfl-in-value (AS-Winkel, ueber ;; vf-frage-element-winkel) konsumiert - der erwartet direkt ;; "30"/"90", NICHT den Auswahl-Index wie beim Seite-Slot. - (vflw-pending-push-all (list "1" gwinkel gseite)) + ;; Beim 30-Grad-Element wird die Seite NICHT mitgegeben: sie ist im + ;; Dialog ausgegraut und folgt aus der Zufuehrungsachse. Ein hier + ;; abgelegter Wert wuerde von der nachfolgenden Punktfrage + ;; faelschlich als Punkt konsumiert. + (if (= gwinkel "30") + (vflw-pending-push-all (list "1" gwinkel)) + (vflw-pending-push-all (list "1" gwinkel gseite))) (vflw-pending-push-all (list "2")))) ((= ergebnis 2) (setq *vfl-wizard-abbruch-grund* "zurueck")) (t (setq *vfl-wizard-abbruch-grund* "abbrechen")) @@ -3811,6 +3834,35 @@ ;; genommen (vlax-curve-*): fuer eine LINE ist das exakt die Linie selbst, fuer ;; eine Polylinie genau das angeklickte Segment - ohne Segment-Suche von Hand. ;; Rueckgabe: (pa pb) oder nil. +;; ============================================================ +;; AS-BASISLINIE: Zufuehrungsachse am Kettenanfang +;; ============================================================ +;; Der Startpunkt einer Kette mit AS-Element ist geometrisch KEINE Position, +;; sondern eine ACHSE: das AS sitzt an einer durchlaufenden Strecke (z.B. einer +;; Kreiselkette) und schleust daraus aus - seine KS_EIN-Achse MUSS auf dieser +;; Zufuehrungsachse liegen (Nutzer-Einsicht 2026-10-14, dieselbe wie beim +;; Endpunkt: "der Punkt ist eigentlich eine Linie"). +;; +;; Beim AS_Element_90 faellt das kaum auf, beim AS_Element_30 steht das Element +;; sichtbar falsch: vfl-insert-as-element richtet heute das KS_AUS entlang der +;; Kettenrichtung aus, wodurch das KS_EIN um den Blockschwenk verdreht steht. +;; +;; Auf welcher Seite der Zufuehrung das AS ausschleust, ergibt sich dann aus der +;; Lage des naechsten Kettenpunkts zur Basislinie - es muss nicht gefragt werden +;; (Nutzer-Entscheidung 2026-10-14). Im Punkt-Modus bleibt die Seitenfrage +;; dagegen unveraendert erhalten. +;; +;; Reine Rechnung, in tests/test_unit.lsp getestet: +;; Vorzeichen des 2D-Kreuzprodukts (p2-p1) x (ziel-p1). +;; > 0 -> ziel liegt LINKS der Richtung p1->p2 +;; < 0 -> rechts +;; Genau auf der Linie (0) gilt als "links" (willkuerlich, aber deterministisch - +;; der Aufrufer sollte diesen Fall ohnehin nicht erzeugen). +(defun vfl-seite-aus-lage (p1 p2 ziel / kr) + (setq kr (vfl-kreuz2 (- (car p2) (car p1)) (- (cadr p2) (cadr p1)) + (- (car ziel) (car p1)) (- (cadr ziel) (cadr p1)))) + (if (< kr 0.0) "rechts" "links")) + ;; Ziellinie durch ZWEI selbst gesetzte Punkte definieren. ;; ;; Allgemeiner als das Anklicken eines vorhandenen Objekts (Nutzer-Idee @@ -5063,7 +5115,10 @@ (if sep-vor (setq pt (vfl-sep-hz pt hz))) ;; horizontale Zwischenstrecke (0 Grad) (princ (ssg-textf "vfl-horizontale-zwischenstrecke" (list (rtos dL 2 2)))) + ;; Hoehe dieses (horizontalen) VF-Segments erfassen - siehe vf-vfhoehe-merke. + (vf-vfhoehe-merke pt) (setq pt (insert-inclined-scaled-block "Staustrecke_SP_1000_mm" pt dL 0 hz)) + (vf-vfhoehe-schliesse pt) (vfl-acc-vf-seg "horizontal" 0 dL) ;; optionaler Separator NACH (jetzt tatsaechlich bauen) (if sep-nach (setq pt (vfl-sep-hz pt hz))) @@ -5587,9 +5642,11 @@ ;; (Ausgang in vfl-vf-einheit) erledigt den einzigen wirklich ;; noetigen Bogen. ((and schon-flach (= typ "VF") (= w 0) lvf (> lvf 1.0)) + (vf-vfhoehe-merke (car frame)) (setq frame (make-frame-from-dir (insert-inclined-scaled-block "Staustrecke_SP_1000_mm" (car frame) lvf 0 hzn) (hz-winkel->xu hzn 0.0))) + (vf-vfhoehe-schliesse (car frame)) (vfl-acc-vf-seg "horizontal" 0 lvf) (setq cnt (1+ cnt))) ;; Normalfall: gewinkelter ODER horizontaler Koerper - vfs-vf-koerper @@ -6508,10 +6565,53 @@ ;; die noetige Restlaenge ist damit einfach die urspruengliche Distanz MINUS ;; die tatsaechliche (gemessene) AS-Fussabdrucklaenge, keine Punkt-Projektion ;; noetig (Ersatz fuer das fruehere vfl-projiziere-distanz auf pick-punkt). -(defun vfl-kettenanfang-baustein (p-aktuell hz-neu deltaL as-seite as-vorhanden / frame fussabdruck) +;; AS-Element so einfuegen, dass seine KS_EIN-ACHSE auf der Zufuehrungsachse +;; (ein-hz) liegt - Gegenstueck zu vfl-insert-as-element, das stattdessen das +;; KS_AUS entlang der Kettenrichtung ausrichtet. +;; +;; Unterschied in einer Zeile: dort wird der Blockschwenk abgezogen +;; (ein-hz = hz - as-turn), hier NICHT - die uebergebene Richtung IST schon die +;; des KS_EIN. Das KS_AUS zeigt danach um den Blockschwenk gedreht weiter, also +;; beim AS_Element_30 um 30 Grad von der Zufuehrung weg: genau das reale +;; Ausschleusen aus einer durchlaufenden Strecke (Nutzer-Einsicht 2026-10-14). +(defun vfl-insert-as-element-auf-achse (typ startpunkt ein-hz as-seite / rad-h xu-ein) + (setq rad-h (* (float ein-hz) (/ pi 180.0))) + (setq xu-ein (list (cos rad-h) (sin rad-h) 0.0)) + (insert-block-mixed-to-ks + (strcat "AS_Element_" (vfl-as-winkel) "_" as-seite) + (make-frame-from-dir startpunkt xu-ein) + (caddr startpunkt) nil "KS_EIN") +) + +(defun vfl-kettenanfang-baustein (p-aktuell hz-neu deltaL as-seite as-vorhanden + / frame fussabdruck ziel rad) (if as-vorhanden (progn - (setq frame (vfl-insert-as-element "GF" p-aktuell hz-neu 0.0 as-seite)) + ;; BASISLINIEN-Modus: das AS gehoert mit seinem KS_EIN auf die + ;; Zufuehrungsachse (*vfl-as-basis-hz*), nicht mit dem KS_AUS auf die + ;; Kettenrichtung. Die Seite ergibt sich aus der Lage des naechsten + ;; Kettenpunkts zur Basislinie (Nutzer-Vorgabe 2026-10-14): der liegt in + ;; Richtung hz-neu, deltaL entfernt. + (if *vfl-as-basis-hz* + (progn + (setq rad (* (float hz-neu) (/ pi 180.0))) + (setq ziel (list (+ (car p-aktuell) (* deltaL (cos rad))) + (+ (cadr p-aktuell) (* deltaL (sin rad))) + (caddr p-aktuell))) + (setq as-seite (vfl-seite-aus-lage *vfl-as-basis-p1* + (list (+ (car *vfl-as-basis-p1*) + (cos (* *vfl-as-basis-hz* (/ pi 180.0)))) + (+ (cadr *vfl-as-basis-p1*) + (sin (* *vfl-as-basis-hz* (/ pi 180.0)))) + 0.0) + ziel)) + ;; Masse auf die jetzt bekannte Seite umstellen (Winkel steht schon). + (vf-set-as-masse (vfl-as-winkel) as-seite) + (princ (ssg-textf "vfl-as-seite-ermittelt" (list as-seite))) + ;; KS_EIN auf die Zufuehrungsachse legen - NICHT KS_AUS auf hz-neu. + (setq frame (vfl-insert-as-element-auf-achse "GF" p-aktuell + *vfl-as-basis-hz* as-seite))) + (setq frame (vfl-insert-as-element "GF" p-aktuell hz-neu 0.0 as-seite))) (setq fussabdruck (vfl-planar-dist p-aktuell (car frame))) (setq deltaL (- deltaL fussabdruck)) (princ (ssg-textf "vfl-info-as-eingefuegt" (list (rtos deltaL 2 1)))) @@ -6595,6 +6695,83 @@ (vf-set-as-masse *vfl-as-winkel* as-seite) ; Masse fuer Variante as-seite) +;; AS-Winkel abfragen und danach ENTWEDER die Seite ODER - nur beim 30-Grad- +;; Element - eine Zufuehrungsachse (Nutzer-Vorgabe 2026-10-14: "bitte erstmal +;; nur fuer 30 Grad"). +;; +;; Warum erst NACH dem Winkel: die Achsenfrage gilt nur fuer AS_Element_30, der +;; Winkel muss also schon feststehen. Der Kettenstartpunkt bleibt davon +;; unberuehrt - er ist und bleibt der echte Sitz des AS-Elements; die Achse +;; entsteht aus ihm plus EINEM weiteren Punkt, der nur die Richtung gibt. +;; +;; JOURNAL - deswegen ist die Reihenfolge so gewaehlt: +;; 90 Grad : [STR winkel] [STR seite] <- Eintrag fuer Eintrag wie bisher +;; 30 + Seite : [STR winkel] [STR seite] <- ebenfalls wie bisher +;; 30 + Achse : [STR winkel] [PT p2] +;; Die Modus-Wahl selbst wird BEWUSST NICHT journalisiert: sie ist aus der Art +;; des naechsten Eintrags eindeutig rekonstruierbar (PT = Achse, STR = Seite). +;; Dadurch bleiben alle aelteren Ketten ohne Sonderbehandlung editierbar. +;; +;; Setzt *vfl-as-basis-hz*/-p1 (nil, wenn keine Achse gewaehlt wurde). +;; Rueckgabe: Seite. Im Achsen-Modus vorlaeufig "links" - die endgueltige Seite +;; folgt aus der Lage des naechsten Kettenpunkts und wird in +;; vfl-kettenanfang-baustein gesetzt (Laengsverbrauch ist seitengleich, nur das +;; Vorzeichen des Querversatzes dreht). +(defun vfl-frage-as-winkel-seite-oder-achse (seiten-headerkey startpunkt + / antwort as-seite modus p2) + (setq *vfl-as-basis-hz* nil *vfl-as-basis-p1* nil) + (princ (ssg-text "vf-winkel-aus-header")) + (princ (ssg-text "vf-winkel-90")) + (princ (ssg-text "vf-winkel-30")) + (setq *vfl-as-winkel* + (vfl-in-value "STR" + (function (lambda () (vf-frage-element-winkel "vf-winkel-aus-header"))))) + ;; Beim 30-Grad-Element wird die Seite NICHT gefragt (im Gruppendialog + ;; ausgegraut) - stattdessen direkt die Zufuehrungsachse ueber einen zweiten + ;; Punkt. Es gibt also keine Modus-Frage: 30 Grad = Achse, 90 Grad = Seite. + ;; + ;; AUSNAHME Replay: aeltere 30-Grad-Ketten wurden noch MIT Seitenfrage + ;; gebaut, ihr Journal hat an dieser Stelle ein "STR" statt eines "PT". + ;; Steht dort ein STR, ist es ein Altbestand und es wird wie frueher die + ;; Seite gelesen - sonst liefe die Queue aus dem Takt. + (setq modus (if (= *vfl-as-winkel* "30") "1" "2")) + (if (and (= modus "1") *vfl-replay-queue* (/= (vfl-replay-peek-art) "PT")) + (setq modus "2")) + (if (= modus "1") + (progn + ;; Der AS-Gruppendialog legt beim 30-Grad-Element bewusst KEINE Seite in + ;; die Pending-Queue (siehe vflw-gruppe-as-impl) - die Queue ist hier + ;; also leer und der folgende vfl-in-point fragt wirklich nach dem Punkt. + ;; Zweiter Punkt gibt NUR die Richtung der Zufuehrung; der Startpunkt + ;; bleibt der Sitz des AS-Elements. + (setq p2 (vfl-in-point startpunkt (ssg-text "vfl-prompt-as-achse-p2"))) + (if (or (null p2) + (and (< (abs (- (car p2) (car startpunkt))) 1e-9) + (< (abs (- (cadr p2) (cadr startpunkt))) 1e-9))) + (progn + ;; Keine brauchbare Richtung -> auf die Seitenfrage zurueckfallen. + ;; (Der PT-Eintrag steht dann trotzdem im Journal und wird beim + ;; Replay wieder als "Achse" gelesen - deshalb hier KEIN stiller + ;; Rueckfall, sondern Abbruch.) + (princ (ssg-text "vfl-basislinie-punkte-gleich")) + (exit)) + (progn + (setq *vfl-as-basis-p1* startpunkt) + (setq *vfl-as-basis-hz* (vfl-planar-hz startpunkt p2)) + (princ (ssg-textf "vfl-basislinie-info" (list (rtos *vfl-as-basis-hz* 2 1)))) + (setq as-seite "links"))) ; vorlaeufig, s.o. + ) + (progn + (princ (ssg-text seiten-headerkey)) + (princ (ssg-text "gf-seite-links")) + (princ (ssg-text "gf-seite-rechts")) + (setq antwort (vfl-menu (ssg-text "prompt-wahl-1-2") + (list (ssg-text "gf-seite-links") (ssg-text "gf-seite-rechts")) 1 + seiten-headerkey)) + (setq as-seite (if (= antwort "2") "rechts" "links")))) + (vf-set-as-masse *vfl-as-winkel* as-seite) + as-seite) + ;; ES-Winkel (30/90) + Seite abfragen - MARKER-FREIE Schale fuer die Modi 2/3, ;; die das ES-Element als eigene Praeambel (Start/Ziel bereits fest) abfragen ;; und ihr eigenes Journal-/Masse-Schema fuehren. Unterscheidet sich bewusst @@ -6731,6 +6908,9 @@ (cons "L_VF_m" (vfl-join-komma *vfl-acc-lvf*)) (cons "ANTRIEBFAHRTRICHTUNG" (vfl-join-komma *vfl-acc-richtung*)) (cons "VF_WINKEL" (vfl-join-komma *vfl-acc-winkel*)) + ;; Hoehe je Foerdereinheit (Mittelwert Umlenk-KS_EIN / Motor-KS_AUS), + ;; Meter mit 3 Nachkommastellen - Sivas 4433-4437. + (cons "VF_HOEHE_m" (vfl-join-komma *vf-acc-vfhoehe*)) ;; Vario-Kurven (Richtungswechsel im VF-Teil), A=aussen / I=innen (cons "VF_Bogen_A_90" (itoa (vfl-get-count *vfl-acc-variokurve* "A_90"))) (cons "VF_Bogen_A_60" (itoa (vfl-get-count *vfl-acc-variokurve* "A_60"))) @@ -6995,9 +7175,12 @@ ;; werden - sonst wuerde ein WINKEL_AS-Attribut den Wert der VORHERIGEN Kette ;; tragen, wenn diesmal kein AS gewaehlt wird. (setq *vfl-as-winkel* nil *vfl-es-winkel* nil) + ;; Winkel, dann Seite ODER (nur bei 30 Grad) eine Zufuehrungsachse - siehe + ;; vfl-frage-as-winkel-seite-oder-achse. Im Achsen-Modus entfaellt die + ;; Seitenfrage; die Seite folgt aus der Lage des naechsten Kettenpunkts und + ;; wird in vfl-kettenanfang-baustein bestimmt. (if as-vorhanden - (setq as-seite (vfl-frage-as-winkel-seite "vfl-aus-seite-header")) - ) + (setq as-seite (vfl-frage-as-winkel-seite-oder-achse "vfl-aus-seite-header" startpunkt))) ;; ES-Seite wird erst am Kettenende abgefragt (siehe vfl-frage-es-seite), ;; da das ES-Element erst dort eingefuegt wird. @@ -10650,8 +10833,10 @@ (progn (setq pt (vfl3-flach-ein pt letzt-koerper-hz)) (setq flat-p t) ;; Fueller-A: der im Kletter-Segment durch 1/2 GF1 frei werdende Platz (if (and (> filler-A-len 0.1) (not filler-a-done)) - (progn (setq pt (insert-inclined-scaled-block "Staustrecke_SP_1000_mm" + (progn (vf-vfhoehe-merke pt) + (setq pt (insert-inclined-scaled-block "Staustrecke_SP_1000_mm" pt filler-A-len 0 letzt-koerper-hz)) + (vf-vfhoehe-schliesse pt) (vfl-acc-vf-seg "horizontal" 0 filler-A-len) (setq anzahl-vf (1+ anzahl-vf) filler-a-done t))))) (if (= i vf-ende) @@ -10684,7 +10869,9 @@ "vfl-m3-warnung-fueller-ausgang-zu-kurz") (list (rtos fill-len 2 0) (rtos *vfk-restlaenge-min-clamp* 2 0))))) (setq fill-len (max *vfk-restlaenge-min-clamp* fill-len)) + (vf-vfhoehe-merke pt) (setq pt (insert-inclined-scaled-block "Staustrecke_SP_1000_mm" pt fill-len 0 seg-hz)) + (vf-vfhoehe-schliesse pt) (vfl-acc-vf-seg "horizontal" 0 fill-len) (setq anzahl-vf (1+ anzahl-vf)) (setq letzt-koerper-hz seg-hz)) ;; --- Vario-Kurve (flach, direkt bei 0 Grad) --- @@ -10693,8 +10880,10 @@ (progn (setq pt (vfl3-flach-ein pt letzt-koerper-hz)) (setq flat-p t) ;; Fueller-A: der im Kletter-Segment durch 1/2 GF1 frei werdende Platz (if (and (> filler-A-len 0.1) (not filler-a-done)) - (progn (setq pt (insert-inclined-scaled-block "Staustrecke_SP_1000_mm" + (progn (vf-vfhoehe-merke pt) + (setq pt (insert-inclined-scaled-block "Staustrecke_SP_1000_mm" pt filler-A-len 0 letzt-koerper-hz)) + (vf-vfhoehe-schliesse pt) (vfl-acc-vf-seg "horizontal" 0 filler-A-len) (setq anzahl-vf (1+ anzahl-vf) filler-a-done t))))) (setq bwinkel (nth 3 seg) bseite (nth 4 seg) kv-variante (nth 6 seg)) @@ -11230,6 +11419,12 @@ (setq gemessen (vfl-kette-neigung (vfl-kette-rec-ein entry-info) (vfl-kette-rec-aus rec))) (setq betrag (vfl-kette-round (abs gemessen))) (setq richtung (if (< gemessen 0.0) "Auf" "Ab")))) + ;; Hoehe dieses Segments aus DENSELBEN Punkten, aus denen auch + ;; die Laenge kommt (Ende des Eingangsbogens bis Anfang des + ;; Ausgangsbogens) - dadurch bleiben VF_HOEHE_m und L_VF_m auch + ;; beim Rekonstruieren aus bestehenden Bloecken gleich lang. + (vf-vfhoehe-segment (caddr (vfl-kette-rec-aus entry-info)) + (caddr (vfl-kette-rec-ein rec))) (vfl-acc-vf-seg richtung betrag (distance (vfl-kette-rec-aus entry-info) (vfl-kette-rec-ein rec))) (setq entry-info nil) @@ -11286,6 +11481,8 @@ (cons "L_VF_m" (vfl-join-komma *vfl-acc-lvf*)) (cons "ANTRIEBFAHRTRICHTUNG" (vfl-join-komma *vfl-acc-richtung*)) (cons "VF_WINKEL" (vfl-join-komma *vfl-acc-winkel*)) + ;; Hoehe je Foerdereinheit - siehe oben. + (cons "VF_HOEHE_m" (vfl-join-komma *vf-acc-vfhoehe*)) (cons "VF_Bogen_A_90" (itoa (vfl-get-count *vfl-acc-variokurve* "A_90"))) (cons "VF_Bogen_A_60" (itoa (vfl-get-count *vfl-acc-variokurve* "A_60"))) (cons "VF_Bogen_A_30" (itoa (vfl-get-count *vfl-acc-variokurve* "A_30"))) diff --git a/Lisp/vf_standard.lsp b/Lisp/vf_standard.lsp index 658bf1f..53f3d9d 100644 --- a/Lisp/vf_standard.lsp +++ b/Lisp/vf_standard.lsp @@ -612,9 +612,15 @@ ;; beliebig aneinanderreihen (siehe doc/VarioFoerderer_Linienzug_Prinzipien.md). ;; best-winkel=0 => horizontales Sub-Segment (Boegen fest auf_3/ab_3). (defun vfs-vf-koerper (aktueller-punkt richtung best-winkel L_VF hz / - bogen-name bogen-mass bogen-dx bogen-dz) + bogen-name bogen-mass bogen-dx bogen-dz z-ein) (if (null hz) (setq hz 0.0)) (setq hz (float hz)) + ;; Hoehe DIESES VF-Segments (Attribut VF_HOEHE_m, Sivas 4433-4437): + ;; KS_EIN ist der Punkt beim Eintritt, KS_AUS der Rueckgabewert am Ende - + ;; der Mittelwert beider wird unten angehaengt. Eine Kette mit drei durch + ;; Vario-Boegen getrennten Abschnitten bekommt dadurch drei Werte, parallel + ;; zu L_VF_m und VF_WINKEL (Nutzer-Vorgabe 2026-10-21). + (setq z-ein (caddr aktueller-punkt)) ;; 5. 1. Vertikalbogen ;; Sonderfall best-winkel=0 (horizontale Zwischenstrecke, siehe @@ -717,6 +723,8 @@ ) ) + ;; Segment-Hoehe anhaengen: Mittelwert aus KS_EIN (z-ein) und KS_AUS. + (vf-vfhoehe-segment z-ein (caddr aktueller-punkt)) aktueller-punkt ) diff --git a/cfg/Merkmalsstamm_in_Sivas.xlsx b/cfg/Merkmalsstamm_in_Sivas.xlsx index 0dd6ba7..7d6c57e 100644 Binary files a/cfg/Merkmalsstamm_in_Sivas.xlsx and b/cfg/Merkmalsstamm_in_Sivas.xlsx differ diff --git a/lang/de_DE.json b/lang/de_DE.json index 2ed8a48..24ddeb7 100644 --- a/lang/de_DE.json +++ b/lang/de_DE.json @@ -99,6 +99,10 @@ "vfl-es-setzen-frage": "\n\nES-Element setzen?", "vfl-es-nein-zielpunkt": "\n 2 - Nein (Kette endet direkt am Zielpunkt)", "vfl-prompt-endpunkt-fahrtrichtung": "\n\nEndpunkt entlang Fahrtrichtung waehlen (bestimmt die Laenge): ", + "vfl-prompt-as-achse-p2": "\n\nZufuehrungsachse - 2. Punkt (gibt nur die Richtung, der Startpunkt bleibt): ", + "vfl-basislinie-punkte-gleich": "\n Die beiden Punkte sind identisch - keine Richtung bestimmbar. Abbruch.", + "vfl-basislinie-info": "\n Zufuehrungsachse: %1 Grad. Die AS-Seite ergibt sich aus der Lage des naechsten Punkts.", + "vfl-as-seite-ermittelt": "\n AS-Seite aus der Lage des naechsten Punkts: %1", "vfl-endpunkt-modus-header": "\n\nWie soll der Endpunkt bestimmt werden?", "vfl-endpunkt-modus-opt-linie": "\n 1 - Ziellinie zeichnen (2 Punkte) - der Schnittpunkt bestimmt die Laenge", "vfl-prompt-ziellinie-p1": "\n\nZiellinie - 1. Punkt (beliebige Richtung, Z wird ignoriert): ", diff --git a/lang/en_GB.json b/lang/en_GB.json index 238f66b..4f82c0d 100644 --- a/lang/en_GB.json +++ b/lang/en_GB.json @@ -99,6 +99,10 @@ "vfl-es-setzen-frage": "\n\nPlace ES element?", "vfl-es-nein-zielpunkt": "\n 2 - No (chain ends directly at the target point)", "vfl-prompt-endpunkt-fahrtrichtung": "\n\nSelect end point along travel direction (defines the length): ", + "vfl-prompt-as-achse-p2": "\n\nFeed axis - 2nd point (defines the direction only, the start point stays): ", + "vfl-basislinie-punkte-gleich": "\n The two points are identical - no direction can be derived. Aborting.", + "vfl-basislinie-info": "\n Feed axis: %1 degrees. The AS side follows from the position of the next point.", + "vfl-as-seite-ermittelt": "\n AS side from the position of the next point: %1", "vfl-endpunkt-modus-header": "\n\nHow should the end point be determined?", "vfl-endpunkt-modus-opt-linie": "\n 1 - Draw target line (2 points) - the intersection defines the length", "vfl-prompt-ziellinie-p1": "\n\nTarget line - 1st point (any direction, Z is ignored): ", diff --git a/lib/export_sivas.py b/lib/export_sivas.py index 26cdd36..a0d9fcc 100644 --- a/lib/export_sivas.py +++ b/lib/export_sivas.py @@ -103,6 +103,10 @@ GERUEST_TYP_ANZEIGE = sivas_mapping.load_flat_mapping("geruest_typ_anzeige") SEPARATOR_BEARBEITUNG_ANZEIGE = "ohne HARTCOAT HRC 50 ± 5" +# Sivas-Merkmale "Streckengruppe - Variofoerderer 001..005 - Hoehe in Meter". +# Reihenfolge = Bau-Reihenfolge der Foerdereinheiten in VF_HOEHE_m. +VF_HOEHE_MERKMALE = ("4433", "4434", "4435", "4436", "4437") + def geruest_typ_anzeige(wert): """Wandelt den ASCII-Attributwert GERUEST_TYP in die Sivas-Anzeige um.""" @@ -379,6 +383,20 @@ def build_variofoerderer_details(block): details["4354"] = vf_richtung[0].lower() if vf_richtung else "" details["4386"] = str(int(vf_winkel[0])) if vf_winkel else "" + # Hoehe je Foerdereinheit (4433-4437, "Variofoerderer 001-005 - Hoehe in + # Meter"). Quelle ist das Attribut VF_HOEHE_m: eine kommagetrennte Liste + # in Bau-Reihenfolge, je Eintrag der Mittelwert aus KS_EIN der + # Umlenkstation und KS_AUS der Motorstation (siehe vf-vfhoehe-anfang/-ende + # in Lisp/vf_core.lsp). Einheit Meter, 3 Nachkommastellen - wie L_VF_m. + # + # Mehr als fuenf Foerdereinheiten kann Sivas hier nicht abbilden; die + # ueberzaehligen werden bewusst weggelassen statt zusammengefasst, damit + # kein Merkmal einen erfundenen Wert traegt. Fehlende Eintraege bleiben + # ganz weg (kein leeres Merkmal). + vf_hoehen = parse_float_list(attribs.get("VF_HOEHE_m", "")) + for nummer, hoehe in zip(VF_HOEHE_MERKMALE, vf_hoehen): + details[nummer] = "%.3f" % hoehe + return {k: str(v) for k, v in details.items()} diff --git a/tests/test_unit.lsp b/tests/test_unit.lsp index 41c6d23..fec355e 100644 --- a/tests/test_unit.lsp +++ b/tests/test_unit.lsp @@ -446,6 +446,86 @@ (+ (cadr _ks) (sin (/ pi 6.0))) 0.0))) (tu-true "vfl-laenge-bis-ziellinie/realfall-plausibel" (and _l (> _l 1430.0) (< _l 1431.0))) + + ;; --- AS-Seite aus der Lage des naechsten Punkts zur Basislinie --- + ;; Basislinie zeigt nach +X; ein Ziel mit +Y liegt links davon, mit -Y rechts. + (tu-eq "vfl-seite-aus-lage/links" + "links" (vfl-seite-aus-lage '(0.0 0.0 0.0) '(1000.0 0.0 0.0) '(500.0 800.0 0.0))) + (tu-eq "vfl-seite-aus-lage/rechts" + "rechts" (vfl-seite-aus-lage '(0.0 0.0 0.0) '(1000.0 0.0 0.0) '(500.0 -800.0 0.0))) + ;; Richtung der Basislinie umdrehen -> Seite dreht mit + (tu-eq "vfl-seite-aus-lage/basislinie-umgedreht" + "rechts" (vfl-seite-aus-lage '(1000.0 0.0 0.0) '(0.0 0.0 0.0) '(500.0 800.0 0.0))) + ;; schraege Basislinie (45 Grad): Ziel oberhalb davon liegt links + (tu-eq "vfl-seite-aus-lage/schraeg" + "links" (vfl-seite-aus-lage '(0.0 0.0 0.0) '(1000.0 1000.0 0.0) '(0.0 1000.0 0.0))) + ;; Z spielt keine Rolle (rein planare Entscheidung) + (tu-eq "vfl-seite-aus-lage/z-ignoriert" + "rechts" (vfl-seite-aus-lage '(0.0 0.0 5000.0) '(1000.0 0.0 -200.0) '(500.0 -800.0 999.0))) + + ;; --- VF-Hoehe je SEGMENT (Attribut VF_HOEHE_m, Sivas 4433-4437) --- + ;; Ein Eintrag je GERADEM VF-Abschnitt: Mittelwert aus dessen KS_EIN und + ;; KS_AUS, in METERN mit 3 Nachkommastellen. Zwischen Umlenk- und + ;; Motorstation koennen mehrere Abschnitte liegen (auf/ab/horizontal), die + ;; Liste laeuft darum parallel zu L_VF_m und VF_WINKEL. + (vf-vfhoehe-reset) + (tu-eq "vf-vfhoehe/leer-nach-reset" "" (vfl-join-komma *vf-acc-vfhoehe*)) + + ;; 2000 mm und 3000 mm -> Mittel 2500 mm -> "2.500" + (vf-vfhoehe-segment 2000.0 3000.0) + (tu-eq "vf-vfhoehe/mittelwert-meter" "2.500" (vfl-join-komma *vf-acc-vfhoehe*)) + + ;; Drei Abschnitte wie im realen Block (L_VF_m hatte dort 3 Eintraege): + ;; horizontal, horizontal, steigend - alle drei bekommen einen Wert. + (vf-vfhoehe-reset) + (vf-vfhoehe-segment 2076.0 2076.0) ; horizontal + (vf-vfhoehe-segment 2076.0 2076.0) ; horizontal + (vf-vfhoehe-segment 2076.0 2600.0) ; steigend -> Mittel 2338 + (tu-eq "vf-vfhoehe/drei-abschnitte" + "2.076,2.076,2.338" (vfl-join-komma *vf-acc-vfhoehe*)) + + ;; Unvollstaendige Messung haengt NICHTS an - sonst verschoebe sich die + ;; Liste gegen L_VF_m und eine Hoehe landete beim falschen Foerderer. + (vf-vfhoehe-reset) + (vf-vfhoehe-segment 1000.0 2000.0) + (vf-vfhoehe-segment nil 3000.0) + (vf-vfhoehe-segment 4000.0 nil) + (tu-eq "vf-vfhoehe/unvollstaendig-ignoriert" "1.500" (vfl-join-komma *vf-acc-vfhoehe*)) + + ;; negative Hoehen (Kette unter Null) muessen durchgehen + (vf-vfhoehe-reset) + (vf-vfhoehe-segment -500.0 -1500.0) + (tu-eq "vf-vfhoehe/negativ" "-1.000" (vfl-join-komma *vf-acc-vfhoehe*)) + (vf-vfhoehe-reset) + + ;; vf-meter-3: GENAU 3 Nachkommastellen, unabhaengig von DIMZIN. + ;; (rtos x 2 3) allein kuerzt nachfolgende Nullen ("2.5", "-1") - real im + ;; Testlauf aufgetreten, deshalb eigene Formatierung. + (tu-eq "vf-meter-3/glatt" "2.500" (vf-meter-3 2500.0)) + (tu-eq "vf-meter-3/ganzzahlig" "3.000" (vf-meter-3 3000.0)) + (tu-eq "vf-meter-3/negativ" "-1.000" (vf-meter-3 -1000.0)) + (tu-eq "vf-meter-3/null" "0.000" (vf-meter-3 0.0)) + (tu-eq "vf-meter-3/drei-stellen" "1.234" (vf-meter-3 1234.0)) + + ;; --- SONDERFALL Standard/Etage: Hoehe ueber die GANZE Kette --- + ;; Nur ein Motor und nur ein gerader VF-Abschnitt, darum zaehlt dort + ;; AS-KS_EIN .. ES-KS_AUS statt des Segmentwerts (Nutzer-Vorgabe 2026-10-21). + (vf-vfhoehe-reset) + (vf-vfhoehe-kette 1000.0 3000.0) + (tu-eq "vf-vfhoehe-kette/mittelwert" "2.000" (vfl-join-komma *vf-acc-vfhoehe*)) + + ;; ERSETZT den beim Bau gesammelten Segmentwert, haengt ihn NICHT an - + ;; sonst stuenden fuer eine Standard-Anlage zwei Hoehen in der Liste. + (vf-vfhoehe-reset) + (vf-vfhoehe-segment 5000.0 5000.0) ; Segmentwert aus vfs-vf-koerper + (vf-vfhoehe-kette 1000.0 3000.0) + (tu-eq "vf-vfhoehe-kette/ersetzt-segmentwert" "2.000" (vfl-join-komma *vf-acc-vfhoehe*)) + + ;; fehlender Endpunkt -> lieber leer als geraten + (vf-vfhoehe-reset) + (vf-vfhoehe-kette 1000.0 nil) + (tu-eq "vf-vfhoehe-kette/ohne-endpunkt-leer" "" (vfl-join-komma *vf-acc-vfhoehe*)) + (vf-vfhoehe-reset) )