diff --git a/CLAUDE.md b/CLAUDE.md index 63ff4e1..9712b9f 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -195,7 +195,7 @@ Abhaengigkeiten: `ezdxf`, Standard-Library. Benoetigen Umgebungsvariablen (`DXFM | `dxf_vf_abbild.py` | Abbild der `VF_`/`GF_`-Wrapperbloecke einer Testzeichnung: Attribute UND Bausteinzusammensetzung je Kette, Ketten ueber den Einfuegepunkt der Spec zugeordnet (der Blockname taugt nicht - die VF-Nummer beginnt in jeder Zeichnung neu bei 1). Regressionsnetz fuer Umbauten am Bau-Ablauf: `--referenz` vergleicht gegen `tests/reference/hundm05_attribute.json` (Exit 1 bei Abweichung), `--schreibe-referenz` frischt sie nach einem GEPRUEFTEN Lauf auf. Faengt genau das, was der Ergebnis-Record nicht sieht - eine vertauschte `vfl-acc-*`-Reihenfolge (Komma-Listen `L_VF_m`/`L_GF_m`/`GF_WINKEL`, in der Zeichnung unsichtbar, im Sivas-Export falsch) und eine falsche Blockvariante. Liest ASCII-DXF streamend; binaere DXF kann `dxf_scan_components.py`. Genutzt von `tests/test_vf_geometrie.py` | | `vf_spec_export.py` | Uebersetzt ein Eingabe-Journal in eine **Spec** und zurueck (`spec_aus_journal` / `journal_aus_spec`) - Gegenstueck zu `vsp-journal` in `Lisp/vf_spec.lsp`, beide muessen dieselbe Tokenfolge liefern. Der Rundlauf `journal_aus_spec(spec_aus_journal(tok)) == tok` laeuft in `tests/test_vf_spec.py` gegen die 5 echten HundM-Ketten und beweist, dass die Spec das Journal verlustfrei abbildet. Schreibt das flache Spec-JSON (vom LISP-Parser lesbar). Objekte der Ziel-Datei, die keine Kette beschreiben (von Hand ergaenzte Anlagenteile mit `function`, z.B. Kreisel), werden beim Neuschreiben unveraendert ans Dateiende uebernommen - sonst waeren sie beim naechsten Lauf weg. Aufruf: `python vf_spec_export.py ` — erzeugt `tests/testdata/hundm05.json` (Spec, Eingabe fuer `TEST_HUNDM05`/`VF_SPEC_BAU`) aus `tests/testdata/hm_recformat.json` (Aufzeichnung) | | `dxf_anlage_export.py` | Gegenstueck zum Scanner: liest eine mit unseren Makros GEBAUTE (und von Hand nachbearbeitete) Anlage aus der Zeichnung zurueck in Nachbau-Testdaten - je Wrapper genau die Parameter, die auch dessen Edit-Befehl liest (KREISEL_n: Einfuegepunkt, `abstand` exakt aus der Blockdefinition; GF_n: SSG_GF_EDIT + SEITE_AS/ES, endet die GF auf einer Kreisel-Kette, wird dessen Hoehe `hoehe_ende_mm`; VF_n `standard`/`etage`: mubea-Schema wie `vfs-edit-basis-lesen`; VF_n `linienzug`: Journal UNVERAENDERT als `"typ": "linienzug_journal"` - bewusst keine Spec, weil der Bau-Dialog neuer ist als die Spec-Grammatik; uebrige Bloecke als Einzel-Insert ohne `_2D`/`_3D`-Suffix). Reihenfolge Kreisel, VF, GF, Einzelbloecke (VF vor GF wie mubea). Aufruf: `python dxf_anlage_export.py [--projekt NAME]` — erzeugt `tests/testdata/dematic_arvato.json` aus `Dematic_TST.dxf`. Test `tests/test_dxf_anlage_export.py` | -| `dxf_scan_components.py` | Erkennt ILS-/Omniflo-Komponenten in einer **fremden** Projektzeichnung (ohne unsere Wrapper-Bloecke) und schreibt sie im JSON-Schema aus `tests/testdata/object_data.md` — Grundlage fuer Nachbau-Testfaelle wie `tests/testdata/mubea.json`. Liest binaere DXF im GB-Bereich streamend per mmap (ezdxf-DOM waere zu gross), loest INSERTs inkl. OCS/Extrusionsrichtung rekursiv in Weltkoordinaten auf, paart AN8/SP8 zu Kreiseln und Antrieb/Umlenkung zu Foerderern. Gefaellestrecken, die als 3D-LINE auf einem Staustrecken-Layer gezeichnet sind (`GF_LINIE_LAYER`, z.B. `6-SP`), werden in einem zweiten Lesedurchgang zu Modus-1-Eintraegen: Start/Ende werden auf die Ketten der anschliessenden Kreisel verlaengert (gezeichnet ist nur die Staustrecke; `deltaL` reicht von AS- bis ES-Ursprung), AS-Seite aus dem Ausschleuselement am oberen Ende; dieses und Separatoren auf der Achse entfallen als Einzel-Insert (die GF baut beide selbst). Sitzt der AS am UNTEREN Ende, ist es ein Steigfoerderer und wird nur gemeldet (`gf_eintraege_aus_linien`, Test `tests/test_dxf_scan_gf.py`). `--ohne-hoehen` setzt alle Z auf 0, `--kreisel-hoehe X Y Z` gibt einem Kreisel seine Hoehe - die daran haengenden GF bekommen daraus `start_mm.z`/`hoehe_ende_mm`, den Winkel rechnet der Treiber (`dematic_arvato:gf-winkel`: atan ueber die HORIZONTALE Laenge, ES-Abfall per `gf-messe-dz-block` iteriert - NICHT `gf-winkel-berechnen`, dessen asin bei steilen GF wie im Showroom ~50 statt ~37 Grad liefert; `dematic_arvato:gf-endhoehe-pruefen` meldet `warnung` bei > 5 mm Abweichung von HOEHE_BIS). Flache 2D-Plaene: `--flach` (waagrechte Staustrecke mit AS an genau einem Ende = GF, zerstueckelte Linien werden vereint, ES am unteren Ende wird verbraucht, AS/ES-Seite ohne Namensangabe aus der Lage zur Fahrtrichtung), `--kreisel-achse X1 Y1 X2 Y2` (Kreisel ohne AN8/SP8), Einzelteile auf einer Kreisel-Kette erben dessen Anwender-Hoehe (`sensor_hoehen_von_kreiseln`). Kreisel-Paarung mit Tausch-Nachbesserung auf minimale Abstandssumme (zwei Kreisel auf einer Achse). Genutzt fuer `tests/testdata/showroom_ils.json` (`TEST_SHOWROOM_ILS`). Aufruf: `python dxf_scan_components.py [--fenster X0 Y0 X1 Y1]` — ohne `--fenster` liefert eine Anlagenzeichnung leicht ueber 1300 Eintraege, zuviel fuer einen Testlauf | +| `dxf_scan_components.py` | Erkennt ILS-/Omniflo-Komponenten in einer **fremden** Projektzeichnung (ohne unsere Wrapper-Bloecke) und schreibt sie im JSON-Schema aus `tests/testdata/object_data.md` — Grundlage fuer Nachbau-Testfaelle wie `tests/testdata/mubea.json`. Liest binaere DXF im GB-Bereich streamend per mmap (ezdxf-DOM waere zu gross), loest INSERTs inkl. OCS/Extrusionsrichtung rekursiv in Weltkoordinaten auf, paart AN8/SP8 zu Kreiseln und Antrieb/Umlenkung zu Foerderern. Gefaellestrecken, die als 3D-LINE auf einem Staustrecken-Layer gezeichnet sind (`GF_LINIE_LAYER`, z.B. `6-SP`), werden in einem zweiten Lesedurchgang zu Modus-1-Eintraegen: Start/Ende werden auf die Ketten der anschliessenden Kreisel verlaengert (gezeichnet ist nur die Staustrecke; `deltaL` reicht von AS- bis ES-Ursprung), AS-Seite aus dem Ausschleuselement am oberen Ende; dieses und Separatoren auf der Achse entfallen als Einzel-Insert (die GF baut beide selbst). Sitzt der AS am UNTEREN Ende, ist es ein Steigfoerderer und wird nur gemeldet (`gf_eintraege_aus_linien`, Test `tests/test_dxf_scan_gf.py`). `--ohne-hoehen` setzt alle Z auf 0, `--kreisel-hoehe X Y Z` gibt einem Kreisel seine Hoehe - die daran haengenden GF bekommen daraus `start_mm.z`/`hoehe_ende_mm`, den Winkel rechnet der Treiber (`dematic_arvato:gf-geometrie`: atan ueber die HORIZONTALE Laenge, ES-Laenge UND -Abfall bei der Einlaufneigung gemessen und iteriert, Staustrecke daraus - die flache ES-Laenge liess steile GF ~144 mm vor der Kette enden - NICHT `gf-winkel-berechnen`, dessen asin bei steilen GF wie im Showroom ~50 statt ~37 Grad liefert; `dematic_arvato:gf-endhoehe-pruefen` meldet `warnung` bei > 5 mm Abweichung von HOEHE_BIS). Flache 2D-Plaene: `--flach` (waagrechte Staustrecke mit AS an genau einem Ende = GF, zerstueckelte Linien werden vereint, ES am unteren Ende wird verbraucht, AS/ES-Seite ohne Namensangabe aus der Lage zur Fahrtrichtung), `--kreisel-achse X1 Y1 X2 Y2` (Kreisel ohne AN8/SP8), Einzelteile auf einer Kreisel-Kette erben dessen Anwender-Hoehe (`sensor_hoehen_von_kreiseln`). Kreisel-Paarung mit Tausch-Nachbesserung auf minimale Abstandssumme (zwei Kreisel auf einer Achse). Genutzt fuer `tests/testdata/showroom_ils.json` (`TEST_SHOWROOM_ILS`). Aufruf: `python dxf_scan_components.py [--fenster X0 Y0 X1 Y1]` — ohne `--fenster` liefert eine Anlagenzeichnung leicht ueber 1300 Eintraege, zuviel fuer einen Testlauf | ### Entwickler-Tools (`lib/`) - kein ezdxf, reine Text-/AutoLISP-Verarbeitung diff --git a/tests/test_dematic_arvato.lsp b/tests/test_dematic_arvato.lsp index de1db72..a286aa7 100644 --- a/tests/test_dematic_arvato.lsp +++ b/tests/test_dematic_arvato.lsp @@ -36,7 +36,8 @@ ;; "modus": 1 -> Gefaellestrecke ueber mubea:gf-bauen ;; (start_mm + deltaL_mm, kein Endpunkt). Mit ;; hoehe_ende_mm (Hoehe des Kreisels am Ende) -;; rechnet dematic_arvato:gf-winkel den Winkel, +;; rechnet dematic_arvato:gf-geometrie Winkel +;; und Staustreckenlaenge (gf-bauen-exakt), ;; sonst gilt winkel_grad. ;; "typ": "gerade" -> waagrechter Foerderer aus der Scanner-Vorlage - ;; kein Bau-Pfad, wird gemeldet und uebersprungen @@ -97,59 +98,99 @@ (vsp-set (vsp-set rec "status" "uebersprungen") "fehler_text" "kein Bau-Pfad fuer typ gerade")) -;; Gefaellewinkel aus Start- und Endhoehe - derselbe Loeser wie Modus 3 -;; (gf-winkel-berechnen, Gefaellestrecke.lsp) mit den AS/ES-Massen der -;; aktuellen 2D/3D-Bibliothek. Die Kette ist eine einzige Gerade plus der -;; eine Separator vor dem ES (extra-sep 1); ihre Laenge ohne AS/ES/Separator -;; ist deltaL - aus-dx - ein-dx - 300, damit ergibt der Loeser als -;; Gesamtlaenge wieder genau deltaL (deltaL reicht von AS- bis ES-Ursprung). -;; VORZEICHEN: vf-set-as-masse (vf_core) misst dz als KS_AUS.z - KS_EIN.z -;; (bei AS/ES negativ), gf-winkel-berechnen erwartet die Konvention von -;; gf-init-bibliothek (KS_EIN.z - KS_AUS.z, positiv) und zieht den Wert ab. -;; Mit der VF-Konvention wurde der feste AS/ES-Abfall addiert statt -;; abgezogen - real gebaut: 3.79 statt ~2.49 Grad, GF endete 258 mm unter -;; dem Kreisel (HOEHE_BIS 1568 statt 1826). Darum hier der Betrag. +;; ES-Element bei Einlaufneigung w (Grad, bergab) in Fahrtrichtung hz +;; vermessen: horizontale Laenge entlang hz und Abfall von KS_EIN bis KS_AUS. +;; Wie gf-messe-dz-block (Gefaellestrecke.lsp), liefert aber zusaetzlich die +;; Laenge - beide aendern sich mit der Neigung (90-Grad-ES flach ~436 mm +;; lang, bei 43 Grad nur noch ~292 mm). +;; Rueckgabe: (dx dz), dz positiv = nach unten. +(defun dematic_arvato:es-mass (blockname hz w / rad-h rad-v frame-aus ent-vor ent-nach p) + (setq rad-h (* hz (/ pi 180.0)) + rad-v (* w (/ pi 180.0))) + (setq ent-vor (entlast)) + (setq frame-aus + (insert-block-ks-to-ks blockname + (make-frame-from-dir '(0 0 0) + (list (* (cos rad-h) (cos rad-v)) (* (sin rad-h) (cos rad-v)) + (- (sin rad-v)))))) + (setq ent-nach (entlast)) + (if (and ent-nach (not (eq ent-nach ent-vor))) (entdel ent-nach)) + (setq p (car frame-aus)) + (list (+ (* (car p) (cos rad-h)) (* (cadr p) (sin rad-h))) + (- 0.0 (caddr p)))) + +;; Winkel UND Staustreckenlaenge einer GF (Modus 1) aus Start- und Endhoehe, +;; so dass der ES-Auslauf genau auf Hoehe UND Kette des unteren Kreisels +;; liegt. deltaL reicht von AS-KS_EIN bis ES-KS_AUS (horizontal). ;; -;; NICHT gf-winkel-berechnen: dessen asin(dH / L) behandelt die Laenge als -;; SCHRAEGE Laenge (richtig fuer Modus 3). Modus 1 haelt aber die -;; HORIZONTALE Laenge deltaL fest und streckt die Staustrecke um 1/cos(w) -;; (mubea:gf-bauen) - der Abfall der Schraege (Staustrecke + Separator) ist -;; dann genau (deltaL - aus-dx - ein-dx) * tan(w). Bei 2-3 Grad (Dematic) -;; ist das gleich, bei steilen GF nicht: Showroom_ILS_3_0 faellt 1860 mm auf -;; 2405 mm (~37 Grad), asin ergab ~50 Grad und die GF endete weit unter dem -;; unteren Kreisel. Der Abfall des ES haengt von der Einlaufneigung ab - er -;; wird wie in Modus 3 (gf-messe-dz-block) beim aktuellen Winkel gemessen -;; und iteriert; das AS liegt flach, sein Abfall ist fest (aus-dz). -(defun dematic_arvato:gf-winkel (deltaL hz as-seite es-seite as-winkel es-winkel - z-start z-ende / L dz-as dz-es winkel vorher - iter rad-h rad-v) +;; H = deltaL - aus-dx - es_dx(w) horizontale Laenge der Schraege +;; tan(w) = (dH - aus-dz - es_dz(w)) / H Schraege = Staustrecke + Separator +;; L_stau = H / cos(w) - 300 +;; +;; es_dx/es_dz werden beim aktuellen Winkel gemessen (dematic_arvato:es-mass) +;; und iteriert; das AS liegt flach, aus-dx/aus-dz sind fest. +;; +;; Warum nicht mubea:gf-bauen / gf-winkel-berechnen: +;; - gf-winkel-berechnen rechnet asin(dH / L), also mit SCHRAEGER Laenge +;; (Modus 3); Modus 1 haelt aber die horizontale Laenge fest. Bei 2-3 Grad +;; (Dematic) gleich, bei den steilen Showroom-GF (1860 mm auf 2405 mm) +;; ~50 statt ~43 Grad - die GF endete weit unter dem Kreisel. +;; - mubea:gf-bauen zieht die FLACH gemessene ES-Laenge ein-dx ab. Bei 43 +;; Grad ist das ES horizontal ~144 mm kuerzer, die GF hoerte so 144 mm +;; vor der Kette des unteren Kreisels auf (Hoehe stimmte). +;; VORZEICHEN: vf-set-as-masse misst aus-dz als KS_AUS.z - KS_EIN.z +;; (negativ) - darum der Betrag (Dematic: sonst HOEHE_BIS 1568 statt 1826). +;; Rueckgabe: (winkel L_stau) +(defun dematic_arvato:gf-geometrie (deltaL hz as-seite es-seite as-winkel es-winkel + z-start z-ende / es-block dH es H winkel vorher + iter dx-as dz-as) (mubea:ensure-gf-init) (vf-set-as-masse as-winkel as-seite) (vf-set-es-masse es-winkel es-seite) - (setq L (- deltaL (float aus-dx) (float ein-dx)) - dz-as (abs (float aus-dz)) - dz-es (abs (float ein-dz)) - rad-h (* hz (/ pi 180.0)) - vorher -999.0 - iter 0) - (setq winkel (* (atan (- z-start z-ende dz-as dz-es) L) (/ 180.0 pi))) - (while (and (< iter 6) (> (abs (- winkel vorher)) 0.001)) + (setq es-block (strcat "ES_Element_" es-winkel "_" es-seite) + dx-as (float aus-dx) + dz-as (abs (float aus-dz)) + dH (- z-start z-ende) + es (list (float ein-dx) (abs (float ein-dz))) + vorher -999.0 + iter 0) + (setq H (- deltaL dx-as (car es)) + winkel (* (atan (- dH dz-as (cadr es)) H) (/ 180.0 pi))) + (while (and (< iter 8) (> (abs (- winkel vorher)) 0.0005)) (setq vorher winkel - rad-v (* winkel (/ pi 180.0))) - (setq dz-es (gf-messe-dz-block (strcat "ES_Element_" es-winkel "_" es-seite) - (list (* (cos rad-h) (cos rad-v)) (* (sin rad-h) (cos rad-v)) - (- (sin rad-v))))) - (setq winkel (* (atan (- z-start z-ende dz-as dz-es) L) (/ 180.0 pi)) + es (dematic_arvato:es-mass es-block hz winkel) + H (- deltaL dx-as (car es)) + winkel (* (atan (- dH dz-as (cadr es)) H) (/ 180.0 pi)) iter (1+ iter))) (vf-set-as-masse "90" as-seite) (vf-set-es-masse "90" es-seite) - winkel) + (list winkel (- (/ H (cos (* winkel (/ pi 180.0)))) 300.0))) + +;; GF mit vorgegebener Staustreckenlaenge einfuegen (wie mubea:gf-bauen, aber +;; L_stau kommt aus dematic_arvato:gf-geometrie statt aus der flachen +;; ES-Laenge). +(defun dematic_arvato:gf-bauen-exakt (tid start-mm deltaL hz as-seite es-seite + as-winkel es-winkel winkel L_stau / ent) + (mubea:ensure-gf-init) + ;; Element-Masse der Variante wie in mubea:gf-bauen, danach zurueck auf 90 + (vf-set-as-masse as-winkel as-seite) + (vf-set-es-masse es-winkel es-seite) + (princ (strcat "\n [GF] " tid " -> Start=(" (rtos (car start-mm) 2 0) + "," (rtos (cadr start-mm) 2 0) "," (rtos (caddr start-mm) 2 0) + ") hz=" (rtos hz 2 0) " deltaL=" (rtos deltaL 2 0) + " winkel=" (rtos winkel 2 3) " L_stau=" (rtos L_stau 2 1))) + (setq ent (car (gefaellestrecke-einfuegen + L_stau winkel start-mm as-seite es-seite hz + deltaL as-winkel es-winkel "0" "Schoenenberger Geruest"))) + (vf-set-as-masse "90" as-seite) + (vf-set-es-masse "90" es-seite) + ent) ;; Gefaellestrecke (Modus 1) bauen - Felder wie mubea:build-gf, ohne dessen ;; anzahl/delta-Vervielfaeltigung (jede GF steht einzeln in der Datei). ;; Mit "hoehe_ende_mm" (Hoehe des Kreisels am Ende) wird der Winkel daraus ;; gerechnet, sonst gilt "winkel_grad" (Default 3.0). -(defun dematic_arvato:bau-gf (obj / tid start deltaL hz winkel z-ende +(defun dematic_arvato:bau-gf (obj / tid start deltaL hz winkel z-ende geo as-seite es-seite as-winkel es-winkel rec) (setq tid (ssg-val obj "test_id") start (ssg-val obj "start_mm") @@ -177,12 +218,15 @@ (vsp-set (vsp-set rec "status" "spec-fehler") "fehler_text" "hoehe_ende_mm >= start_mm.z - kein Gefaelle")) (T + ;; Mit Endhoehe: Winkel + Staustreckenlaenge exakt (gf-geometrie), + ;; sonst fester Winkel ueber mubea:gf-bauen. + (setq geo (if (numberp z-ende) + (dematic_arvato:gf-geometrie (float deltaL) (float hz) + as-seite es-seite as-winkel es-winkel + (float (caddr start)) (float z-ende)))) (setq winkel (cond - ((numberp z-ende) - (dematic_arvato:gf-winkel (float deltaL) (float hz) as-seite es-seite - as-winkel es-winkel - (float (caddr start)) (float z-ende))) + (geo (car geo)) ((numberp winkel) (float winkel)) (T 3.0))) ;; Winkel und Soll-Endhoehe als Meldung (das Record-Schema aus vf_spec @@ -195,8 +239,12 @@ ""))))) (setq rec (dematic_arvato:rec-block rec - (mubea:gf-bauen tid (mapcar 'float start) (float deltaL) (float hz) - as-seite es-seite as-winkel es-winkel winkel) + (if geo + (dematic_arvato:gf-bauen-exakt tid (mapcar 'float start) (float deltaL) + (float hz) as-seite es-seite as-winkel + es-winkel winkel (cadr geo)) + (mubea:gf-bauen tid (mapcar 'float start) (float deltaL) (float hz) + as-seite es-seite as-winkel es-winkel winkel)) "gefaellestrecke-einfuegen lieferte keinen GF-Block")) ;; Ist-Endhoehe gegen den Kreisel am Ende pruefen (dematic_arvato:gf-endhoehe-pruefen rec z-ende)))) diff --git a/tests/test_showroom_ils.lsp b/tests/test_showroom_ils.lsp index 0ff80b7..bb3e9bf 100644 --- a/tests/test_showroom_ils.lsp +++ b/tests/test_showroom_ils.lsp @@ -27,7 +27,8 @@ ;; angenaehert (Radmitten = Mittelpunkte der PIN-Kreise). ;; - Weichen (WB-*/WM-*/WL-*/WR-*) werden bewusst NICHT nachgebaut. ;; - Die GF sind steil (1860 bzw. 1495 mm Abfall auf 2405 mm) - den Winkel -;; rechnet dematic_arvato:gf-winkel aus hoehe_ende_mm, und +;; und die Staustreckenlaenge rechnet dematic_arvato:gf-geometrie aus +;; hoehe_ende_mm (ES-Masse bei der Neigung gemessen), und ;; dematic_arvato:gf-endhoehe-pruefen meldet "warnung", wenn eine GF ;; den unteren Kreisel um mehr als 5 mm verfehlt. ;;