Sepliste-XDATA fuer Separator/AS/ES-Reihenfolge + Unit-Tests, makunbound-Fix

Sammelt beim Bau von VF_n/GF_n-Ketten die Baureihenfolge der Separator-/
AS-/ES-Sub-Bloecke vor dem Block-Sweep und schreibt sie als XDATA auf den
fertigen Wrapper (ssg_ks_insert.lsp); export.lsp liest sie fuer das neue
"sepliste"-Feld im CSV-Export, export_neighbors.py nutzt es als Fallback
zur BBox-Kollisionspruefung. Dazu Unit-Tests fuer die reinen Serialisierungs-
funktionen (Chunking, Roundtrip) in test_unit.lsp.

Ausserdem: makunbound (existiert nicht in BricsCAD-AutoLISP) aus
test_run_all.lsp entfernt - verursachte Laufzeitfehler bei
SSG_RUN_ALL_TESTS_EXPORT/_OFFEN.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-09 20:36:42 +02:00
parent f3500f7d2a
commit 3698c8982c
9 changed files with 529 additions and 22 deletions
+5 -1
View File
@@ -1164,7 +1164,7 @@
es-seite geruest-einzelmodul geruest-typ
as-winkel es-winkel /
gf-bname gf-ss ent gf-insert typ-str anzahl-gf
gf-winkel-str i gf-g10z gf-korr-dz gf-osmode-alt)
gf-winkel-str i gf-g10z gf-korr-dz gf-osmode-alt gf-sepliste)
(if (null geruest-einzelmodul) (setq geruest-einzelmodul "1"))
(if (null geruest-typ) (setq geruest-typ (ssg-geruest-idx-to-typ (ssg-geruest-typ-to-idx nil))))
;; Beschriftung: Mitte der ersten Geraden, 400mm senkrecht versetzt
@@ -1207,6 +1207,9 @@
(ssadd ent gf-ss)
(setq ent (entnext ent))
)
;; Sepliste (Separator/AS/ES-Reihenfolge, siehe ssg_ks_insert.lsp) VOR dem
;; Sweep sammeln - danach existieren die Sub-Entities nicht mehr einzeln.
(setq gf-sepliste (ssg-sepliste-sammeln last-ent))
;; Block definieren und einfuegen. ssg-block-wrap-welt schaltet dabei den
;; Objektfang (OSMODE=0), die Elevation (0) und das BKS (Welt) sauber ab -
;; sonst wird der uebergebene Einfuegepunkt umgefangen bzw. der OCS-Skew der
@@ -1218,6 +1221,7 @@
;; Ziel-Layer der Baugruppe aus cfg/layer.cfg (ohne Eintrag bleibt die
;; Referenz auf dem aktuellen Layer, wie vor der Umstellung).
(ssg-layer-setzen gf-insert "gefaellestrecke")
(ssg-sepliste-xdata-schreiben gf-insert "SSG_GF_SEP" gf-sepliste)
;; Kette wurde bei Z=0 gebaut; der OSMODE=0-Wrap loest den OCS-Skew der
;; gekippten Elemente sauber zur logischen Position (Z=0) auf. Den fertigen
;; GF_n-Block daher nur noch senkrecht auf die Zielhoehe heben (OSMODE=0,
+37 -1
View File
@@ -588,7 +588,8 @@
;; include-bbox -- Kodierung ueber csv:kos-encode (siehe dort).
(defun csv:block-to-json (ename include-bbox / ed blk-name layer pt rotation handle attribs bbox bbox-json
insert-quat insert-kos k-list warnung-json warnung-eintrag
zuordnung-fix-json zuordnung-fix-eintrag)
zuordnung-fix-json zuordnung-fix-eintrag sepliste-json sepliste-app sepliste
sepliste-teile sepliste-first)
(setq ed (entget ename))
(setq blk-name (cdr (assoc 2 ed)))
(setq layer (cdr (assoc 8 ed)))
@@ -667,6 +668,40 @@
(setq zuordnung-fix-json
(strcat ",\"zuordnung_fix\":\"" (csv:json-escape (cdr zuordnung-fix-eintrag)) "\""))
)
;; VF_n/GF_n-Wrapper: Separator-/AS-/ES-Reihenfolge aus der beim Bau
;; geschriebenen XDATA lesen (siehe ssg-sepliste-xdata-lesen,
;; ssg_ks_insert.lsp) und als "sepliste"-Array einbetten. Nur gesetzt, wenn
;; die XDATA vorhanden ist (Altbestand-Wrapper ohne die neue XDATA bleiben
;; ohne dieses Feld - export_neighbors.py faellt dann auf den bisherigen
;; BBox-Kollisionspfad zurueck).
(setq sepliste-json "")
(if (car (atoms-family 1 '("SSG-SEPLISTE-XDATA-LESEN")))
(progn
(setq sepliste-app
(cond
((wcmatch (strcase blk-name) "VF_*") "SSG_VF_SEP")
((wcmatch (strcase blk-name) "GF_*") "SSG_GF_SEP")
(t nil)))
(if sepliste-app
(setq sepliste (ssg-sepliste-xdata-lesen ename sepliste-app)))
(if sepliste
(progn
(setq sepliste-teile "" sepliste-first t)
(foreach e sepliste
(setq sepliste-teile
(strcat sepliste-teile (if sepliste-first "" ",")
"{\"typ\":\"" (nth 0 e) "\""
",\"lfdnr\":" (itoa (nth 1 e))
",\"x\":" (rtos (nth 2 e) 2 2)
",\"y\":" (rtos (nth 3 e) 2 2)
"}"))
(setq sepliste-first nil)
)
(setq sepliste-json (strcat ",\"sepliste\":[" sepliste-teile "]"))
)
)
)
)
(strcat
" {\"block_name\":\"" (csv:json-escape blk-name) "\""
",\"layer\":\"" (csv:json-escape layer) "\""
@@ -684,6 +719,7 @@
bbox-json
warnung-json
zuordnung-fix-json
sepliste-json
"}"
)
)
+144
View File
@@ -354,4 +354,148 @@
)
)
;; ------------------------------------------------------------
;; SEPLISTE: Separator-/AS-/ES-Reihenfolge als XDATA am fertigen Wrapper
;; ------------------------------------------------------------
;; Sammelt beim Bau einer VF_n/GF_n-Kette die Weltposition + Baureihenfolge
;; aller "Staustrecke_Separator_SP_300_mm"/"AS_Element_*"/"ES_Element_*"-
;; Sub-Bloecke, BEVOR der abschliessende _.-BLOCK-Sweep (ssg-block-wrap-welt)
;; sie in die Wrapper-Blockdefinition einsaugt und ihre Modellraum-Existenz
;; damit beendet. Genutzt von vf-block-erstellen (vf_core.lsp),
;; vfl-block-erstellen (vf_linienzug.lsp) und gf-block-erstellen
;; (Gefaellestrecke.lsp) - an allen drei Stellen identisch aufgerufen, daher
;; hier zentral (vgl. Projektregel "Wiederverwendung vor Neuschaffung").
;;
;; Traegt bewusst KEINE ssg-id-generate-ID (kein ID-ATTDEF an diesen
;; Bloecken, Sinn/Zweck rein interne Geometrie-Traeger) - jeder Eintrag
;; bekommt nur eine laufende Nummer innerhalb der Liste. Der Export bildet
;; daraus bei Bedarf eigene, global fortlaufende TeileIds.
;; Einfaches Chunking eines Strings in Stuecke <= n Zeichen (DXF-Limit einer
;; 1000-Gruppe ist 255 Byte) - eigenstaendige Kopie statt vfl-chunk-string
;; (vf_linienzug.lsp), da diese Datei laut Lademechanismus VOR vf_linienzug
;; geladen wird und dessen Funktionen hier noch nicht existieren.
(defun ssg-sepliste-chunk-string (s n / out len)
(setq out '())
(while (> (setq len (strlen s)) n)
(setq out (cons (substr s 1 n) out))
(setq s (substr s (1+ n))))
(if (> (strlen s) 0) (setq out (cons s out)))
(reverse out))
;; Sammelt alle seit lastEnt neu erzeugten Separator-/AS-/ES-Entities in
;; Baureihenfolge (identische entnext-Wanderung wie die Auswahlsatz-Sammlung
;; der Aufrufer, nur mit wcmatch-Filter). Rueckgabe: Liste von
;; (typ lfdnr x y), typ = "SEP"/"AS"/"ES", lfdnr 1-basiert in Baureihenfolge.
(defun ssg-sepliste-sammeln (lastEnt / ent bname typ lfdnr pt liste)
(setq liste '() lfdnr 0)
(setq ent (if lastEnt (entnext lastEnt) (entnext)))
(while ent
(if (= (cdr (assoc 0 (entget ent))) "INSERT")
(progn
(setq bname (cdr (assoc 2 (entget ent))))
(setq typ (cond
((wcmatch bname "Staustrecke_Separator_SP_300_mm*") "SEP")
((wcmatch bname "AS_Element_*") "AS")
((wcmatch bname "ES_Element_*") "ES")
(t nil)))
(if typ
(progn
(setq lfdnr (1+ lfdnr))
(setq pt (cdr (assoc 10 (entget ent))))
(setq liste (cons (list typ lfdnr (car pt) (cadr pt)) liste))
)
)
)
)
(setq ent (entnext ent))
)
(reverse liste)
)
;; Serialisiert die Sepliste zu "TYP:LFDNR:X:Y,TYP:LFDNR:X:Y,...".
(defun ssg-sepliste->string (liste / eintrag out first)
(setq out "" first t)
(foreach eintrag liste
(setq eintrag
(strcat (nth 0 eintrag) ":" (itoa (nth 1 eintrag)) ":"
(rtos (nth 2 eintrag) 2 2) ":" (rtos (nth 3 eintrag) 2 2)))
(if first (progn (setq out eintrag) (setq first nil))
(setq out (strcat out "," eintrag)))
)
out
)
;; Gegenstueck zu ssg-sepliste->string - Rueckgabe Liste von (typ lfdnr x y).
(defun ssg-sepliste-string->liste (s / out teile eintraege feld)
(setq out '())
(if (> (strlen s) 0)
(progn
(setq eintraege '() teile "")
(foreach c (ssg-sepliste-explode-chars s)
(if (= c ",")
(progn (setq eintraege (cons teile eintraege)) (setq teile ""))
(setq teile (strcat teile c))
)
)
(setq eintraege (reverse (cons teile eintraege)))
(foreach eintrag eintraege
(setq feld (ssg-sepliste-split-feld eintrag ":"))
(if (= (length feld) 4)
(setq out (cons (list (nth 0 feld) (atoi (nth 1 feld))
(atof (nth 2 feld)) (atof (nth 3 feld))) out))
)
)
)
)
(reverse out)
)
(defun ssg-sepliste-explode-chars (s / i n out)
(setq out '() i 1 n (strlen s))
(while (<= i n) (setq out (cons (substr s i 1) out)) (setq i (1+ i)))
(reverse out)
)
(defun ssg-sepliste-split-feld (s sep / pos out)
(setq out '())
(while (setq pos (vl-string-search sep s))
(setq out (cons (substr s 1 pos) out))
(setq s (substr s (+ pos 1 (strlen sep)))))
(reverse (cons s out))
)
;; Schreibt die Sepliste als XDATA (App app-name, z.B. "SSG_VF_SEP"/
;; "SSG_GF_SEP") auf den fertigen Wrapper-Block ent. Layout der 1000-Gruppen:
;; [0]="sepliste" (Marker), [1..]=serialisierte Liste in 250-Byte-Chunks
;; (DXF-Limit 255 Byte je 1000-Gruppe) - identisches Muster wie
;; vfl-journal-xdata-schreiben (vf_linienzug.lsp).
(defun ssg-sepliste-xdata-schreiben (ent app-name liste / chunks appentry)
(if (and ent liste)
(progn
(regapp app-name)
(setq chunks (ssg-sepliste-chunk-string (ssg-sepliste->string liste) 250))
(setq appentry
(cons app-name
(cons (cons 1000 "sepliste")
(mapcar (function (lambda (c) (cons 1000 c))) chunks))))
(entmod (append (entget ent) (list (list -3 appentry))))
)
)
ent
)
;; Liest die Sepliste-XDATA. Rueckgabe: Liste von (typ lfdnr x y), oder nil
;; (kein/unbekanntes Sepliste-XDATA am Entity).
(defun ssg-sepliste-xdata-lesen (ent app-name / xd app-data werte)
(setq xd (entget ent (list app-name)))
(setq app-data (cdr (assoc -3 xd)))
(if app-data
(progn
(setq werte (mapcar 'cdr (cdr (car app-data))))
(if (and werte (= (car werte) "sepliste"))
(ssg-sepliste-string->liste (apply 'strcat (cdr werte)))
nil))
nil
)
)
(princ)
+7 -1
View File
@@ -831,7 +831,8 @@
hoehe-von hoehe-bis deltaH deltaL L_VF L_GF1 L_GF2
richtung best-winkel startpunkt lastEnt hz
geruest-einzelmodul geruest-typ
/ vf-bname vf-ss vf-e vf-insert montagehoehe-m attdef-ypos L_GF_m-str)
/ vf-bname vf-ss vf-e vf-insert montagehoehe-m attdef-ypos L_GF_m-str
vf-sepliste)
(if (null hz) (setq hz 0.0))
(if (null geruest-einzelmodul) (setq geruest-einzelmodul "1"))
(if (null geruest-typ) (setq geruest-typ (ssg-geruest-idx-to-typ (ssg-geruest-typ-to-idx nil))))
@@ -868,6 +869,10 @@
(setq vf-e (entnext vf-e))
)
;; Sepliste (Separator/AS/ES-Reihenfolge, siehe ssg_ks_insert.lsp) VOR dem
;; Sweep sammeln - danach existieren die Sub-Entities nicht mehr einzeln.
(setq vf-sepliste (ssg-sepliste-sammeln lastEnt))
;; Block erstellen, einfuegen, Attribute setzen
(setq vf-bname (strcat "VF_" (itoa vf-nummer)))
(if (> (sslength vf-ss) 0)
@@ -880,6 +885,7 @@
;; Ziel-Layer der Baugruppe aus cfg/layer.cfg (ohne Eintrag bleibt die
;; Referenz auf dem aktuellen Layer, wie vor der Umstellung).
(ssg-layer-setzen vf-insert "variofoerderer")
(ssg-sepliste-xdata-schreiben vf-insert "SSG_VF_SEP" vf-sepliste)
(setq montagehoehe-m (/ (+ hoehe-von hoehe-bis) 2000.0))
;; L_GF_m: kommagetrennte Segmentlaengen in Metern (vorne,hinten),
;; analog L_GF_m in Gefaellestrecke.lsp.
+5 -1
View File
@@ -3258,7 +3258,7 @@
;; ============================================================
(defun vfl-block-erstellen (vfl-nummer anzahl-gf anzahl-vf hoehe-von hoehe-bis
delta-l-total as-seite es-seite startpunkt lastEnt /
vfl-bname vfl-ss ent vfl-insert typ-str)
vfl-bname vfl-ss ent vfl-insert typ-str vfl-sepliste)
;; TYP: einsegmentige Gefaellestrecke ohne Bogen -> "Gefaellestrecke";
;; mit VF, Bogen oder mehreren GF-Segmenten -> "Streckengruppe".
(setq typ-str
@@ -3289,6 +3289,9 @@
(ssadd ent vfl-ss)
(setq ent (entnext ent))
)
;; Sepliste (Separator/AS/ES-Reihenfolge, siehe ssg_ks_insert.lsp) VOR dem
;; Sweep sammeln - danach existieren die Sub-Entities nicht mehr einzeln.
(setq vfl-sepliste (ssg-sepliste-sammeln lastEnt))
;; Block definieren und einfuegen mit garantiert weltparallelem BKS.
;; startpunkt ist ein Welt-Punkt; ssg-block-wrap-welt setzt das BKS
;; temporaer auf Welt (verhindert den 31.95mm-Z-Versatz bei abweichendem
@@ -3297,6 +3300,7 @@
;; Ziel-Layer der Baugruppe aus cfg/layer.cfg (ohne Eintrag bleibt die
;; Referenz auf dem aktuellen Layer, wie vor der Umstellung).
(ssg-layer-setzen vfl-insert "variofoerderer")
(ssg-sepliste-xdata-schreiben vfl-insert "SSG_VF_SEP" vfl-sepliste)
;; Werte (volle Liste; nicht vorhandene Tags ignoriert ssg-attrib-set-on)
(ssg-attrib-set-on vfl-insert
(list