Sensor-Zuordnung: Separator/Scanner je Kreisel/Strecke zaehlen vor Export

count_sep_scan.lsp neu als wiederverwendbare Engine (cs-zuordnung-lauf),
die automatisch vor jedem CSV-/Sivas-Export laeuft (export.lsp,
csv:run-export). Zaehlt Separator_SP/Scanner je Carrier (Kreisel/Eckrad,
VF_*, GF_*) ueber die gemeinsamen cfg/export.cfg-Muster und schreibt
ANZAHL_SEPARATOR/ANZAHL_SCANNER, so dass die Summe zur Szene passt.

Scanner ausserhalb jeder Carrier-Boundingbox ohne ZUORDNUNG werden per
Abstand dem naechsten Kreisel/Strecke zugeschlagen (ZUORDNUNG = Carrier-ID).
Ist der Abstand zum zweitnaechsten Kandidaten fast gleich (<= strittig_diff_mm,
neu in cfg/export.cfg [Sensorzuordnung]), gilt die Zuordnung als strittig:
Konsolenausgabe der ergaenzten Scanner + JSON-"warnung"-Feld mit den IDs der
strittigen Kandidaten (csv:block-to-json), das export_csv.py in der Spalte
"Warnungen" ausgibt.

Menue: ZAEHLE_SEP_SCAN-Vorlauf vor EXPORTSIVAS entfernt (laeuft jetzt
automatisch im Export); interaktiver Befehl bleibt erhalten.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-08-21 15:37:50 +02:00
parent 966bfa866d
commit edfba75801
7 changed files with 429 additions and 190 deletions
+341 -188
View File
@@ -1,63 +1,91 @@
;;; ============================================================
;;; count_sep_scan.lsp - Eigenstaendiges Zaehl-Werkzeug
;;; count_sep_scan.lsp - Separator-/Scanner-Zuordnung
;;; ------------------------------------------------------------
;;; Zaehlt die manuell platzierten Bloecke "Scanner" und
;;; "Separator_SP" je ILS-Block (Kreisel / VarioFoerderer /
;;; Gefaellestrecke) und schreibt die Anzahlen in die Attribute
;;; des jeweiligen ILS-Blocks. Anschliessend QSAVE.
;;; "Separator_SP" (Muster siehe cfg/export.cfg [blockpattern]
;;; pattern_scanner/pattern_separator) je Carrier (Kreisel/Eckrad,
;;; VarioFoerderer-Streckengruppe VF_*, Gefaellestrecke GF_* -
;;; Muster pattern_kreisel/pattern_variofoerderer/
;;; pattern_gefaellestrecke) und schreibt die Anzahlen in die
;;; Attribute ANZAHL_SCANNER/ANZAHL_SEPARATOR des jeweiligen
;;; Carrier-Blocks.
;;;
;;; Zuordnung eines Sensors zu einem ILS-Block (Z wird ignoriert):
;;; - Scanner: der Einfuegepunkt (X/Y) muss in der Bounding-Box
;;; des ILS-Blocks liegen. Die 3D-Geometrie des Scanners kann
;;; weit vom Einfuegepunkt entfernt sein, daher zaehlt hier der
;;; Einfuegepunkt (dort klickt der Benutzer auf den Block).
;;; - Separator: der geometrische Mittelpunkt (Zentrum der eigenen
;;; Bounding-Box, unabhaengig vom $INSBASE) muss in der
;;; Bounding-Box des ILS-Blocks liegen.
;;; - Liegt der Punkt in mehreren Boxen (Ueberlappung), wird
;;; der ILS-Block mit dem naechstgelegenen Box-Zentrum gewaehlt.
;;; - Ein optionaler Toleranzrand (*senstol*) vergroessert die
;;; Box, damit Sensoren knapp am Rand noch erfasst werden.
;;; Zuordnung eines Sensors zu einem Carrier (Z wird ignoriert):
;;; 0. Traegt der Sensor bereits ein ZUORDNUNG-Attribut, wird das
;;; respektiert (nicht ueberschrieben) - er zaehlt mit, wenn die
;;; dort stehende ID zu einem bekannten Carrier passt.
;;; 1. Scanner: der Einfuegepunkt (X/Y) muss in der Bounding-Box
;;; des Carriers liegen. Die 3D-Geometrie des Scanners kann
;;; weit vom Einfuegepunkt entfernt sein, daher zaehlt hier der
;;; Einfuegepunkt (dort klickt der Benutzer auf den Block).
;;; 2. Separator: der geometrische Mittelpunkt (Zentrum der eigenen
;;; Bounding-Box, unabhaengig vom $INSBASE) muss in der
;;; Bounding-Box des Carriers liegen.
;;; 3. Liegt der Punkt in mehreren Boxen (Ueberlappung), wird der
;;; Carrier mit dem naechstgelegenen Box-Zentrum gewaehlt.
;;; 4. Liegt der Punkt in KEINER Carrier-Box (Scanner koennen
;;; durchaus ausserhalb der Kreisel-BBox montiert sein), wird der
;;; raeumlich naechstgelegene Carrier ueber alle Kreisel/Strecken
;;; hinweg gewaehlt (cs-nearest-two). Ist der Abstand zum
;;; zweitnaechsten Carrier fast gleich gross (Differenz <=
;;; cs-strittig-diff-mm, siehe cfg/export.cfg [Sensorzuordnung]
;;; strittig_diff_mm), gilt die Zuordnung als "strittig": sie
;;; wird trotzdem geschrieben, aber zusaetzlich in
;;; *cs-scanner-ergaenzt* (Report-Liste) und
;;; *cs-scanner-warnung-by-handle* (Key = Entity-Handle, Wert =
;;; Warnungstext) vermerkt. csv:block-to-json (export.lsp) liest
;;; *cs-scanner-warnung-by-handle* aus und haengt den Text als
;;; "warnung"-Feld an den JSON-Block des betroffenen Scanners an
;;; - export_csv.py gibt ihn in der CSV-Spalte "Warnungen" aus.
;;; *cs-scanner-ergaenzt* sammelt ALLE per Abstand (nicht per
;;; BBox) zugeordneten Scanner, nicht nur die strittigen -
;;; cs-zuordnung-lauf gibt diese Liste auf der Konsole aus.
;;; 5. Ein optionaler Toleranzrand (cs-senstol, siehe
;;; cfg/export.cfg [Sensorzuordnung] senstol_mm) vergroessert die
;;; Box, damit Sensoren knapp am Rand noch erfasst werden.
;;; 6. Bei zugeordneten Sensoren wird die ID des Carriers (Attribut
;;; "ID") in das Sensor-Attribut ZUORDNUNG geschrieben.
;;; 7. Sensoren, zu denen ueberhaupt kein Carrier existiert (leere
;;; Zeichnung), werden mit einem Kreis auf dem Layer *mark-layer*
;;; markiert (zentriert auf den Sensor).
;;;
;;; Zusaetzlich:
;;; - Bei zugeordneten Sensoren wird die Bezeichnung des ILS-Blocks
;;; (Attribut "Bezeichnung", Fallback Blockname) in das Sensor-
;;; Attribut *zuordnung-tag* geschrieben.
;;; - Nicht zugeordnete Sensoren werden mit einem Kreis auf dem
;;; Layer *mark-layer* markiert (zentriert auf den Sensor).
;;; - Der Befehl ist wiederholbar: alte Markierungskreise werden
;;; zu Beginn entfernt.
;;; cs-zuordnung-lauf ist die wiederverwendbare Engine (siehe unten):
;;; sie wird sowohl vom interaktiven Befehl ZAEHLE_SEP_SCAN als auch
;;; automatisch vor jedem CSV-/Sivas-Export aufgerufen (Lisp/export.lsp,
;;; csv:run-export) - dort OHNE QSAVE und ohne die ausfuehrliche
;;; Detailzeile je Carrier (verbose=nil), damit die Konsolenausgabe
;;; beim normalen Export kompakt bleibt.
;;;
;;; Aufruf: ZAEHLE_SEP_SCAN
;;; Laden: per APPLOAD oder (load "count_sep_scan")
;;; Aufruf (interaktiv): ZAEHLE_SEP_SCAN
;;; Laden: per APPLOAD oder (ssg-ensure "count_sep_scan")
;;; ============================================================
(vl-load-com)
;;; ------------------------------------------------------------
;;; KONFIGURATION (bei Bedarf anpassen)
;;; KONFIGURATION
;;; ------------------------------------------------------------
;; Toleranzrand um die ILS-Block-Box in Zeichnungseinheiten (mm).
;; 0.0 = exakte Box. Groesserer Wert = Sensoren knapp ausserhalb
;; werden noch dem ILS-Block zugerechnet.
;; Toleranzrand um die Carrier-Box in Zeichnungseinheiten (mm), aus
;; cfg/export.cfg [Sensorzuordnung] senstol_mm (Default 0.0 = exakte
;; Box). Wird von cs-zuordnung-lauf bei jedem Lauf frisch gelesen und
;; hier nur mit einem Startwert vorbelegt, damit cs-in-box/cs-grid-add
;; auch ausserhalb eines Laufs referenzierbar bleiben.
(if (null *senstol*) (setq *senstol* 0.0))
;; Blockname-Muster (wcmatch, Grossschreibung) fuer die manuell
;; platzierten Sensoren.
(setq *scanner-pattern* "SCANNER,SCANNER_*")
(setq *separator-pattern* "SEPARATOR_SP,SEPARATOR_SP_*")
;; Attribut-Tag im Scanner-/Separator-Block, in das die ID des
;; zugeordneten Carriers geschrieben wird.
(setq *zuordnung-tag* "ZUORDNUNG")
;; Blockname-Muster fuer die INTERN im ILS-Block (in der Kette)
;; gezeichneten Separatoren. Diese bilden die Basis fuer
;; ANZAHL_SEPARATOR und werden bei jedem Lauf frisch gezaehlt.
;; Blockname-Muster fuer die INTERN im Carrier-Block (in der Kette)
;; gezeichneten Separatoren. Diese bilden einen Teil von
;; ANZAHL_SEPARATOR und werden bei jedem Lauf frisch aus der
;; Blockdefinition gezaehlt (siehe cs-count-nested) - rein LISP-
;; intern, daher (anders als pattern_separator/pattern_scanner) nicht
;; in cfg/export.cfg.
(setq *intern-separator-pattern* "STAUSTRECKE_SEPARATOR_SP*")
;; Attribut-Tag im Scanner-/Separator-Block, in das die Bezeichnung
;; des zugeordneten ILS-Blocks geschrieben wird (Gross/Klein egal).
(setq *zuordnung-tag* "zuordnung")
;; Markierung nicht zugeordneter Sensoren: Kreis auf eigenem Layer.
;; Markierung von Sensoren ohne jeglichen Carrier: Kreis auf eigenem Layer.
(setq *mark-layer* "S_SENSOR_UNZUGEORDNET")
(setq *mark-color* 2) ;; 2 = Gelb
;; Kreisradius = Faktor * halbe XY-Diagonale der Sensor-Box,
@@ -65,19 +93,32 @@
(if (null *mark-radius-factor*) (setq *mark-radius-factor* 1.5))
(if (null *mark-radius-min*) (setq *mark-radius-min* 500.0))
;; --- Toleranzrand (mm) aus cfg/export.cfg [Sensorzuordnung] senstol_mm ---
(defun cs-senstol ()
(atof (export:cfg "Sensorzuordnung" "senstol_mm" "0"))
)
;; --- "Strittig"-Schwelle (mm) aus cfg/export.cfg [Sensorzuordnung] ---
;; Differenz zwischen dem Abstand zum naechsten und zweitnaechsten
;; Carrier, unterhalb derer eine Distanz-Zuordnung als unsicher gilt.
(defun cs-strittig-diff-mm ()
(atof (export:cfg "Sensorzuordnung" "strittig_diff_mm" "500"))
)
;;; ------------------------------------------------------------
;;; ILS-Block-Erkennung und zugehoerige Attribut-Tags
;;; Alle ILS-Bloecke (Kreisel / VF / GF) verwenden einheitlich
;;; ANZAHL_SCANNER und ANZAHL_SEPARATOR.
;;; Rueckgabe: (scanner-tag separator-tag) oder nil
;;; Carrier-/Sensor-Erkennung ueber die gemeinsamen Muster aus
;;; cfg/export.cfg [blockpattern] (export:pattern, siehe export.lsp) -
;;; dieselben Muster wie csv:collect-export-blocks/export_csv.py.
;;; ------------------------------------------------------------
(defun cs-carrier-tags (nm / up)
;; --- Ist nm ein Kreisel/Eckrad-, VarioFoerderer- oder Gefaellestrecke-
;; Compound-Block? Alle drei fuehren einheitlich die Attribute
;; ANZAHL_SCANNER/ANZAHL_SEPARATOR. ---
(defun cs-carrier-p (nm / up)
(setq up (strcase nm))
(if (or (wcmatch up "KREISEL_*")
(wcmatch up "VF_*")
(wcmatch up "GF_*"))
(list "ANZAHL_SCANNER" "ANZAHL_SEPARATOR")
nil
(or (wcmatch up (export:pattern "pattern_kreisel" "KR_*,KREISEL_*,ECKRAD_*"))
(wcmatch up (export:pattern "pattern_variofoerderer" "VF_*"))
(wcmatch up (export:pattern "pattern_gefaellestrecke" "GF_*"))
)
)
@@ -88,26 +129,12 @@
(defun cs-sensor-kind (nm / up)
(setq up (strcase nm))
(cond
((wcmatch up *scanner-pattern*) 'scanner)
((wcmatch up *separator-pattern*) 'separator)
((wcmatch up (strcase (export:pattern "pattern_scanner" "Scanner*"))) 'scanner)
((wcmatch up (strcase (export:pattern "pattern_separator" "Separator_SP*,S-LP*"))) 'separator)
(t nil)
)
)
;;; ------------------------------------------------------------
;;; Punkt-Rueckgabe von vla-getboundingbox in eine Liste wandeln.
;;; BricsCAD liefert ein Safearray, AutoCAD ein Variant mit
;;; Safearray - beide Faelle werden hier abgedeckt.
;;; ------------------------------------------------------------
(defun cs-pt->list (v)
(cond
((= (type v) 'variant) (vlax-safearray->list (vlax-variant-value v)))
((= (type v) 'safearray) (vlax-safearray->list v))
((listp v) v)
(t nil)
)
)
;;; ------------------------------------------------------------
;;; Bounding-Box eines INSERT holen (WCS, achsparallel).
;;; Erzwingt vorher ein vla-update (Regen des Objekts), da
@@ -116,6 +143,15 @@
;;; Rueckgabe: (minx miny maxx maxy) oder nil bei Fehler.
;;; *cs-last-error* enthaelt bei Fehler den echten COM-Fehlertext.
;;; ------------------------------------------------------------
(defun cs-pt->list (v)
(cond
((= (type v) 'variant) (vlax-safearray->list (vlax-variant-value v)))
((= (type v) 'safearray) (vlax-safearray->list v))
((listp v) v)
(t nil)
)
)
(defun cs-bbox (ent / obj res)
(setq *cs-last-error* nil)
(setq obj (vlax-ename->vla-object ent))
@@ -157,8 +193,8 @@
)
;;; ------------------------------------------------------------
;;; Liegt Punkt pt (X/Y) in ILS-Block-Record rec (mit Toleranz)?
;;; rec = (ename tagScan tagSep minx miny maxx maxy cx cy)
;;; Liegt Punkt pt (X/Y) in Carrier-Record rec (mit Toleranz)?
;;; rec = (ename tagScan tagSep minx miny maxx maxy cx cy id)
;;; ------------------------------------------------------------
(defun cs-in-box (pt rec / x y)
(setq x (car pt) y (cadr pt))
@@ -246,13 +282,13 @@
)
;;; ------------------------------------------------------------
;;; Sensor-Punkt einem ILS-Block zuordnen.
;;; Rueckgabe: ILS-Block-Record oder nil (keine Zuordnung).
;;; Sensor-Punkt einem Carrier per BBox zuordnen (Schritt 1-3 oben).
;;; Rueckgabe: Carrier-Record oder nil (kein BBox-Treffer).
;;; Bei mehreren Treffern: naechstes Box-Zentrum (X/Y).
;;; grid = Vorsortierung aus cs-grid-build (statt aller Carrier
;;; wird nur die Kandidatenliste der eigenen Zelle geprueft).
;;; ------------------------------------------------------------
(defun cs-assign (pt grid / matches best bestd d)
(defun cs-assign (pt grid / matches best bestd d rec)
(setq matches nil)
(foreach rec (cs-grid-candidates grid pt)
(if (cs-in-box pt rec) (setq matches (cons rec matches))))
@@ -270,6 +306,61 @@
)
)
;;; ------------------------------------------------------------
;;; Die beiden dem Punkt pt (X/Y) naechstgelegenen Carrier, sortiert
;;; nach Abstand zum Box-Mittelpunkt (gleicher Massstab wie der
;;; Ueberlappungs-Tiebreak in cs-assign). carriers darf nicht leer
;;; sein - siehe cs-resolve-sensor.
;;; Rueckgabe: (best-rec best-dist second-rec-oder-nil second-dist-oder-nil)
;;; ------------------------------------------------------------
(defun cs-nearest-two (pt carriers / paare sortiert rec)
(setq paare nil)
(foreach rec carriers
(setq paare (cons (cons (distance (list (car pt) (cadr pt))
(list (nth 7 rec) (nth 8 rec)))
rec)
paare))
)
(setq sortiert (vl-sort paare (function (lambda (a b) (< (car a) (car b))))))
(list
(cdr (nth 0 sortiert))
(car (nth 0 sortiert))
(if (cdr sortiert) (cdr (nth 1 sortiert)) nil)
(if (cdr sortiert) (car (nth 1 sortiert)) nil)
)
)
;;; --- Carrier-Record mit gegebener ID aus carriers heraussuchen ---
(defun cs-find-carrier-by-id (carriers id / found rec)
(setq found nil)
(foreach rec carriers
(if (and (not found) (= (nth 9 rec) id)) (setq found rec))
)
found
)
;;; ------------------------------------------------------------
;;; Ordnet einen Sensor-Punkt (Scanner oder Separator) einem Carrier
;;; zu (Reihenfolge siehe Datei-Kopfkommentar Schritte 0/1-3/4).
;;; Rueckgabe: (modus rec best-d second-rec second-d)
;;; modus = 'manual | 'bbox | 'distance | 'none (keine Carrier vorhanden)
;;; best-d/second-rec/second-d sind nur bei modus='distance gesetzt.
;;; ------------------------------------------------------------
(defun cs-resolve-sensor (sent spt carriers grid / existing rec nearest)
(setq existing (cs-get-att-str sent *zuordnung-tag*))
(cond
((and existing (/= existing ""))
(list 'manual (cs-find-carrier-by-id carriers existing) nil nil nil))
((setq rec (cs-assign spt grid))
(list 'bbox rec nil nil nil))
((null carriers)
(list 'none nil nil nil nil))
(t
(setq nearest (cs-nearest-two spt carriers))
(list 'distance (nth 0 nearest) (nth 1 nearest) (nth 2 nearest) (nth 3 nearest)))
)
)
;;; ------------------------------------------------------------
;;; Zaehler in einer Assoc-Liste (Schluessel = ename) erhoehen.
;;; ------------------------------------------------------------
@@ -363,8 +454,9 @@
)
;;; ------------------------------------------------------------
;;; Bezeichnung eines ILS-Blocks ermitteln: Attribut "Bezeichnung",
;;; Fallback auf den Blocknamen.
;;; Bezeichnung eines Carriers ermitteln: Attribut "Bezeichnung",
;;; Fallback auf den Blocknamen. Nur fuer Konsolen-/Fehlermeldungen -
;;; die eigentliche Verknuepfung (ZUORDNUNG) laeuft ueber die ID.
;;; ------------------------------------------------------------
(defun cs-carrier-label (ent nm / bez)
(setq bez (cs-get-att-str ent "Bezeichnung"))
@@ -395,7 +487,7 @@
)
;;; ------------------------------------------------------------
;;; Nicht zugeordneten Sensor mit einem Kreis markieren, zentriert
;;; Sensor ohne jeglichen Carrier mit einem Kreis markieren, zentriert
;;; auf die Sensor-Geometrie. Radius aus der XY-Box des Sensors
;;; (Faktor), mindestens *mark-radius-min*.
;;; ------------------------------------------------------------
@@ -422,63 +514,81 @@
)
;;; ============================================================
;;; HAUPTBEFEHL
;;; ENGINE - wird sowohl von ZAEHLE_SEP_SCAN als auch automatisch
;;; vor jedem CSV-/Sivas-Export aufgerufen (siehe export.lsp,
;;; csv:run-export).
;;;
;;; label = Anzeigepraefix fuer die Konsolenausgabe (z.B. "EXPORTCSV").
;;; verbose = T -> zusaetzlich je Carrier eine Detailzeile ausgeben
;;; (nur fuer den interaktiven Befehl sinnvoll - der
;;; automatische Aufruf aus csv:run-export laesst dies aus,
;;; damit die Export-Konsole kompakt bleibt).
;;;
;;; Setzt als Nebenwirkung:
;;; *cs-scanner-ergaenzt* - Liste (scanner-id carrier-id
;;; distanz-mm strittig-p
;;; zweiter-carrier-id zweite-distanz-mm)
;;; je per Abstand (nicht per BBox)
;;; zugeordnetem Scanner.
;;; *cs-scanner-warnung-by-handle* - Assoc-Liste (handle . warnungstext)
;;; fuer strittige Scanner-Zuordnungen,
;;; von csv:block-to-json ausgelesen.
;;; ============================================================
(defun c:ZAEHLE_SEP_SCAN ( / ss i ent ed nm tags bb cx cy label
carriers grid scanlist seplist kind pt
scanCounts sepCounts
rec en nm2 ns nsep internSep newS newP
s sent spt
n-scan-unassigned n-sep-unassigned
n-zuordnung-fehlt mss
okS okP)
(setq carriers nil scanlist nil seplist nil n-zuordnung-fehlt 0)
(defun cs-zuordnung-lauf (label verbose /
mss ss i ent ed nm bb cx cy lbl carrier-id
carriers grid scanlist seplist sk pt
scanCounts sepCounts n-scan-unassigned n-sep-unassigned
n-strittig n-zuordnung-fehlt
s sent spt res mode rec best-d second-rec second-d
scanner-id contested handle
en nm2 ns nsep internSep newS newP okS okP eintrag)
(setq carriers nil scanlist nil seplist nil
n-zuordnung-fehlt 0 n-scan-unassigned 0 n-sep-unassigned 0 n-strittig 0)
(setq *cs-scanner-ergaenzt* nil)
(setq *cs-scanner-warnung-by-handle* nil)
(setq *senstol* (cs-senstol))
;; Alte Markierungskreise entfernen (Idempotenz)
;; Alte Markierungskreise entfernen (Idempotenz - Sensoren, die in
;; einem frueheren Lauf unzuordenbar waren, koennen inzwischen einen
;; Carrier bekommen haben oder umgekehrt).
(setq mss (ssget "_X" (list (cons 0 "CIRCLE") (cons 8 *mark-layer*))))
(if mss (command "_.ERASE" mss ""))
;; Vor der Extents-Berechnung einmal regenerieren, damit
;; vla-getboundingbox verlaessliche Werte liefert.
(vl-catch-all-apply '(lambda () (command "_.REGEN")))
;; --- 1. Alle INSERTs sammeln und klassifizieren ---
(princ (ssg-textf "sens-start" (list label)))
(setq ss (ssget "_X" '((0 . "INSERT"))))
(if (null ss)
(progn (princ "\n>>> Keine Bloecke in der Zeichnung gefunden.") (princ))
(princ (ssg-textf "sens-no-inserts" (list label)))
(progn
(setq i 0)
(while (< i (sslength ss))
(setq ent (ssname ss i))
(setq ed (entget ent))
(setq nm (cdr (assoc 2 ed)))
(setq tags (cs-carrier-tags nm))
(cond
;; --- ILS-Block ---
(tags
((cs-carrier-p nm)
(setq bb (cs-bbox ent))
(if bb
(progn
(setq cx (/ (+ (nth 0 bb) (nth 2 bb)) 2.0))
(setq cy (/ (+ (nth 1 bb) (nth 3 bb)) 2.0))
(setq label (cs-carrier-label ent nm))
(setq lbl (cs-carrier-label ent nm))
(setq carrier-id (cs-get-att-str ent "ID"))
(setq carriers
(cons (list ent (car tags) (cadr tags)
(cons (list ent "ANZAHL_SCANNER" "ANZAHL_SEPARATOR"
(nth 0 bb) (nth 1 bb) (nth 2 bb) (nth 3 bb)
cx cy label)
cx cy carrier-id lbl)
carriers)))
(princ (strcat "\n! Bounding-Box fehlgeschlagen fuer ILS-Block '" nm "'"
(if *cs-last-error*
(strcat " (" *cs-last-error* ")") "")))
(princ (ssg-textf "sens-bbox-fehler" (list nm)))
)
)
;; --- Sensor (Entity + Referenzpunkt merken) ---
(t
(setq kind (cs-sensor-kind nm))
(if kind
(setq sk (cs-sensor-kind nm))
(if sk
(progn
(setq pt (cs-sensor-point ent ed kind))
(if (eq kind 'scanner)
(setq pt (cs-sensor-point ent ed sk))
(if (eq sk 'scanner)
(setq scanlist (cons (list ent pt) scanlist))
(setq seplist (cons (list ent pt) seplist)))))
)
@@ -486,99 +596,142 @@
(setq i (1+ i))
)
;; --- 2. Sensoren zuordnen: zuordnung schreiben bzw. markieren ---
;; Vorsortierung: Carrier einmalig in ein raeumliches Grid einsortieren,
;; damit cs-assign je Sensor nur die Kandidaten der eigenen Zelle statt
;; aller Carrier prueft (siehe cs-grid-build).
(setq grid (cs-grid-build carriers))
(setq scanCounts nil sepCounts nil
n-scan-unassigned 0 n-sep-unassigned 0)
(setq scanCounts nil sepCounts nil)
;; --- Scanner zuordnen (inkl. Distanz-Fallback + Strittig-Erkennung) ---
(foreach s scanlist
(setq sent (car s) spt (cadr s))
(setq rec (cs-assign spt grid))
(if rec
(progn
(setq scanCounts (cs-bump scanCounts (car rec)))
(if (not (cs-set-att sent *zuordnung-tag* (nth 9 rec)))
(setq n-zuordnung-fehlt (1+ n-zuordnung-fehlt))))
(progn
(setq n-scan-unassigned (1+ n-scan-unassigned))
(cs-set-att sent *zuordnung-tag* "")
(cs-mark-unassigned sent))))
(foreach s seplist
(setq sent (car s) spt (cadr s))
(setq rec (cs-assign spt grid))
(if rec
(progn
(setq sepCounts (cs-bump sepCounts (car rec)))
(if (not (cs-set-att sent *zuordnung-tag* (nth 9 rec)))
(setq n-zuordnung-fehlt (1+ n-zuordnung-fehlt))))
(progn
(setq n-sep-unassigned (1+ n-sep-unassigned))
(cs-set-att sent *zuordnung-tag* "")
(cs-mark-unassigned sent))))
;; --- 3. ANZAHL-Attribute setzen und Bericht ausgeben ---
(princ "\n============================================")
(princ "\n Sensor-Zaehlung je ILS-Block")
(princ "\n============================================")
(if (null carriers)
(princ "\n (Keine ILS-Bloecke KREISEL_/VF_/GF_ gefunden)")
(foreach rec carriers
(setq en (car rec))
(setq nm2 (cdr (assoc 2 (entget en))))
(setq ns (cond ((assoc en scanCounts) (cdr (assoc en scanCounts))) (t 0)))
(setq nsep (cond ((assoc en sepCounts) (cdr (assoc en sepCounts))) (t 0)))
;; interne Kette-Separatoren frisch aus der Blockdefinition zaehlen
(setq internSep (cs-count-nested nm2 *intern-separator-pattern*))
;; Scanner = nur neu zugeordnete
;; Separator = interne Kette-Separatoren + neu zugeordnete
(setq newS ns)
(setq newP (+ internSep nsep))
;; gefundene Anzahlen melden (mit Bezeichnung/Label)
(princ (strcat "\n " nm2 " [" (nth 9 rec) "]"
": Scanner(neu)=" (itoa ns)
", Separator intern=" (itoa internSep)
" + manuell=" (itoa nsep)))
;; Werte schreiben und Ergebnis bestaetigen
(setq okS (cs-set-att en (nth 1 rec) (itoa newS)))
(setq okP (cs-set-att en (nth 2 rec) (itoa newP)))
(if (and okS okP)
(princ (strcat "\n -> geschrieben: "
"ANZAHL_SCANNER=" (itoa newS)
", ANZAHL_SEPARATOR=" (itoa newP)))
(princ (strcat "\n -> FEHLER beim Schreiben: "
(if okS "" "ANZAHL_SCANNER fehlt ")
(if okP "" "ANZAHL_SEPARATOR fehlt"))))
(setq res (cs-resolve-sensor sent spt carriers grid))
(setq mode (nth 0 res) rec (nth 1 res)
best-d (nth 2 res) second-rec (nth 3 res) second-d (nth 4 res))
(cond
((eq mode 'none)
(setq n-scan-unassigned (1+ n-scan-unassigned))
(cs-mark-unassigned sent))
((null rec) nil) ;; manuell zugeordnet, ID unbekannt -> unangetastet
(t
(setq scanCounts (cs-bump scanCounts (car rec)))
(if (member mode '(bbox distance))
(if (not (cs-set-att sent *zuordnung-tag* (nth 9 rec)))
(setq n-zuordnung-fehlt (1+ n-zuordnung-fehlt))))
(if (eq mode 'distance)
(progn
(setq scanner-id (cs-get-att-str sent "ID"))
(setq contested (and second-rec
(<= (- second-d best-d) (cs-strittig-diff-mm))))
(setq *cs-scanner-ergaenzt*
(cons (list scanner-id (nth 9 rec) (fix (+ best-d 0.5))
contested
(if contested (nth 9 second-rec) nil)
(if contested (fix (+ second-d 0.5)) nil))
*cs-scanner-ergaenzt*))
(if contested
(progn
(setq n-strittig (1+ n-strittig))
(setq handle (cdr (assoc 5 (entget sent))))
(setq *cs-scanner-warnung-by-handle*
(cons (cons handle
(strcat "Distanz-Zuordnung unsicher: "
(nth 9 rec) " (" (itoa (fix (+ best-d 0.5))) "mm) vs. "
(nth 9 second-rec) " (" (itoa (fix (+ second-d 0.5))) "mm)"))
*cs-scanner-warnung-by-handle*))
)
)
)
)
)
)
)
(princ (strcat "\n--------------------------------------------"
"\n ILS-Bloecke gesamt: " (itoa (length carriers))
"\n Scanner gesamt: " (itoa (length scanlist))
" (nicht zugeordnet: " (itoa n-scan-unassigned) ")"
"\n Separator gesamt: " (itoa (length seplist))
" (nicht zugeordnet: " (itoa n-sep-unassigned) ")"))
(if (> (+ n-scan-unassigned n-sep-unassigned) 0)
(princ (strcat "\n " (itoa (+ n-scan-unassigned n-sep-unassigned))
" nicht zugeordnete(r) Sensor(en) mit Kreis markiert"
" (Layer '" *mark-layer* "').")))
(if (> n-zuordnung-fehlt 0)
(princ (strcat "\n WARNUNG: " (itoa n-zuordnung-fehlt)
" zugeordnete(r) Sensor(en) ohne Attribut '"
*zuordnung-tag* "' - Bezeichnung nicht schreibbar.")))
(setq *cs-scanner-ergaenzt* (reverse *cs-scanner-ergaenzt*))
;; --- 4. Zeichnung speichern ---
(princ "\n--------------------------------------------")
(princ "\n Speichere Zeichnung (QSAVE) ...")
(command "_.QSAVE")
(princ "\n>>> Fertig.")
(princ)
;; --- Separatoren zuordnen (gleiche Logik, ohne Strittig-Report -
;; der Distanz-Fallback stellt hier nur sicher, dass die
;; ANZAHL_SEPARATOR-Summe auch bei Sonderfaellen stimmt). ---
(foreach s seplist
(setq sent (car s) spt (cadr s))
(setq res (cs-resolve-sensor sent spt carriers grid))
(setq mode (nth 0 res) rec (nth 1 res))
(cond
((eq mode 'none)
(setq n-sep-unassigned (1+ n-sep-unassigned))
(cs-mark-unassigned sent))
((null rec) nil)
(t
(setq sepCounts (cs-bump sepCounts (car rec)))
(if (member mode '(bbox distance))
(if (not (cs-set-att sent *zuordnung-tag* (nth 9 rec)))
(setq n-zuordnung-fehlt (1+ n-zuordnung-fehlt))))
)
)
)
;; --- ANZAHL_SCANNER/ANZAHL_SEPARATOR je Carrier schreiben ---
(foreach rec carriers
(setq en (car rec))
(setq nm2 (cdr (assoc 2 (entget en))))
(setq ns (cond ((assoc en scanCounts) (cdr (assoc en scanCounts))) (t 0)))
(setq nsep (cond ((assoc en sepCounts) (cdr (assoc en sepCounts))) (t 0)))
;; interne Kette-Separatoren frisch aus der Blockdefinition zaehlen
(setq internSep (cs-count-nested nm2 *intern-separator-pattern*))
(setq newS ns)
(setq newP (+ internSep nsep))
(setq okS (cs-set-att en (nth 1 rec) (itoa newS)))
(setq okP (cs-set-att en (nth 2 rec) (itoa newP)))
(if verbose
(princ (ssg-textf "sens-carrier-zeile"
(list nm2 (nth 9 rec) (itoa ns) (itoa internSep) (itoa nsep)
(itoa newS) (itoa newP)))))
(if (not (and okS okP))
(princ (ssg-textf "sens-carrier-fehler" (list nm2))))
)
;; --- Zusammenfassung ---
(princ (ssg-textf "sens-summary"
(list label (itoa (length carriers))
(itoa (length scanlist)) (itoa n-scan-unassigned)
(itoa (length seplist)) (itoa n-sep-unassigned))))
(if *cs-scanner-ergaenzt*
(progn
(princ (ssg-textf "sens-ergaenzt-header"
(list label (itoa (length *cs-scanner-ergaenzt*)))))
(foreach eintrag *cs-scanner-ergaenzt*
(princ (ssg-textf "sens-ergaenzt-zeile"
(list (nth 0 eintrag) (nth 1 eintrag) (itoa (nth 2 eintrag)))))
(if (nth 3 eintrag)
(princ (ssg-textf "sens-strittig-suffix"
(list (nth 4 eintrag) (itoa (nth 5 eintrag))))))
)
)
)
(if (> n-strittig 0)
(princ (ssg-textf "sens-strittig-summary" (list label (itoa n-strittig)))))
(if (> (+ n-scan-unassigned n-sep-unassigned) 0)
(princ (ssg-textf "sens-unassigned-marked"
(list label (itoa (+ n-scan-unassigned n-sep-unassigned)) *mark-layer*))))
(if (> n-zuordnung-fehlt 0)
(princ (ssg-textf "sens-zuordnung-fehlt" (list label (itoa n-zuordnung-fehlt)))))
)
)
(princ)
)
;;; ============================================================
;;; BEFEHL: ZAEHLE_SEP_SCAN - interaktiver Aufruf mit vollem Bericht
;;; (verbose) und abschliessendem QSAVE. Der automatische Aufruf vor
;;; jedem CSV-/Sivas-Export (csv:run-export in export.lsp) nutzt
;;; dieselbe Engine (cs-zuordnung-lauf) direkt, ohne QSAVE.
;;; ============================================================
(defun c:ZAEHLE_SEP_SCAN ()
(cs-zuordnung-lauf "ZAEHLE_SEP_SCAN" T)
(princ "\n--------------------------------------------")
(princ "\n Speichere Zeichnung (QSAVE) ...")
(command "_.QSAVE")
(princ "\n>>> Fertig.")
(princ)
)
(princ "\n>>> count_sep_scan.lsp geladen. Befehl: ZAEHLE_SEP_SCAN")
(princ)
+28 -1
View File
@@ -463,7 +463,7 @@
;; csv:get-k-kos-strings) werden immer geschrieben, unabhaengig von
;; 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)
insert-quat insert-kos k-list warnung-json warnung-eintrag)
(setq ed (entget ename))
(setq blk-name (cdr (assoc 2 ed)))
(setq layer (cdr (assoc 8 ed)))
@@ -506,6 +506,17 @@
)
)
)
;; Warnung fuer strittige Scanner-Zuordnungen anhaengen (siehe
;; cs-zuordnung-lauf in count_sep_scan.lsp, das *cs-scanner-warnung-
;; by-handle* vor csv:collect-export-blocks befuellt). Referenziert
;; die Variable direkt statt ueber atoms-family: ein in AutoLISP noch
;; nie gesetztes globales Symbol wertet beim Lesen zu nil aus (kein
;; Fehler), genau wie *export-test-override* in diesem Modul.
(setq warnung-json "")
(setq warnung-eintrag (assoc handle *cs-scanner-warnung-by-handle*))
(if warnung-eintrag
(setq warnung-json (strcat ",\"warnung\":\"" (csv:json-escape (cdr warnung-eintrag)) "\""))
)
(strcat
" {\"block_name\":\"" (csv:json-escape blk-name) "\""
",\"layer\":\"" (csv:json-escape layer) "\""
@@ -521,6 +532,7 @@
",\"k3\":\"" (csv:k-kos k-list "K3") "\""
",\"k4\":\"" (csv:k-kos k-list "K4") "\""
bbox-json
warnung-json
"}"
)
)
@@ -615,6 +627,21 @@
;; Vor dem Export HOEHE/DREHUNG aller Omniflo-Elemente aktualisieren
(princ (ssg-textf "exp-update-hoehe-drehung" (list label)))
(omni:update-all-attribs)
;; Vor dem Export: Separator-/Scanner-Zuordnung pruefen und
;; ANZAHL_SEPARATOR/ANZAHL_SCANNER an Kreisel/Streckengruppe/
;; Gefaellestrecke aktualisieren (siehe count_sep_scan.lsp,
;; cs-zuordnung-lauf). Scanner ausserhalb jeder Carrier-Boundingbox
;; werden dabei per Abstand zugeordnet (ZUORDNUNG-Attribut = Carrier-
;; ID) und - bei mehrdeutigem Abstand - als "strittig" markiert;
;; *cs-scanner-warnung-by-handle* wird von csv:block-to-json unten
;; ausgelesen und haengt den Warnungstext an den betroffenen Scanner-
;; Block im JSON an. verbose=nil haelt die Export-Konsole kompakt
;; (die ausfuehrliche Detailzeile je Carrier gibt es nur beim
;; interaktiven Befehl ZAEHLE_SEP_SCAN).
(ssg-ensure "count_sep_scan")
(if (atoms-family 1 '("cs-zuordnung-lauf"))
(cs-zuordnung-lauf label nil)
)
(princ (ssg-textf "exp-collect-blocks" (list label)))
(setq ss (csv:collect-export-blocks))
+21
View File
@@ -157,3 +157,24 @@ bb_minimum_height_mm=1
# Linie durch beide Stationsmitten) - genau diese Linie trennt links/rechts.
durchmesser_mm=800
[Sensorzuordnung]
# Zuordnung von manuell platzierten Separator_SP/Scanner-Bloecken zu einem
# Kreisel/Eckrad, einer VarioFoerderer-Streckengruppe (VF_*) oder einer
# Gefaellestrecke (GF_*) - siehe Lisp/count_sep_scan.lsp (cs-zuordnung-lauf).
# Laeuft automatisch vor jedem CSV-/Sivas-Export (Lisp/export.lsp,
# csv:run-export) und schreibt ANZAHL_SEPARATOR/ANZAHL_SCANNER am
# Kreisel/VF/GF sowie ZUORDNUNG (= ID des zugeordneten Elements) am Sensor.
# Toleranzrand (mm) um die Carrier-Boundingbox: Sensoren knapp ausserhalb
# gelten noch als "innerhalb". 0 = exakte Box.
senstol_mm=0
# Scanner (und Separatoren) ausserhalb JEDER Carrier-Boundingbox ohne
# vorhandenes ZUORDNUNG-Attribut werden per Abstand dem naechstgelegenen
# Kreisel/VF/GF zugeschlagen. Liegt der Abstand zum zweitnaechsten
# Kandidaten hoechstens strittig_diff_mm ueber dem des naechsten, gilt die
# Zuordnung als unsicher ("strittig"): sie wird trotzdem geschrieben, aber
# als Warnung im CSV-Export (Spalte "Warnungen") und in der BricsCAD-Konsole
# ausgegeben. Wert in mm.
strittig_diff_mm=500
+12
View File
@@ -512,6 +512,18 @@
"exp-csv-missing": "\n[%1] FEHLER: Python meldete Erfolg, aber die CSV fehlt: %2. Log: %3",
"exp-omni-no-inserts": "\n[OMNI_UPDATE] Keine INSERT-Bloecke in der Zeichnung.",
"exp-omni-updated": "\n[OMNI_UPDATE] %1 Elemente aktualisiert (HOEHE, DREHUNG).",
"sens-start": "\n[%1] Pruefe Separator-/Scanner-Zuordnung (ANZAHL_SEPARATOR/ANZAHL_SCANNER)...",
"sens-no-inserts": "\n[%1] Keine Bloecke in der Zeichnung gefunden - Sensor-Zuordnung uebersprungen.",
"sens-bbox-fehler": "\n ! Bounding-Box fehlgeschlagen fuer ILS-Block '%1'",
"sens-carrier-zeile": "\n %1 [%2]: Scanner(neu)=%3, Separator intern=%4 + manuell=%5 -> ANZAHL_SCANNER=%6, ANZAHL_SEPARATOR=%7",
"sens-carrier-fehler": "\n FEHLER beim Schreiben der Anzahl-Attribute an %1",
"sens-summary": "\n[%1] %2 Kreisel/Streckengruppen/Gefaellestrecken aktualisiert, %3 Scanner gesamt (%4 nicht zuordenbar), %5 Separator gesamt (%6 nicht zuordenbar).",
"sens-ergaenzt-header": "\n[%1] %2 Scanner ausserhalb jeder Kreisel-/Strecken-Boundingbox per Abstand automatisch zugeordnet:",
"sens-ergaenzt-zeile": "\n Scanner %1 -> %2 (%3mm)",
"sens-strittig-suffix": " STRITTIG: auch nah an %1 (%2mm)",
"sens-strittig-summary": "\n[%1] WARNUNG: %2 der obigen Zuordnungen sind strittig (Abstand zu zwei Kandidaten fast gleich) - siehe Spalte 'Warnungen' im CSV-Export.",
"sens-unassigned-marked": "\n[%1] %2 Sensor(en) ohne jeglichen Kreisel/Strecke markiert (Layer '%3').",
"sens-zuordnung-fehlt": "\n[%1] WARNUNG: %2 zugeordnete(r) Sensor(en) ohne Attribut 'ZUORDNUNG' - Wert nicht schreibbar.",
"id-new-assigned": "\n[ID] Neue ID zugewiesen: %1",
"id-invalid-block": "\n[ID] FEHLER: Kein gueltiger INSERT-Block!",
"id-check-no-blocks": "\n[IDSCHECK] Keine relevanten Bloecke gefunden.",
+12
View File
@@ -512,6 +512,18 @@
"exp-csv-missing": "\n[%1] ERROR: Python reported success but the CSV is missing: %2. Log: %3",
"exp-omni-no-inserts": "\n[OMNI_UPDATE] No INSERT blocks in the drawing.",
"exp-omni-updated": "\n[OMNI_UPDATE] %1 elements updated (HEIGHT, ROTATION).",
"sens-start": "\n[%1] Checking separator/scanner assignment (ANZAHL_SEPARATOR/ANZAHL_SCANNER)...",
"sens-no-inserts": "\n[%1] No blocks found in the drawing - sensor assignment skipped.",
"sens-bbox-fehler": "\n ! Bounding box failed for ILS block '%1'",
"sens-carrier-zeile": "\n %1 [%2]: Scanner(new)=%3, Separator internal=%4 + manual=%5 -> ANZAHL_SCANNER=%6, ANZAHL_SEPARATOR=%7",
"sens-carrier-fehler": "\n ERROR writing the count attributes on %1",
"sens-summary": "\n[%1] %2 Kreisel/Streckengruppen/Gefaellestrecken updated, %3 scanners total (%4 unassignable), %5 separators total (%6 unassignable).",
"sens-ergaenzt-header": "\n[%1] %2 scanners outside every Kreisel/track bounding box auto-assigned by distance:",
"sens-ergaenzt-zeile": "\n Scanner %1 -> %2 (%3mm)",
"sens-strittig-suffix": " CONTESTED: also close to %1 (%2mm)",
"sens-strittig-summary": "\n[%1] WARNING: %2 of the above assignments are contested (distance to two candidates nearly equal) - see column 'Warnungen' in the CSV export.",
"sens-unassigned-marked": "\n[%1] %2 sensor(s) without any Kreisel/track marked (layer '%3').",
"sens-zuordnung-fehlt": "\n[%1] WARNING: %2 assigned sensor(s) without a 'ZUORDNUNG' attribute - value not writable.",
"id-new-assigned": "\n[ID] New ID assigned: %1",
"id-invalid-block": "\n[ID] ERROR: Not a valid INSERT block!",
"id-check-no-blocks": "\n[IDSCHECK] No relevant blocks found.",
+14
View File
@@ -27,6 +27,13 @@ Warnungen = Spalte fuer Auffaelligkeiten. Aktuell: eine Strecke sollte genau
zwei Kreisel (Anfang + Ende) beruehren - beruehrt ihre (in x/y gepruefte)
Bounding-Box mehr, steht hier ein Hinweis und es wird zusaetzlich eine
Konsolen-WARNUNG ausgegeben (mit exakten 3D-BBoxen sollte das nicht auftreten).
Scanner-Zeilen bekommen dieselbe Spalte fuer strittige Distanz-Zuordnungen:
liegt ein Scanner ausserhalb jeder Kreisel-/Streckengruppen-/Gefaellestrecke-
Boundingbox und ist der Abstand zum naechsten und zweitnaechsten Carrier fast
gleich gross, schreibt Lisp/count_sep_scan.lsp (cs-zuordnung-lauf, vor dem
Export automatisch aufgerufen) einen Warnungstext mit den IDs beider
Kandidaten in das "warnung"-Feld des Scanner-Blocks im Roh-JSON (siehe
csv:block-to-json in export.lsp) - hier unveraendert uebernommen.
Aufruf:
python export_csv.py <export_raw.json> <data_dir> <output.csv>
@@ -765,6 +772,13 @@ def process_blocks(blocks, lookup, dbg=None):
"bezeichnung": f"Scanner :{scanner_count}",
"planquadrat": planquadrat_column(block, planquadrat_cfg),
"merkmale": build_sensor_merkmale(block),
# "warnung" kommt von cs-zuordnung-lauf (Lisp/count_sep_scan.lsp),
# das vor dem Export strittige Distanz-Zuordnungen (Scanner
# ausserhalb jeder Kreisel-/Strecken-Boundingbox, Abstand zu zwei
# Kandidaten fast gleich) in *cs-scanner-warnung-by-handle*
# sammelt - csv:block-to-json (export.lsp) haengt den Text als
# "warnung"-Feld an den betroffenen Scanner-Block im JSON an.
"warnung": block.get("warnung", ""),
**bbox_columns(block),
})
continue
+1 -1
View File
@@ -131,7 +131,7 @@
[Marker bearbeiten]^C^C(ssg-ensure "Tro_Edit") TRO_EDIT
[<-Bearbeiten (Auto)]^C^C(ssg-ensure "SSG_LIB_Commands") SSG_BLOCKEDIT
[->Export]
[Export Sivas]^C^C(ssg-ensure "count_sep_scan") ZAEHLE_SEP_SCAN (ssg-ensure "export") EXPORTSIVAS
[Export Sivas]^C^C(ssg-ensure "export") EXPORTSIVAS
[<-Export CSV]^C^C(ssg-ensure "export") EXPORTCSV
[->Konfiguration]
[Umschalten 2D und 3D]^C^C(ssg-ensure "SSG_LIB_Commands") SSG_DIM_SWITCH