- ZUORDNUNG: ssg-id-check-all laeuft jetzt ZUERST, danach sind die Wrapper-IDs endgueltig - Separator-Kopien, AS/ES- und Einheiten-Marker bekommen ZUORDNUNG direkt beim Erzeugen. Entfallen: csv:sep-proxies-zuordnung-setzen, csv:asmark-ids-setzen, csv:unitmark-ids-setzen. zuordnung_fix/*cs-sep-fix-by-handle* bleibt (count_sep_scan rechnet eigenstaendige Sensoren bewusst neu). - sepliste-IDs: Zuordnung ueber die KS-Kettenfolge je Typ (csv:sepliste-kette-paare) statt ueber die teils gespiegelten sepliste-Koordinaten (csv:sepliste-paare, csv:sepliste-ids-setzen entfallen). Die Kette wird je Lauf einmal berechnet (csv:kette-von); die ID liest csv:block-to-json erst beim Schreiben. Behebt vertauschte TrackIds/Nachbarn in HundM05 VF_1 (0031/0029). - Element-Klassifizierung: eine Tabelle csv:element-familien (cfg-Muster) fuer Suche und Klassifizierung; csv:proxy-typ entfaellt. - Einheiten-Marker: K1/K2 nur noch in *csv-baustein-by-handle* (csv:geometrie-merken), K1/K2-Attribute und csv:unitmark-k-kos-strings entfallen. Geprueft: HundM05-Export alt/neu headless verglichen - einzige Abweichung ist die korrigierte Reihenfolge in VF_1. LISP-Unit-Tests 303/303. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
693 lines
39 KiB
Markdown
693 lines
39 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).
|
|
|
|
Jeder Wert wird dabei genau EINMAL gesetzt; die Reihenfolge sorgt dafuer,
|
|
dass er beim Setzen schon feststeht (`csv:export-vorbereiten`):
|
|
|
|
1. **`ssg-id-check-all`** — ZUERST, noch bevor ein Stellvertreter existiert:
|
|
vergibt fehlende IDs und korrigiert Duplikate. Danach ist die ID jedes
|
|
Wrappers (`VF_n`/`GF_n`/`KREISEL_n`) endgueltig, die folgenden Schritte
|
|
koennen `ZUORDNUNG` direkt beim Erzeugen setzen.
|
|
2. **`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. Dabei bekommt die Kopie `ZUORDNUNG = Wrapper-ID` (bekannt,
|
|
nicht geraten) und wird in `*cs-sep-fix-by-handle*` als "bekannt"
|
|
vermerkt: `count_sep_scan.lsp` und `export_csv.py` (Feld `zuordnung_fix`)
|
|
berechnen die Zuordnung EIGENSTAENDIGER Sensoren bei jedem Lauf
|
|
geometrisch neu und nehmen verpackte Separatoren damit davon aus.
|
|
Rueckgabe: Liste von Records `(proxy-ename wrapper-ename "SEP" (x y z)
|
|
trennsteg-ent)`.
|
|
2b. **`csv:asmark-aktualisieren`** — die AS-/ES-Enden derselben Kette
|
|
(`AS_Element*`/`ES_Element*`, Muster `pattern_schleuselement`) bekommen
|
|
KEINE Kopie des Elements, sondern einen eigenen (ebenfalls temporaeren)
|
|
Marker, mit `ZUORDNUNG` direkt beim Erzeugen — siehe eigener Abschnitt
|
|
"Der AS/ES-Marker" unten.
|
|
2c. **`csv:element-proxies-erzeugen`** — nur Modus `all_elements`: Kopien der
|
|
uebrigen Kettenbausteine (Boegen, Streckenelemente, Umlenk-/Motorstation).
|
|
3. **`csv:ketten-verarbeiten`** — je `VF_n`/`GF_n` die KS-geordnete Kette
|
|
(`csv:kette-von`, einmal je Lauf berechnet) auswerten: Einheiten-Proxies
|
|
(Modus `group_vf_gf`), IDs in Kettenfolge, Separator-Typ VF/GF
|
|
(`csv:kette-sep-typen`) und die Stellvertreter der `sepliste`-Eintraege
|
|
(`csv:sepliste-kette-paare`, siehe eigener Abschnitt unten). Alles aus
|
|
DERSELBEN Kette - Einheiten-BB, Separator-Typ und TrackIds passen damit
|
|
zusammen.
|
|
3b. **`ssg-id-check-all`** (zweiter Lauf) — Restnummerierung (z.B. Kreisel-
|
|
Separator ohne Kette) und Duplikat-Korrektur unter den neuen
|
|
Stellvertretern; danach `csv:res-ids-schreiben` (reservierte IDs).
|
|
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.
|
|
|
|
**Stabile IDs ueber Exportlaeufe.** Alle diese Objekte sind temporaer und
|
|
werden nach dem Export geloescht (ausser mit `csv_leave_proxies`). Ihre ID
|
|
bleibt trotzdem gleich: nach `ssg-id-check-all` schreibt
|
|
`csv:res-ids-schreiben` sie als **reservierte IDs** an den VF_n/GF_n-Wrapper
|
|
(XDATA `SSG_ID_RES`, Helfer `ssg-id-res-*` in `Lisp/ssg_id.lsp`), der naechste
|
|
Export gibt sie dem neuen Objekt an derselben Stelle zurueck. Schluessel:
|
|
|
|
| Schluessel | Objekt |
|
|
|---|---|
|
|
| `AS\|n` / `ES\|n` | AS-/ES-Marker, n = sepliste-`lfdnr` |
|
|
| `K\|i` | Separator-/Element-Kopie des i-ten Bausteins der geordneten Kette (0-basiert, `csv:kette-ordnen`) |
|
|
| `U\|n` | n-te Foerder-/Gefaelleeinheit des Wrappers (`group_vf_gf`) |
|
|
|
|
`ssg-id-max-in-ss` zaehlt die reservierten IDs mit, damit sie in der
|
|
Zwischenzeit an keinen anderen Block vergeben werden. Nur Objekte OHNE
|
|
reservierte ID (erster Export einer Kette, Modus neu gewaehlt) bekommen eine
|
|
neue Nummer - oberhalb aller vorhandenen, also nicht mehr zwingend in
|
|
Kettenfolge neben den uebrigen Zeilen der Kette. Ein Edit baut einen neuen
|
|
Wrapper ohne diese XDATA; dessen Kettenobjekte werden dann neu nummeriert.
|
|
|
|
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.
|
|
Auch das Merkmal `Typ` (VF/GF) der Separator-Zeilen kommt aus dieser
|
|
Schneidung (`csv:kette-sep-typen`): der Separator erbt den Typ seiner Einheit,
|
|
also VF fuer den Separator direkt vor der Umlenkstation und jeden Separator
|
|
zwischen Umlenk- und Motorstation, sonst GF. Die fruehere bauzeitige
|
|
XDATA-Markierung `SSG_SEP_TYP` an jedem Trennsteg wird nicht mehr geschrieben
|
|
oder gelesen - sie war an 15 Einfuegestellen verteilt und dort teils falsch.
|
|
|
|
**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
|
|
|
|
JEDER Eintrag traegt zusaetzlich die `id` des realen Bauteils, das ihn in der
|
|
Zeichnung vertritt (zugeordnet in `csv:ketten-verarbeiten`, die ID selbst
|
|
liest `csv:block-to-json` erst beim Schreiben vom Stellvertreter):
|
|
|
|
```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
|
|
temporaere `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
|
|
nach dem Export wie alle Hilfskopien wieder GELOESCHT wird (nur der
|
|
Debug-Schalter `csv_leave_proxies` bzw. der Haken im Dialog
|
|
`CSV_EXPORT_KONFIG` laesst ihn zur Ansicht stehen):
|
|
|
|
- **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`.
|
|
Danach legt `csv:res-ids-schreiben` die ID als **reservierte ID** am
|
|
VF_n/GF_n-Wrapper ab (Eintrag `AS|1=0032`, siehe "Stabile IDs ueber
|
|
Exportlaeufe" oben). Der naechste Export gibt sie dem neu gesetzten Marker
|
|
zurueck, **ein AS/ES behaelt seine TeileId also ueber Exportlaeufe
|
|
hinweg**, obwohl der Marker dazwischen nicht existiert.
|
|
|
|
Der Marker ist abgeleitete Geometrie: jeder Export baut ihn neu auf (die Lage
|
|
folgt damit automatisch einer umgebauten Kette) und uebernimmt die ID ueber
|
|
den Schluessel (`TYP`, `LFDNR`) aus der reservierten ID des Wrappers — bewusst
|
|
nicht ueber die Position, damit eine verschobene Kette ihre TeileIds behaelt.
|
|
Steht noch ein Marker in der Zeichnung (aeltere Zeichnung, in der die Marker
|
|
frueher dauerhaft blieben, oder ein Lauf mit `csv_leave_proxies`), wird seine
|
|
ID ueber (`ZUORDNUNG`, `TYP`, `LFDNR`) uebernommen, falls der Wrapper noch
|
|
keine reservierte ID fuehrt; der alte Marker wird dabei geloescht, ebenso
|
|
jeder, der keinem Kettenende mehr entspricht.
|
|
|
|
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 -> Bauteil (`csv:sepliste-kette-paare`).** Ueber die
|
|
REIHENFOLGE je Typ: das k-te `AS`/`SEP`/`ES` der sepliste (nach `lfdnr`) ist
|
|
das k-te `AS`/`SEP`/`ES` der KS-geordneten Kette (`csv:kette-von`). Die
|
|
sepliste entsteht beim Bau per `entnext`-Wanderung in Baureihenfolge, und
|
|
gebaut wird in Foerderrichtung - dieselbe Folge, die die KS-Kette
|
|
rekonstruiert. Die x/y-Koordinaten der sepliste werden dafuer NICHT benutzt.
|
|
|
|
Frueher lief die Zuordnung ueber den kuerzesten Abstand zwischen sepliste-
|
|
Koordinate und Kopie (`csv:sepliste-paare`). Die sepliste-Koordinaten sind
|
|
aber teils gespiegelt (bekannter Fall: `x` bei `lfdnr=2`) - das vertauschte
|
|
real zwei Separatoren von HundM05 `VF_1` in TrackIds und Nachbarn. Passen
|
|
sepliste und Kette in der Anzahl nicht zusammen, meldet der Export das
|
|
(`exp-sepliste-ohne-proxy`); die ueberzaehligen Eintraege bleiben ohne `id`.
|
|
Getestet in `tests/test_unit.lsp` (`csv:sepliste-kette-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
|
|
`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 `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).
|