K1/K2 und Boundingbox je Export-Zeile folgen jetzt dem echten Kettenbaustein statt seinem Stellvertreter: - Separator (all_elements): KS + bbox des 300-mm-Trennstegs statt des Sensor-Symbols; separator_track/group_vf_gf behalten das Symbol - AS/ES (all_elements, group_vf_gf): KS + bbox des AS-/ES-Elements statt des 200x200-Markers ohne K - gemeinsamer Kern csv:ks-welt-punkte; VF/GF-Wrapper-K1/K2 = K1 des AS bzw. K2 des ES Gruppierter Export neu (sepliste-/XDATA-Algorithmus entfernt): - Bausteine je Wrapper als Vektorkette K2 -> K1 ordnen (Start am AS) - Foerdereinheit = Separator vor der Umlenkstation bis Motorstation, alles dazwischen Gefaelleeinheit (csv:kette-einheiten, Unit-Tests) - je Einheit ein temporaerer Proxy mit 3D-Boundingbox, K1/K2, Laenge und Anzahl Boegen; wird nach dem Export geloescht (ausser csv_leave_proxies) - IDs neuer Export-Objekte entlang der Kette AS -> ES aufsteigend Weiteres: - ssg-make-layer ohne _LAYER-Befehl fuer bestehende Layer (Konsole) - einheitliche, zweisprachige Meldungen fuer behaltene Hilfskopien - bin/show_exportcsv.bat: Export-CSV als Boundingbox-SVG (sps_skel) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
662 lines
37 KiB
Markdown
662 lines
37 KiB
Markdown
# CSV-Export (EXPORTCSV) — Ablauf und Architektur
|
|
|
|
Beschreibt den kompletten Weg vom `EXPORTCSV`-Befehl in BricsCAD bis zur
|
|
fertigen CSV: welche Daten LISP sammelt, was Python daraus berechnet, und wie
|
|
die "Nachbarn"-Spalte zustande kommt. Stand: `Lisp/export.lsp`,
|
|
`lib/export_csv.py`, `lib/export_neighbors.py`.
|
|
|
|
## Ueberblick: zwei Phasen, eine JSON-Datei dazwischen
|
|
|
|
```
|
|
BricsCAD (LISP) Python
|
|
───────────────── ──────
|
|
c:EXPORTCSV
|
|
-> csv:run-export
|
|
-> Kopien fuer verpackte
|
|
Separatoren einfuegen
|
|
-> AS/ES-Marker aufbauen
|
|
-> IDs vergeben (ssg-id-check-all)
|
|
-> IDs in die sepliste
|
|
eintragen
|
|
-> Separator-/Scanner-Zuordnung
|
|
(count_sep_scan.lsp)
|
|
-> REGENALL
|
|
-> csv:collect-export-blocks export_raw.json lib/export_csv.py::main
|
|
-> je Block csv:block-to-json ───────────────> -> process_blocks
|
|
-> Nachbarschaft/
|
|
Umlauf/Zuordnung
|
|
-> CSV schreiben
|
|
```
|
|
|
|
LISP sammelt reine Zeichnungsdaten (Position, Rotation, Attribute,
|
|
Bounding-Box, Anschluss-Koordinatensysteme) und schreibt sie als flaches
|
|
JSON-Array (`results/export_raw.json`). Python bekommt keine BricsCAD-API zur
|
|
Verfuegung — es rechnet ausschliesslich mit den Zahlen aus diesem JSON. Alles,
|
|
was "Nachbar X kennt Nachbar Y" bedeutet, wird also in Python aus Geometrie
|
|
(Bounding-Box-Ueberschneidung, Distanz, Winkel) oder aus mitgelieferten
|
|
Reihenfolge-Daten (sepliste, siehe unten) rekonstruiert — LISP selbst kennt
|
|
diese Beziehungen beim Bauen der Einzelketten nicht.
|
|
|
|
## Phase 1 (LISP): `csv:run-export` — was vor dem JSON passiert
|
|
|
|
Datei: `Lisp/export.lsp`. `c:EXPORTCSV` ruft `csv:run-export` mit
|
|
`include-bbox=T` auf (im Unterschied zu `c:EXPORTSIVAS`, das keine
|
|
Bounding-Boxen braucht).
|
|
|
|
1. **`csv:sep-proxies-erzeugen`** — Separator_SP-Symbole, die beim
|
|
Zusammenbau von `KREISEL_n`/`ECKRAD_n`/`VF_n`/`GF_n` in die
|
|
Compound-Blockdefinition verpackt wurden, sind fuer `(ssget "X" ...)`
|
|
unsichtbar. Diese Funktion fuegt fuer jeden Fund eine ECHTE, temporaere
|
|
Kopie als top-level INSERT an derselben Weltposition ein. Die Kopie
|
|
durchlaeuft danach ID-Vergabe und Export ganz normal ueber den freien-
|
|
Separator-Pfad (Blockname-Muster `Separator_SP*` trifft zu) und bekommt
|
|
dadurch eine echte `vla-getboundingbox`-Box, die das verpackte Original nie
|
|
haben koennte. Rueckgabe: Liste von Records
|
|
`(proxy-ename wrapper-ename typ (x y z))`.
|
|
1b. **`csv:asmark-aktualisieren`** — die AS-/ES-Enden derselben Kette
|
|
(`AS_Element*`/`ES_Element*`, Muster `pattern_schleuselement`) bekommen
|
|
KEINE temporaere Kopie, sondern einen DAUERHAFTEN Marker — siehe eigener
|
|
Abschnitt "Der AS/ES-Marker" unten. Beide Wege teilen sich die
|
|
Platzierungsrechnung (`csv:nested-welt-records` liefert die Weltlage jedes
|
|
verpackten Fundes, `csv:block-einfuegen-welt` setzt ein Objekt dorthin).
|
|
2. **`ssg-id-check-all`** — vergibt fehlende IDs (inkl. der frisch erzeugten
|
|
Proxies) und korrigiert Duplikate. Muss NACH den Proxies laufen, sonst
|
|
wuerden diese Kopien numerisch nie erfasst. `ssg-id-collect-blocks`
|
|
(`ssg_id.lsp`) erfasst dafuer auch `AS_Element*`/`ES_Element*`.
|
|
3. **`csv:sep-proxies-zuordnung-setzen`** — jetzt steht die ID jedes Wrappers
|
|
fest: die Proxy-Kopie bekommt `ZUORDNUNG = Wrapper-ID` fest zugewiesen
|
|
(nicht geraten) und wird zusaetzlich in `*cs-sep-fix-by-handle*` vermerkt,
|
|
damit `count_sep_scan.lsp` diesen Wert spaeter nicht per Geometrie
|
|
ueberschreibt. Nur fuer die Separator-Kopien — AS/ES sind keine Sensoren.
|
|
3b. **`csv:asmark-ids-setzen` + `csv:sepliste-ids-setzen`** — ebenfalls erst
|
|
jetzt moeglich: jedes Bauteil wird seinem `sepliste`-Eintrag zugeordnet und
|
|
seine ID unter `*csv-sepliste-id-by-handle*` (Key = Wrapper-Handle)
|
|
abgelegt; `csv:block-to-json` schreibt sie als Feld `id` in den Eintrag.
|
|
AS/ES laufen ueber die Marker (die ihre ID schon kennen, die Zuordnung
|
|
steht im Marker-Attribut `LFDNR`), SEP ueber die temporaeren Kopien und
|
|
`csv:sepliste-paare` — siehe eigener Abschnitt unten. `csv:asmark-ids-setzen`
|
|
traegt zusaetzlich die `ZUORDNUNG` (Wrapper-ID) am Marker nach; ohne sie
|
|
faende der naechste Export den Marker nicht wieder.
|
|
4. **`omni:update-all-attribs`** — Hoehe/Drehung aller Omniflo-Elemente vor
|
|
dem Export aktualisieren.
|
|
5. **`count_sep_scan.lsp::cs-zuordnung-lauf`** (nur bei Bedarf, siehe
|
|
`cs-zuordnung-noetig-p`) — ordnet frei platzierte Separator/Scanner-Symbole
|
|
per Bounding-Box-Ueberschneidung bzw. Distanz einem Kreisel/VF/GF zu und
|
|
schreibt `ANZAHL_SEPARATOR`/`ANZAHL_SCANNER` an den Carrier.
|
|
6. **`REGENALL`** — erzwingt eine vollstaendige Regeneration der Zeichnung.
|
|
Notwendig, weil `vla-getboundingbox` sonst in der Praxis fehlschlagen kann
|
|
(realer Fall: ein Export ohne vorheriges REGENALL lieferte fuer ALLE 75
|
|
Bloecke keine Bounding-Box). `csv:get-bbox` ruft zusaetzlich pro Objekt
|
|
`vla-update` auf, das allein reichte in diesem Fall nicht aus.
|
|
7. **`csv:collect-export-blocks`** — sammelt alle relevanten top-level
|
|
INSERTs (Kreisel, Omniflo, VF/GF-Wrapper, Separator/Scanner,
|
|
BTMT-Stationen, Eingang/Ausgang) ueber die Blockname-Muster aus
|
|
`cfg/export.cfg` `[blockpattern]`.
|
|
8. **`csv:block-to-json`** je Fund — siehe naechster Abschnitt.
|
|
|
|
### `csv:block-to-json` — Inhalt eines JSON-Eintrags
|
|
|
|
Pro INSERT wird ein JSON-Objekt geschrieben:
|
|
|
|
| Feld | Quelle | Bemerkung |
|
|
|---|---|---|
|
|
| `block_name`, `layer`, `handle` | direkt vom INSERT | |
|
|
| `x`, `y`, `z`, `rotation` | `(assoc 10)` / `(assoc 50)` | roher Einfuegepunkt/-winkel, KEIN `vla-getboundingbox` |
|
|
| `attribs` | alle ATTRIB-Werte | |
|
|
| `insertpoint` | `csv:kos-encode` | Position+Rotation als 24-Zeichen-Base64 (Quaternion) |
|
|
| `k1`..`k4` | `csv:k-liste` (Kern `csv:ks-welt-punkte`) bzw. Fallbacks | Anschluss-Koordinatensysteme, siehe unten |
|
|
| `bbox` | `csv:get-bbox` (`vla-getboundingbox`), im Modus `all_elements` fuer Separator/AS/ES die des Bausteins (`csv:baustein-merken`) | NUR wenn `include-bbox=T` und der Aufruf gelingt; fehlt sonst komplett im JSON |
|
|
| `warnung` | `*cs-scanner-warnung-by-handle*` | strittige Scanner-Zuordnung aus `cs-zuordnung-lauf` |
|
|
| `zuordnung_fix` | `*cs-sep-fix-by-handle*` | nur bei Proxy-Kopien: die feststehende Wrapper-ID |
|
|
| `sepliste` | `ssg-sepliste-xdata-lesen` + `*csv-sepliste-id-by-handle*` | nur bei VF_n/GF_n, siehe eigener Abschnitt |
|
|
|
|
**K1-K4 je Blocktyp:** normale ILS-Bloecke fuehren echte K1-K4-Sub-Bloecke.
|
|
Omniflo-Geraden (`AP110*`) haben keine echten K-Bloecke — K1/K2 werden aus
|
|
Laenge+Rotation synthetisiert (`csv:gerade-k-kos-strings`). VF_n/GF_n leiten
|
|
K1 (Eingang) und K2 (Ausgang) aus den eingebetteten AS_Element/ES_Element ab
|
|
(`csv:vfgf-k-kos-strings`), damit die Kreisel<->Strecke-Kollisionspruefung in
|
|
Python mit einer kleinen festen Box an der AS/ES-Position arbeiten kann statt
|
|
mit der ganzen (oft sehr langen) Wrapper-Box. K1 des Wrappers ist dabei K1
|
|
des AS, K2 der K2 des ES (`csv:vfgf-element-punkte`, dieselbe Rechnung wie
|
|
deren eigene Zeilen).
|
|
|
|
**Kettenteile: Stellvertreter vs. Baustein.** Innerhalb einer VF_n/GF_n-Kette
|
|
gilt fuer jedes Teil K1 = KS_EIN, K2 = KS_AUS des Bausteins - K2 eines Teils
|
|
ist exakt K1 des naechsten. Bogen/Streckenelement/Motor-/Umlenkstation werden
|
|
als 1:1-Kopie exportiert und erfuellen das von selbst. Separator und AS/ES
|
|
exportieren dagegen einen **Stellvertreter**: die Kopie des Sensor-Symbols
|
|
`Separator_SP` (sitzt 235 mm weit im 300-mm-Trennsteg
|
|
`Staustrecke_Separator_SP_300_mm`, hat nur EIN eigenes KS) bzw. den
|
|
`ZZ_ASES_MARKER` (200x200, ohne KS). Was die Zeile fuehrt, haengt am Modus:
|
|
|
|
| Modus | Separator | AS/ES |
|
|
|---|---|---|
|
|
| `all_elements` | K1/K2 + bbox des Trennstegs | K1/K2 + bbox des AS-/ES-Elements (AS: K1 Kreisel, K2 Kettenanfang; ES: K1 Kettenende, K2 Kreisel) |
|
|
| `group_vf_gf` | KS + bbox des Symbols | wie `all_elements` (schliesst an die Einheiten an) |
|
|
| `separator_track` | KS + bbox des Symbols | bbox des Markers, keine K |
|
|
|
|
IDs der neuen Export-Objekte jeder Kette (Separator-/Element-Kopien, neue
|
|
AS/ES-Marker, Einheiten-Proxies) werden vor `ssg-id-check-all` entlang der
|
|
Kette von AS nach ES aufsteigend vergeben (`csv:ketten-verarbeiten`) - die
|
|
CSV ist nach TeileId sortiert, die Zeilen stehen damit in Kettenfolge.
|
|
|
|
Umgesetzt ueber `csv:baustein-merken`: beim Erzeugen des Stellvertreters
|
|
werden K-Punkte (`csv:ks-welt-punkte`) und bbox (kurzlebige Kopie des
|
|
Bausteins, `csv:baustein-bbox`) unter seinem Handle gemerkt
|
|
(`*csv-baustein-by-handle*`, je Lauf neu); `csv:k-liste` und
|
|
`csv:block-to-json` bevorzugen diese Werte. Der Trennsteg eines Symbols wird
|
|
ueber die Lage des Symbols IM Trennsteg gefunden (`csv:trennsteg-zu-symbol`),
|
|
nicht ueber den naechsten Ursprung.
|
|
|
|
**Gruppierter Modus (`group_vf_gf`): Foerder-/Gefaelleeinheiten.** Eine
|
|
VF_n/GF_n-Kette wechselt zwischen Gefaelle- und Foerderabschnitten (GF, VF,
|
|
GF, ...). `csv:ketten-verarbeiten` holt je Wrapper alle Bausteine samt
|
|
Welt-KS (`csv:kette-teile`: AS, Trennstege, Boegen, Streckenelemente,
|
|
Umlenk-/Motorstation, ES), ordnet sie als **Vektorkette** - auf K2 eines
|
|
Bausteins liegt K1 des naechsten, Start am AS (`csv:kette-ordnen`, Toleranz
|
|
2 mm) - und schneidet die Typfolge in Einheiten (`csv:kette-einheiten`):
|
|
eine **Foerdereinheit** beginnt am Separator vor der Umlenkstation und endet
|
|
mit der Motorstation, alles dazwischen ist eine **Gefaelleeinheit**; AS/ES
|
|
bleiben eigene Zeilen. Kurven sind dabei kein Problem, weil nur die
|
|
KS-Nachbarschaft zaehlt. Je Einheit entsteht ein Proxy `ZZ_UNIT_MARKER` auf
|
|
der vereinigten Boundingbox (gemessen an kurzlebigen Kopien der Bausteine),
|
|
mit K1 vom ersten und K2 vom letzten Baustein, `LAENGE_m` = Summe der
|
|
Streckenelement-Laengen K1->K2, `ANZAHL_BOEGEN`. Alle Proxies werden nach
|
|
dem Export geloescht, ausser der Debug-Schalter `csv_leave_proxies` ist
|
|
gesetzt. Reisst die KS-Kette ab, meldet der Export das (`exp-kette-luecke`)
|
|
und fuehrt den Rest als eigene Teilkette weiter. Die fruehere Gruppierung
|
|
ueber die sepliste-XDATA und den Separator-Typ (`SSG_SEP_TYP`) ist entfallen.
|
|
|
|
**Die `bbox` ist das einzige Feld, das `vla-getboundingbox` braucht** — alle
|
|
anderen Felder kommen aus rohen DXF-Gruppencodes oder XDATA. Ein Ausfall von
|
|
`vla-getboundingbox` zeigt sich also NICHT im ganzen JSON-Eintrag, sondern nur
|
|
im fehlenden `bbox`-Objekt. `csv:get-bbox` protokolliert einen Fehlschlag seit
|
|
Kurzem in `*csv-bbox-fail-count*`/`*csv-bbox-fail-first*`; `csv:run-export`
|
|
meldet nach dem JSON-Schreiben eine Warnung (`exp-bbox-fail`), falls das
|
|
vorkam.
|
|
|
|
### Die `sepliste` — Reihenfolge-Information einer VF_n/GF_n-Kette
|
|
|
|
Waehrend eine Gefaellestrecke (`GEFAELLESTRECKE`) oder ein VarioFoerderer
|
|
(`FOERDERANLAGE`) gebaut wird, sammelt `ssg-sepliste-sammeln`
|
|
(`Lisp/ssg_ks_insert.lsp`) alle currently frisch erzeugten `AS_Element_*`-,
|
|
`Staustrecke_Separator_SP_300_mm*`- und `ES_Element_*`-Sub-INSERTs in
|
|
Baureihenfolge und schreibt sie als XDATA (App `SSG_VF_SEP`/`SSG_GF_SEP`) auf
|
|
den fertigen Wrapper: `[(typ lfdnr x y), ...]` mit `typ` = `"AS"`/`"SEP"`/
|
|
`"ES"`, `lfdnr` 1-basiert. Das ist reine Bauabfolge, keine Weltkoordinate im
|
|
strengen Sinn — je nach Bauphase koennen einzelne Eintraege noch im
|
|
Baukoordinatensystem stehen, bevor die Kette an ihre finale Position
|
|
verschoben wurde (siehe Abschnitt "sepliste-Koordinaten" unten).
|
|
|
|
Die sepliste beschreibt NUR die Reihenfolge innerhalb einer Kette
|
|
(AS -> Sep1 -> ... -> SepN -> ES). Sie sagt nichts darueber, an welchen
|
|
Kreisel die Kette andockt — das ergibt sich erst aus der geometrischen Lage
|
|
in der fertigen Zeichnung (siehe Python-Teil).
|
|
|
|
**Wichtig:** die in einer Kette verpackten Separatoren sind KEIN doppeltes
|
|
Konzept zur sepliste — es sind dieselben physischen Symbole, nur aus zwei
|
|
Blickwinkeln: `csv:sep-proxies-erzeugen` liefert sie als echte, exportierbare
|
|
INSERT-Kopie (mit Bounding-Box und ID); die sepliste liefert dieselben
|
|
Separatoren als Eintraege mit Vorgaenger-/Nachfolger-Wissen. Python fuehrt
|
|
beides zusammen (siehe `map_sepliste_kette_items` unten) statt sie doppelt
|
|
zu zaehlen.
|
|
|
|
### Das Feld `id` je sepliste-Eintrag
|
|
|
|
Seit `csv:sepliste-ids-setzen` traegt JEDER Eintrag zusaetzlich die `id` des
|
|
realen Bauteils, das ihn in der Zeichnung vertritt:
|
|
|
|
```json
|
|
{"typ": "AS", "lfdnr": 1, "x": 5070.1, "y": 1199.2, "id": "0031"}
|
|
```
|
|
|
|
Das ist dieselbe ID, die die zugehoerige temporaere Kopie (SEP) bzw. der
|
|
dauerhafte `ZZ_ASES_MARKER` (AS/ES) in ihrer eigenen JSON-Zeile traegt — die
|
|
Verbindung "Kettenglied <-> reales Teil" steht damit schon im Export und muss
|
|
in Python nicht mehr ueber Koordinaten-Naehe geraten werden. `map_sepliste_
|
|
kette_items` (siehe Abschnitt 5 unten) loest genau darueber jeden sepliste-
|
|
Eintrag exakt per ID-Gleichheit auf. Das Feld fehlt, wenn keine Kopie erzeugt
|
|
werden konnte oder deren ID-Attribut leer blieb — fuer SEP faellt das auf den
|
|
alten Koordinaten-Abgleich zurueck (Altbestand), fuer AS/ES bleibt der
|
|
Eintrag dann unaufgeloest (siehe Abschnitt 3 unten).
|
|
|
|
### Der AS/ES-Marker (`ZZ_ASES_MARKER`)
|
|
|
|
Die beiden Kettenenden stecken als `AS_Element_*`/`ES_Element_*` verpackt in
|
|
der Wrapper-Blockdefinition: in der Zeichnung sind sie weder anklickbar noch
|
|
einzeln erkennbar, und `(ssget "X" ...)` findet sie nicht. `csv:asmark-*`
|
|
(`Lisp/export.lsp`) setzt darum an jedes Ende ein kleines Rechteck, das nach
|
|
dem Export in der Zeichnung STEHEN BLEIBT:
|
|
|
|
- **Sichtbarmachung** — Kantenlaenge aus `cfg/export.cfg` `[Marker]`
|
|
`ases_groesse_mm` (Default 200 mm), mittig auf dem Einfuegepunkt des
|
|
Schleuselements, in dessen Drehung (auch bei geneigter Kette). Layer aus
|
|
`cfg/layer.cfg` `[allgemein]` `ases_marker` — abschaltbar wie jede andere
|
|
Hilfsgeometrie. Die Blockdefinition wird bei Bedarf zur Laufzeit erzeugt
|
|
(`csv:asmark-blockdef-sicherstellen`, Rechteck + ATTDEFs + `_.-BLOCK`), es
|
|
gibt keine DWG dafuer.
|
|
- **ID-Traeger** — die Attribute (alle unsichtbar) sind
|
|
`ID`, `ZUORDNUNG` (Wrapper-ID), `TYP` (`AS`/`ES`), `LFDNR` (sepliste-Eintrag),
|
|
`ELEMENT` (Blockname des echten Elements, haelt die Variante 30/90,
|
|
links/rechts, 2D/3D fest) und `ARTINR`. `ssg-id-check-all` fuellt die `ID`,
|
|
und weil der Marker liegen bleibt, **behaelt ein AS/ES seine TeileId ueber
|
|
Exportlaeufe hinweg** — im Unterschied zu den Separator-Kopien, die jeder
|
|
Lauf neu erzeugt und neu nummeriert.
|
|
|
|
Der Marker ist abgeleitete Geometrie: jeder Export baut ihn neu auf (die Lage
|
|
folgt damit automatisch einer umgebauten Kette) und uebernimmt vom bisherigen
|
|
Marker nur dessen `ID`. Wiedergefunden wird er ueber (`ZUORDNUNG`, `TYP`,
|
|
`LFDNR`) — bewusst nicht ueber die Position, damit eine verschobene Kette ihre
|
|
TeileIds behaelt. Was am Ende keinem Kettenende mehr entspricht (Kette
|
|
geloescht, Eintrag entfallen), wird geloescht; Karteileichen entstehen nicht.
|
|
Ein frisch gebauter Wrapper hat beim Setzen der Marker noch keine ID — seine
|
|
Marker bekommen die `ZUORDNUNG` nach der ID-Vergabe nachgetragen und sind ab
|
|
dem naechsten Export wiederfindbar.
|
|
|
|
Im Export ist der Marker eine eigene Zeile (`pattern_ases_marker` in
|
|
`cfg/export.cfg`) und liefert die `id` des AS-/ES-Eintrags in der `sepliste`.
|
|
|
|
**Zuordnung Eintrag -> Kopie (`csv:sepliste-paare`).** Verglichen wird nur
|
|
innerhalb desselben Typmarkers (`AS`/`ES`/`SEP`) und EXKLUSIV: alle Paare
|
|
eines Typs werden nach Abstand sortiert, das kuerzeste zuerst genommen, danach
|
|
sind Eintrag UND Kopie verbraucht. Zwei Gruende gegen das naheliegende "je
|
|
Eintrag die naechste Kopie":
|
|
|
|
- waehlen zwei Eintraege dieselbe Kopie, bleibt eine Kopie ohne Eintrag und
|
|
ein Eintrag ohne ID (genau dieses Muster verursacht in Python die
|
|
Separatoren ohne `Nachbarn`/`TrackIds`);
|
|
- EIN fehlerhafter Koordinatenwert in der sepliste (bekannter Fall: gespiegeltes
|
|
`x` bei `lfdnr=2`) verschiebt so keine anderen Zuordnungen mehr — alle
|
|
uebrigen Eintraege finden ihre Kopie ueber den kurzen Abstand zuerst, der
|
|
fehlerhafte bekommt den Rest zugeteilt. Liegt dieser Rest weiter als
|
|
`csv:sepliste-abstand-warnung-mm` (500 mm) entfernt, meldet der Export das
|
|
(`exp-sepliste-abstand`), ohne die Zuordnung zu verwerfen.
|
|
|
|
Die Sortierung ist eine strikte Totalordnung (Abstand, dann `lfdnr`, dann
|
|
Index) — `vl-sort` wirft Elemente weg, die unter der Vergleichsfunktion gleich
|
|
sind, zwei Paare mit identischem Abstand wuerden sonst zu einem verschmelzen.
|
|
Getestet in `tests/test_unit.lsp` (`csv:sepliste-paare/*`, zeichnungsfrei).
|
|
|
|
## Phase 2 (Python): `lib/export_csv.py::process_blocks`
|
|
|
|
Aufruf: `python export_csv.py <export_raw.json> <data_dir> <output.csv>`.
|
|
`main()` laedt das JSON, den Omniflo-Katalog (Boegen/Weichen) und ruft
|
|
`process_blocks(blocks, lookup)` auf, das die eigentliche Item-Liste baut.
|
|
|
|
CSV-Spalten (Header, siehe `format_csv_line`/`main`):
|
|
|
|
```text
|
|
Elementnummer;TeileArt;TeileId;Bezeichnung;Planquadrat;Anzahl;Position;Boundingbox;
|
|
Insertpoint;K1;K2;K3;K4;Nachbarn;TrackIds;Fehler;Warnungen;Merkmale
|
|
```
|
|
|
|
- **Position** = Mittenkoordinate x, y, z; **Boundingbox** = Ausdehnung
|
|
(Laenge, Breite, Hoehe) in x, y, z — beide aus `csv:get-bbox`
|
|
(`vla-getboundingbox`), siehe Phase 1.
|
|
- **Insertpoint** = KOS-String (24-Zeichen Base64, Position+Rotation als
|
|
Quaternion, `csv:kos-encode`) des Block-Einfuegepunkts selbst.
|
|
- **K1-K4** = Positions-Strings der im Block enthaltenen Koordinatensysteme
|
|
K1..K4 (12-Zeichen Base64, nur Position, `csv:trans-encode`) — nur bei
|
|
Omniflo Bogen/Weiche/Gerade und VF_n/GF_n (K1/K2 aus AS/ES) vorhanden,
|
|
sonst leer.
|
|
- **Fehler** = `"unverbunden"` (keine Partner, aber mindestens einer noetig),
|
|
`"nur ein Partner"` (ein Partner, aber mindestens zwei noetig) oder leer.
|
|
Kreisel/Eckrad/Omniflo brauchen mindestens einen Partner,
|
|
Gefaellestrecke/Foerderer/Strecke-Modul mindestens zwei
|
|
(`export_neighbors.MIN_PARTNER`).
|
|
- **Elementnummer** wird NICHT aus der TeileId uebernommen, sondern am Ende
|
|
neu von 1 an vergeben, nachdem alle Zeilen aufsteigend nach TeileId
|
|
sortiert wurden (`teileid_sort_key` in `process_blocks`). Ohne geloeschte
|
|
Elemente stimmen Elementnummer und TeileId dadurch ueberein; geloeschte
|
|
Elemente hinterlassen Luecken nur in der TeileId-Folge. Zeilen ohne
|
|
numerische TeileId (Omniflo-Sum-Zeile, SEP-/SCN-Fallback-IDs ohne
|
|
ID-Attribut) behalten stabil ihre bisherige Reihenfolge und stehen danach.
|
|
|
|
### Beispiel: Kreisel mit 2 Separatoren + VarioFoerderer mit 2 Separatoren
|
|
|
|
Ausgangslage in der Zeichnung: ein kleiner Kreisel (`KREISEL_1`, ID `0010`)
|
|
mit zwei frei aufgesetzten Separatoren auf seiner Bahn, daran andockend ein
|
|
`VF_1` mit einer Kette AS -> Separator -> Separator -> ES (IDs `0021`-`0024`).
|
|
|
|
**Ausschnitt `export_raw.json`** (von `csv:collect-export-blocks` /
|
|
`csv:block-to-json` geschrieben, hier gekuerzt auf die fuer dieses Beispiel
|
|
relevanten Felder):
|
|
|
|
```json
|
|
[
|
|
{
|
|
"block_name": "KREISEL_1",
|
|
"layer": "ILS_Kreisel",
|
|
"handle": "2A1",
|
|
"x": 10000.0, "y": 5000.0, "z": 0.0, "rotation": 0.0,
|
|
"attribs": {"ID": "0010", "DREHRICHTUNG": "UZS", "ABSTAND": "800"},
|
|
"insertpoint": "AAB3QAAATkAAAAAA...",
|
|
"bbox": {"min": [9200.0, 4200.0, 0.0], "max": [10800.0, 5800.0, 0.0]}
|
|
},
|
|
{
|
|
"block_name": "Separator_SP_300mm",
|
|
"layer": "ILS_Sensor",
|
|
"handle": "2A5",
|
|
"x": 10800.0, "y": 5000.0, "z": 0.0, "rotation": 0.0,
|
|
"attribs": {"ID": "0011"},
|
|
"insertpoint": "AAD4QAAATkAAAAAA..."
|
|
},
|
|
{
|
|
"block_name": "Separator_SP_300mm",
|
|
"layer": "ILS_Sensor",
|
|
"handle": "2A9",
|
|
"x": 10000.0, "y": 5800.0, "z": 0.0, "rotation": 90.0,
|
|
"attribs": {"ID": "0012"},
|
|
"insertpoint": "AAB3QAAAxEAAAAAA..."
|
|
},
|
|
{
|
|
"block_name": "VF_1",
|
|
"layer": "ILS_Foerderer",
|
|
"handle": "2B0",
|
|
"x": 10800.0, "y": 5000.0, "z": 0.0, "rotation": 0.0,
|
|
"attribs": {"ID": "0021", "Bezeichnung": ""},
|
|
"insertpoint": "AAD4QAAATkAAAAAA...",
|
|
"k1": "AAD4QAAATkAAAAAA...",
|
|
"k2": "AAAAQwAATkAAAAAA...",
|
|
"bbox": {"min": [10800.0, 4850.0, 0.0], "max": [13000.0, 5150.0, 300.0]},
|
|
"sepliste": [
|
|
{"typ": "AS", "lfdnr": 1, "x": 10800.0, "y": 5000.0, "id": "0025"},
|
|
{"typ": "SEP", "lfdnr": 2, "x": 11600.0, "y": 5000.0, "id": "0022"},
|
|
{"typ": "SEP", "lfdnr": 3, "x": 12300.0, "y": 5000.0, "id": "0023"},
|
|
{"typ": "ES", "lfdnr": 4, "x": 13000.0, "y": 5000.0, "id": "0026"}
|
|
]
|
|
},
|
|
{
|
|
"block_name": "Separator_SP_300mm",
|
|
"layer": "ILS_Sensor",
|
|
"handle": "2B4",
|
|
"x": 11600.0, "y": 5000.0, "z": 0.0, "rotation": 0.0,
|
|
"attribs": {"ID": "0022"}
|
|
},
|
|
{
|
|
"block_name": "Separator_SP_300mm",
|
|
"layer": "ILS_Sensor",
|
|
"handle": "2B8",
|
|
"x": 12300.0, "y": 5000.0, "z": 0.0, "rotation": 0.0,
|
|
"attribs": {"ID": "0023"}
|
|
}
|
|
]
|
|
```
|
|
|
|
Die beiden Separatoren `0022`/`0023` sind hier die ECHTEN, top-level
|
|
exportierten Separator-Bloecke (identisch mit den `SEP`-Eintraegen der
|
|
`sepliste` von `VF_1` — siehe "Die `sepliste`" oben: kein doppeltes Konzept,
|
|
zwei Blickwinkel auf dieselben Symbole).
|
|
|
|
**Resultierende CSV-Zeilen** (Header + betroffene Zeilen, Spalten
|
|
`Insertpoint`/`K3`/`K4`/`Merkmale` fuer die Lesbarkeit gekuerzt):
|
|
|
|
```text
|
|
Elementnummer;TeileArt;TeileId;Bezeichnung;Planquadrat;Anzahl;Position;Boundingbox;Insertpoint;K1;K2;K3;K4;Nachbarn;TrackIds;Fehler;Warnungen;Merkmale
|
|
1;"ILS 2.0 Kreisel";"0010";"Kreisel :1";"B/3";1;"10000.0, 5000.0, 0.0";"1600.0, 1600.0, 0.0";"...";"";"";"";"";"0011, 0012, 0021";"0011, 0012, 0021, 0022, 0023, 0024";"";"";{"Abstand":"800"}
|
|
2;"ILS 2.0 Separator";"0011";"Separator :1";"B/3";1;"10800.0, 5000.0, 0.0";"210.0, 150.0, 14.0";"...";"";"";"";"";"0010, 0012";"";"";"";{"Zuordnung":"0010"}
|
|
3;"ILS 2.0 Separator";"0012";"Separator :2";"B/3";1;"10000.0, 5800.0, 0.0";"150.0, 210.0, 14.0";"...";"";"";"";"";"0011, 0021";"";"";"";{"Zuordnung":"0010"}
|
|
4;"ILS 2.0 VarioFoerderer";"0021";"VarioFoerderer :1";"B/3";1;"11900.0, 5000.0, 0.0";"2200.0, 300.0, 300.0";"...";"...";"...";"";"";"0010, 0024";"0021, 0022, 0023, 0024";"";"";{}
|
|
5;"ILS 2.0 Separator";"0022";"Separator :3";"C/4";1;"11600.0, 5000.0, 0.0";"210.0, 150.0, 14.0";"...";"";"";"";"";"";"";"";"";{"Zuordnung":"0021"}
|
|
6;"ILS 2.0 Separator";"0023";"Separator :4";"C/4";1;"12300.0, 5000.0, 0.0";"210.0, 150.0, 14.0";"...";"";"";"";"";"";"";"";"";{"Zuordnung":"0021"}
|
|
```
|
|
|
|
Zu beachten:
|
|
|
|
- Der Kreisel (`0010`) traegt in `TrackIds` den KOMPLETTEN Umlauf inkl. der
|
|
VF-Kettenenden (`0021`=AS, `0024`=ES, aus `compute_kreisel_umlauf`), waehrend
|
|
seine `Nachbarn`-Spalte nur die unmittelbaren Umlauf-Nachbarn zeigt.
|
|
- `VF_1` (`0021`) bekommt seine eigene `TrackIds`-Spalte
|
|
(`write_separatorliste_merkmale`) aus der `sepliste`: AS -> SEP -> SEP -> ES
|
|
in Baureihenfolge, unabhaengig vom Kreisel-Umlauf.
|
|
- Die AS/ES-Enden (`0021`=AS, `0024`=ES) sind eigene Zeilen (Zeilentyp
|
|
"ILS Ausschleuselement"/"ILS Einschleuselement"), direkt aus dem
|
|
dauerhaften `ZZ_ASES_MARKER` klassifiziert — in diesem gekuerzten Beispiel
|
|
nicht einzeln aufgefuehrt, aber ihre TeileIds tauchen in `TrackIds` beider
|
|
Seiten auf.
|
|
- Die Separatoren 3 und 5 (`0012`/`0022`) unterscheiden sich nur durch ihre
|
|
`Zuordnung`: `0012` haengt am Kreisel, `0022`/`0023` an der VF-Kette — beide
|
|
Faelle laufen ueber denselben `pattern_separator`-Blocktyp.
|
|
|
|
### 1. Blöcke klassifizieren
|
|
|
|
Eine grosse Schleife über alle rohen JSON-Blöcke ordnet jeden Block anhand
|
|
der `cfg/export.cfg [blockpattern]`-Muster einer TeileArt zu (Kreisel,
|
|
Eckrad, Omniflo Bogen/Weiche/Gerade, VF-Strecke, Gefaellestrecke,
|
|
Strecke-Modul, Separator, Scanner, BTMT Be-/Entladung, Eingang/Ausgang) und
|
|
baut daraus ein
|
|
Item-dict mit `teileart`, `teileid`, `bezeichnung`, `planquadrat`,
|
|
`merkmale` und den bbox-/KOS-Spalten (`bbox_columns`). VF_n/GF_n-Items
|
|
bekommen dabei zusaetzlich `x`/`y` (Kettenanfang, fuer die Schleuselement-
|
|
Berechnung) und `sepliste` (fuer die Ketten-Nachbarschaft) durchgereicht.
|
|
|
|
Die `bezeichnung`-Spalte von VF_n/GF_n/Kreisel/Eckrad kommt ueber
|
|
`bezeichnung_oder_fallback` NICHT nur aus der Lauf-Nummerierung
|
|
("VarioFoerderer :3", "Kreisel :2" usw.), sondern zuerst aus dem
|
|
Attribut `"Bezeichnung"` des Blocks (ATTDEF, in BricsCAD frei editierbar -
|
|
bei Kreisel z.B. ueber das "name"-Dialogfeld). Ist es leer oder fehlt es
|
|
(Altbestand ohne das Attribut), bleibt es bei der generierten
|
|
Standardbezeichnung. Damit laesst sich ein in BricsCAD umbenanntes Element
|
|
im CSV-Export wiedererkennen.
|
|
|
|
Separator/Scanner-Items ohne Bounding-Box (weil `vla-getboundingbox`
|
|
scheiterte) bekommen einen Fallback: `_fallback_separator_bbox` baut aus der
|
|
festen Symbol-Groesse (`cfg/export.cfg [Boundingbox] separator_box_*`,
|
|
Default 210x150x14mm) und der bekannten Position/Rotation eine synthetische
|
|
`_bbox`, damit der Separator trotzdem an der Nachbarschaftserkennung
|
|
teilnimmt.
|
|
|
|
#### Eingang/Ausgang — nur im CSV-Export
|
|
|
|
Die Materialfluss-Ein-/Ausgangssymbole (Bloecke `Eingang_<DIM>`/`Ausgang_<DIM>`,
|
|
eingefuegt ueber `ils-insert-station`, Befehle `ILS_EINGANG_INSERT`/
|
|
`ILS_AUSGANG_INSERT`) werden **nur im CSV-Export** als eigene Zeile gefuehrt
|
|
(TeileArt `"Eingang"`/`"Ausgang"`, Merkmale wie BTMT: ARTINR/Hoehe/Drehung).
|
|
`export_sivas.py` ueberspringt sie bewusst — ein Fluss-Symbol hat keinen
|
|
eigenen Sivas-Artikel.
|
|
|
|
Wie Separator/AS/ES nehmen sie als **freie Umlauf-Punkte**
|
|
(`KREISEL_UMLAUF_TEILEARTEN`) an der `"TrackIds"`-Spalte teil und tauchen so
|
|
auch auf einem Kreisel auf, auf dessen Bahn sie liegen. Voraussetzung fuer
|
|
eine ueber Exportlaeufe stabile TeileId ist ein `ID`-ATTDEF in den DWGs
|
|
(nachgetragen, dokumentiert in `data/ils/allocations.json`); `ssg-id-check-all`
|
|
fuellt es vor jedem Export, und `ssg-id-collect-blocks` (`Lisp/ssg_id.lsp`)
|
|
erfasst die Bloecke dafuer. Fehlt die ID (noch), greift wie bei BTMT eine
|
|
lauf-lokale Fallback-Nummer (`EIN-`/`AUS-`-Praefix).
|
|
|
|
### 2. `compute_neighbor_ids` — die generische Bounding-Box-Nachbarschaft
|
|
|
|
Datei `lib/export_neighbors.py`. Testet drei Gruppen gegeneinander (NICHT
|
|
alle Elemente gemeinsam — das waere bei grossen Omniflo-Anlagen zu teuer bzw.
|
|
fachlich falsch):
|
|
|
|
1. Kreisel/Eckrad gegen Kreisel/Eckrad. Ein echter Kreisel wird dafuer in
|
|
eine linke und rechte Haelfte gesplittet (`kreisel_half_bboxes`) — die
|
|
Trennlinie ist die Achse durch Antriebs- und Spannstation.
|
|
2. Kreisel/Eckrad gegen Gefaellestrecke/Foerderer/Strecke-Modul. Fuer
|
|
GF/VF wird dabei NICHT die grosse Wrapper-Box getestet, sondern je eine
|
|
kleine feste Box an der AS- (K1) und ES-Position (K2) — nur dort beruehrt
|
|
eine Strecke tatsaechlich einen Kreisel.
|
|
3. Omniflo-Elemente gegen Omniflo-Elemente (Bogen/Weiche/Gerade), per
|
|
STRtree-Broad-Phase + KOS-Verfeinerung ueber K1-K4.
|
|
|
|
Ergebnis ist die rohe `Nachbarn`-Spalte fuer Kreisel/Eckrad/GF/VF (fuer
|
|
Separator/Scanner bewusst NICHT — die werden unten anders behandelt) sowie
|
|
die `Fehler`-Spalte (`compute_neighbor_errors`: "unverbunden"/"nur ein
|
|
Partner", je nach Mindestanzahl Partner pro TeileArt).
|
|
|
|
### 3. Synthetische Zeilen an den Uebergangsstellen
|
|
|
|
Nur noch EINE Art von "Zeile ohne eigene Zeichnungsgeometrie" wird aus der
|
|
Geometrie bereits klassifizierter Items abgeleitet:
|
|
|
|
- **`compute_kreisel_touch_switches`** — praezise Kapsel-Geometrie (nicht nur
|
|
Bounding-Box) findet jeden Punkt, an dem sich zwei echte Kreisel wirklich
|
|
beruehren, und erzeugt dort eine `"ILS Weiche"`-Zeile
|
|
(`build_kreisel_weiche_items`). Das ist der Verzweigungspunkt, an dem
|
|
Material von einem Kreisel zum anderen wechseln kann.
|
|
|
|
AS/ES-Kettenenden werden NICHT mehr synthetisiert (fruehere Version:
|
|
`compute_strecke_kreisel_schleus`/`build_strecke_schleus_items` suchten den
|
|
anschliessenden Kreisel per Kollisionstest und erfanden dabei eine eigene,
|
|
bei jedem Export neue TeileId). Der dauerhafte `ZZ_ASES_MARKER`
|
|
(Lisp/export.lsp, siehe oben) ist ein echter Block mit echter Geometrie -
|
|
`"ILS Ausschleuselement"`/`"ILS Einschleuselement"` sind darum eine ganz
|
|
normale Zeile aus Schritt 1 (Klassifikation), TeileId/Position/Boundingbox
|
|
direkt vom Marker-Block selbst.
|
|
|
|
`map_sepliste_kette_items` (siehe Abschnitt 5 unten) ordnet danach jedem
|
|
sepliste-Eintrag (AS/SEP/ES) das dazu passende, bereits vorhandene Item zu.
|
|
Erzeugt bewusst KEINE eigenen Zeilen.
|
|
|
|
### 4. Der Kreisel-Umlauf — wie Nachbarn ueber einen Kreisel hinweg entstehen
|
|
|
|
`compute_kreisel_umlauf` ist der zentrale Baustein, der freie Punkte
|
|
(Separatoren, BTMT Beladung/Entladung, AS/ES-Enden) und Kreisel-Kreisel-
|
|
Weichen zu einer widerspruchsfreien Nachbarschaft zusammenfuehrt. Fuer jeden
|
|
echten Kreisel werden alle Punkte gesammelt, die dort geometrisch liegen:
|
|
|
|
- freie Punkte (`KREISEL_UMLAUF_TEILEARTEN` in `lib/export_neighbors.py`:
|
|
Separator, BTMT Beladung, BTMT Entladung, Eingang, Ausgang UND die AS-/ES-
|
|
Ein-/Ausschleuselemente — alles Stationen/Markierungen direkt auf der
|
|
Bahn/dem Track mit eigener Bounding-Box), deren Bounding-Box die Kreisel-
|
|
Box ueberschneidet. Ein AS/ES bringt seine Box vom eigenen `ZZ_ASES_MARKER`
|
|
mit — keine gesonderte Naeherungslogik mehr noetig (fruehere Version
|
|
suchte hierfuer je AS/ES-sepliste-Eintrag den geometrisch naechstgelegenen
|
|
Kreisel, unabhaengig davon ob er wirklich in Reichweite lag),
|
|
- die Kreisel-Kreisel-Weichen aus Schritt 3 (in BEIDEN beteiligten
|
|
Kreisel-Umlaeufen, da eine Weiche der gemeinsame Verzweigungspunkt ist —
|
|
man kann von Kreisel A nach B und zurueck).
|
|
|
|
Alle Punkte werden nach Winkel um die Kreiselachse sortiert (Richtung ueber
|
|
das `DREHRICHTUNG`-Attribut, UZS/GUZ) — das ergibt eine geschlossene,
|
|
zirkulaere Reihenfolge. Vorgaenger/Nachfolger in dieser Reihenfolge werden
|
|
zu `Nachbarn`.
|
|
|
|
Die Matching-Box eines Kreisels ist dabei die tatsaechliche, um die Drehung
|
|
korrigierte Kapsel-Bounding-Box (nicht "Laenge entlang Welt-X" angenommen) —
|
|
ein 90 Grad gedrehter, langer Kreisel haette sonst eine falsch orientierte
|
|
(zu breite/zu schmale) Box und faengt Punkte faelschlich ein oder verpasst
|
|
echte Nachbarn.
|
|
|
|
### 5. Ketteninterne Nachbarn — `compute_sep_kette` + Aufloesung
|
|
|
|
`compute_sep_kette` liest jede `sepliste` und baut daraus einen Schluessel
|
|
je Eintrag (`"<strecke_id>#<lfdnr>"`) mit `vorgaenger`/`nachfolger`
|
|
(wieder solche Schluessel, `None` am Kettenende) sowie `id` (die TeileId des
|
|
vertretenden Items, siehe oben). Das ist eine reine Uebersetzung der
|
|
LISP-Baureihenfolge — kein Rateverfahren.
|
|
|
|
**`map_sepliste_kette_items`** loest jeden sepliste-Schluessel auf sein
|
|
bereits vorhandenes Item auf — EXAKT per ID-Gleichheit (Feld `id` gegen die
|
|
`teileid` der klassifizierten Items), nicht mehr per Koordinaten-Naehe:
|
|
AS/ES sind eigene Items (Schritt 3 entfaellt fuer sie), SEP ein bereits
|
|
klassifiziertes Separator-Item (der verpackte Separator liegt durch
|
|
`csv:sep-proxies-erzeugen` bereits als echtes Item vor — eine zweite, aus
|
|
der sepliste erzeugte Zeile waere derselbe physische Separator ein zweites
|
|
Mal). Nur wenn das `id`-Feld fehlt (Altbestand-Export ohne Marker/Kopie-ID),
|
|
faellt SEP auf den alten Abgleich zurueck: Wrapper-ID (Item-Merkmal
|
|
`"Zuordnung"`) plus naechstgelegener rohen INSERT-Punkt (Item-Felder
|
|
`_x`/`_y` vs. sepliste-Koordinate). AS/ES ohne `id` bleiben unaufgeloest -
|
|
fuer sie existiert kein Item mehr, das sie ersatzweise vertreten koennte.
|
|
|
|
Die Treffer landen zunaechst nur in `sep_kette_by_key` (Schluessel -> Item),
|
|
OHNE die `Nachbarn`-Spalte zu setzen: ein Schluessel kann auf eine Zeile
|
|
zeigen, deren eigener Nachbar (Vorgaenger/Nachfolger) zu diesem Zeitpunkt
|
|
selbst noch nicht aufgeloest ist. `resolve_umlauf_und_sepliste_nachbarn`
|
|
loest danach in einem einzigen Schritt sowohl die Kreisel-Umlauf-Punkte als
|
|
auch die sepliste-Schluessel gegen einen gemeinsamen Lookup (freie Punkte
|
|
nach TeileId, Weichen nach Schluessel, sepliste-Zeilen nach Schluessel) zu
|
|
echten TeileIds auf.
|
|
|
|
### Die Spalte `"TrackIds"` — Reihenfolge als eigene CSV-Spalte
|
|
|
|
Zusaetzlich zur `"Nachbarn"`-Spalte (Vorgaenger/Nachfolger je Zeile) traegt
|
|
`write_separatorliste_merkmale` (`lib/export_csv.py`) jeder VF_n/GF_n-Zeile
|
|
die eigene Spalte `"TrackIds"` (`item["trackids"]`, Top-Level-Feld wie
|
|
`"nachbarn"` — KEIN Merkmal) ein: die TeileIds von AS-Ende, Separatoren UND
|
|
ES-Ende in Baureihenfolge, kommagetrennt (z.B. `"0056, 0052, 0053, 0057"`
|
|
fuer AS, zwei SEP, ES). AS/ES zaehlen mit, weil sie beim Kreisel (siehe
|
|
unten) ebenfalls Teil von dessen `TrackIds` sind — ohne sie fehlten hier die
|
|
Kettenenden. Quelle ist die rohe `sepliste` des Items (nicht `sep_kette`,
|
|
dessen dict-Reihenfolge nicht garantiert ist). Muss NACH
|
|
`map_sepliste_kette_items` laufen, weil erst dann jeder sepliste-Schluessel
|
|
(AS/SEP/ES) auf sein reales Item (TeileId) aufgeloest ist. Fehlt einem
|
|
Eintrag die TeileId (kein `id`-Feld und - nur bei SEP - kein Koordinaten-
|
|
Fallback-Treffer), fehlt dieses Kettenglied dann in `TrackIds` (kein Fehler,
|
|
sondern Folge der fehlenden Zuordnung).
|
|
|
|
### Konsistenzpruefung: AS/ES in beiden Containern
|
|
|
|
`pruefe_ases_in_beiden_containern` (`lib/export_csv.py`, laeuft am Ende von
|
|
`process_blocks`, NACH beiden `TrackIds`-Spalten) prueft fuer jede AS-/ES-
|
|
Zeile, ob ihre TeileId sowohl in den `TrackIds` ihrer EIGENEN Strecke
|
|
(ZUORDNUNG-Attribut des Markers) als auch in den `TrackIds` MINDESTENS eines
|
|
Kreisels auftaucht — beide "Container", zu denen ein AS/ES logisch gehoert.
|
|
Fehlt einer von beiden, ist das kein Normalfall (z.B. ZUORDNUNG leer/falsch,
|
|
Kollisionstoleranz zu knapp, Kreisel zu weit weg, fehlende Bounding-Box am
|
|
Marker): die Zeile bekommt eine Eintragung in der Spalte `"Warnungen"`
|
|
(`item["warnung"]`) UND eine Konsolen-/Debuglog-Meldung.
|
|
|
|
`write_kreisel_separatorliste_merkmale` ueberträgt dasselbe Prinzip auf
|
|
echte Kreisel (`"ILS 2.0 Kreisel"`): Quelle ist hier NICHT die sepliste
|
|
(die kennt nur eine einzelne VF_n/GF_n-Kette), sondern
|
|
`compute_kreisel_umlauf` (`lib/export_neighbors.py`) — der bereits fuer die
|
|
Kreisel-`"Nachbarn"`-Spalte berechnete, zirkulaere Umlauf aller auf dem
|
|
Kreisel liegenden Punkte (freie Punkte — Separator UND BTMT Beladung/
|
|
Entladung, siehe `KREISEL_UMLAUF_TEILEARTEN` —, AS/ES-Enden andockender
|
|
VF_n/GF_n-Ketten, Kreisel-Kreisel-Weichen), sortiert nach Winkel um die
|
|
Kreiselachse in Richtung des `DREHRICHTUNG`-Attributs (UZS/GUZ). Die
|
|
zurueckgegebene Liste beginnt IMMER bei Winkel 0 Grad (Kapsel-Achsrichtung,
|
|
kleinster Winkel >= 0 Grad in Umlaufrichtung) — `write_kreisel_
|
|
separatorliste_merkmale` loest die rohen Umlauf-Schluessel (TeileId eines
|
|
freien Punkts, sep_kette-Schluessel oder Weichen-Key) auf echte TeileIds auf
|
|
und schreibt sie kommagetrennt in `item["trackids"]` auf den Kreisel selbst.
|
|
Muss darum NACH `resolve_umlauf_und_sepliste_nachbarn` (bzw. nach allen
|
|
Zeilen-Registrierungen: `build_kreisel_weiche_items`,
|
|
`map_sepliste_kette_items`) laufen.
|
|
|
|
BTMT Beladung/Entladung sind bewusst NICHT in `SENSOR_TEILEARTEN`
|
|
aufgenommen (die bleibt Separator/Scanner vorbehalten, siehe
|
|
`compute_sensor_zuordnung`/`compute_scanner_nearest_separator` in Abschnitt
|
|
6) — eine eigene Konstante `KREISEL_UMLAUF_TEILEARTEN` sammelt stattdessen
|
|
genau die TeileArten, die als frei platzierter Punkt auf einer Kreisel-Bahn
|
|
liegen koennen.
|
|
|
|
### 6. Sensor-Zuordnung, Ausgabe
|
|
|
|
`compute_sensor_zuordnung` bestimmt fuer jeden Separator/Scanner ohne
|
|
bekannte (fixierte) Zuordnung das Traegerelement (Gefaellestrecke > Foerderer
|
|
> Kreiselhaelfte, per Bounding-Box). `compute_scanner_nearest_separator`
|
|
haengt jedem Scanner den raeumlich naechsten Separator an. Am Ende werden
|
|
alle Items nach TeileId sortiert und als CSV-Zeilen ausgegeben
|
|
(`format_csv_line`).
|
|
|
|
## Warum eine Zeile keine Nachbarn hat — Diagnose-Leitfaden
|
|
|
|
| Symptom | Wahrscheinlichste Ursache |
|
|
|---|---|
|
|
| ALLE Zeilen ohne Nachbarn, auch Kreisel | `bbox`-Feld fehlt im JSON komplett (`vla-getboundingbox` scheiterte global) — pruefen, ob `csv:run-export` eine `exp-bbox-fail`-Warnung ausgegeben hat |
|
|
| Nur GF/VF ohne Nachbarn, Separatoren teils auch | `sepliste` wurde nicht ans Item durchgereicht, oder die Strecke beruehrt geometrisch keinen Kreisel (`Fehler = "unverbunden"`) |
|
|
| Ein Separator/Scanner ohne Nachbarn, Rest ok | Separator ist weder Teil einer sepliste-Kette noch nah genug an einem Kreisel (freier, isoliert platzierter Sensor) — legitimer Fall, keine Fehlkonfiguration |
|
|
| Zwei sich beruehrende Kreisel haben falsche/keine Nachbarn am Beruehrpunkt | Pruefen, ob `compute_kreisel_touch_switches` ueberhaupt eine Weiche erzeugt (Toleranz `[Nachbarschaft] toleranz_mm`) und ob sie im Kreisel-Umlauf beider Kreisel auftaucht |
|
|
| `Nachbarn` enthaelt einen Wert wie `"0026#3"` statt einer TeileId | Regression im Aufloese-Schritt — ein sep_kette-Schluessel wurde nicht durch `resolve_umlauf_und_sepliste_nachbarn` ersetzt (sollte nicht mehr vorkommen, siehe Tests `test_export_kreisel_umlauf.py`) |
|
|
|
|
Das Kollisions-/Nachbarschafts-Debugprotokoll (`cfg/export.cfg`
|
|
`[Nachbarschaft] debug_log=1`) schreibt eine `.dbg`-Datei mit jeder
|
|
Klassifikation, Gruppenzuordnung und jedem Ueberschneidungstest — der
|
|
schnellste Weg, einer fehlenden Nachbarschaft auf den Grund zu gehen.
|
|
|
|
## Relevante Tests
|
|
|
|
- `tests/test_export_kreisel_umlauf.py` — Kreisel-Kreisel-Weiche im Umlauf,
|
|
rotationsunabhaengige Kreisel-Matching-Box, sepliste-Nachbarn-Aufloesung
|
|
ohne rohe Schluessel in der Ausgabe, Kreisel-Umlauf-Start bei 0 Grad,
|
|
BTMT Beladung/Entladung als freie Umlauf-Punkte, `"TrackIds"`-Spalte.
|
|
- `tests/test_export_separator_bbox.py` — feste Separator-Symbol-Box
|
|
(Geometrie + Fallback bei fehlender `vla-getboundingbox`-Box).
|
|
- `tests/test_export_bezeichnung.py` — `"Bezeichnung"`-Spalte von
|
|
VF_n/GF_n/Kreisel/Eckrad nutzt das Anwender-Attribut, sofern gesetzt.
|
|
|
|
Alle laufen komplett ohne BricsCAD (reine Python-Geometrie/-Logik).
|