sepliste: Bauteil-IDs eintragen, AS/ES ueber dauerhaften Marker sichtbar machen

Stufe 1 (LISP-Export-Pfad): export_raw.json enthaelt jetzt fuer jeden
sepliste-Eintrag (AS/SEP/ES) einer VF_n/GF_n-Kette die ID des realen
Bauteils, das ihn vertritt.

- csv:sep-proxies-erzeugen (Separatoren) und die neuen AS/ES-Marker teilen
  sich csv:nested-welt-records/csv:block-einfuegen-welt zur Platzierung.
- csv:sepliste-paare ordnet sepliste-Eintraege ihrem Bauteil exklusiv nach
  Abstand zu (kuerzester zuerst, dann verbraucht) statt "jeder Eintrag
  nimmt sich das naechste" - ein einzelner fehlerhafter Koordinatenwert
  (bekannter Bug: gespiegeltes x bei lfdnr=2) verschiebt dadurch keine
  andere Zuordnung mehr.
- Neu: ZZ_ASES_MARKER - ein kleines Rechteck an jedem AS/ES-Kettenende,
  das nach dem Export STEHEN BLEIBT (anders als die temporaeren
  Separator-Kopien). Macht das sonst unsichtbare, in der Wrapper-
  Blockdefinition verpackte Kettenende in BricsCAD sichtbar/anklickbar
  und traegt seine ID unsichtbar am Attribut ID - dadurch behaelt ein
  AS/ES seine TeileId ueber Exportlaeufe hinweg. Wird bei jedem Export
  neu aufgebaut (Lage folgt der Kette), Wiederfund ueber ZUORDNUNG+TYP+
  LFDNR, verwaiste Marker werden entfernt.

In BricsCAD verifiziert (5 HundM05-Ketten, zwei Exportlaeufe: gleiche
Marker-IDs, korrekte sepliste-"id"-Felder). Dabei einen realen Bug
gefunden und behoben: die Blockdefinition des Markers uebernahm ueber
einen ungefilterten entnext-Walk versehentlich auch die Attribute des
zuletzt eingefuegten Blocks (VF_5 verlor dadurch alle Attribute inkl.
ID) - jetzt auf LWPOLYLINE/ATTDEF gefiltert.

9 neue zeichnungsfreie Unit-Tests (csv:sepliste-paare/*, csv:asmark-
finden/*, csv:proxy-typ/*), 4 neue de/en-Textschluessel (Paritaet
geprueft). bin\run_tests.bat --lisp: 257/257 bestanden.

Noch nicht enthalten (Stufe 2, Python): map_separator_kette_items nutzt
das neue "id"-Feld noch nicht, build_strecke_schleus_items synthetisiert
AS/ES-Zeilen weiterhin selbst statt die exportierten zu lesen.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Samer Ayadi
2026-09-22 13:09:05 +02:00
co-authored by Claude Sonnet 5
parent 9671fe4e04
commit 618ff3d86c
15 changed files with 1205 additions and 216 deletions
+103 -9
View File
@@ -12,9 +12,12 @@ BricsCAD (LISP) Python
───────────────── ──────
c:EXPORTCSV
-> csv:run-export
-> Proxies fuer verpackte
-> 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
@@ -48,15 +51,32 @@ Bounding-Boxen braucht).
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 `(proxy-ename . wrapper-ename)`.
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.
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.
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
@@ -88,7 +108,7 @@ Pro INSERT wird ein JSON-Objekt geschrieben:
| `bbox` | `csv:get-bbox` (`vla-getboundingbox`) | 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` | nur bei VF_n/GF_n, siehe eigener Abschnitt |
| `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
@@ -132,6 +152,80 @@ Separatoren als Eintraege mit Vorgaenger-/Nachfolger-Wissen. Python fuehrt
beides zusammen (siehe `map_separator_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 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_separator_kette_items`, siehe unten; solange Python das Feld noch
nicht liest, aendert sich dort nichts). Das Feld fehlt, wenn keine Kopie
erzeugt werden konnte oder deren ID-Attribut leer blieb — Altbestand und
Fehlerfaelle verhalten sich damit wie bisher.
### 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>`.
@@ -215,10 +309,10 @@ relevanten Felder):
"k2": "AAAAQwAATkAAAAAA...",
"bbox": {"min": [10800.0, 4850.0, 0.0], "max": [13000.0, 5150.0, 300.0]},
"sepliste": [
["AS", 1, 10800.0, 5000.0],
["SEP", 2, 11600.0, 5000.0],
["SEP", 3, 12300.0, 5000.0],
["ES", 4, 13000.0, 5000.0]
{"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"}
]
},
{