From 966bfa866d9c4cdf8c362883bd7d0f00af396cab Mon Sep 17 00:00:00 2001 From: Michael Stangl Date: Fri, 21 Aug 2026 11:46:36 +0200 Subject: [PATCH 1/4] Export CSV: Zeilen nach TeileId sortieren, Elementnummer neu vergeben Damit stimmen Elementnummer (erste Spalte) und TeileId ueberein, solange kein Element geloescht wurde - geloeschte Elemente hinterlassen Luecken in der TeileId-Folge statt in der Zeilennummerierung. Co-Authored-By: Claude Sonnet 5 --- lib/export_csv.py | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/lib/export_csv.py b/lib/export_csv.py index 35319fa..bc6c89a 100644 --- a/lib/export_csv.py +++ b/lib/export_csv.py @@ -84,6 +84,15 @@ EXPORTCSV vorhanden (nicht EXPORTSIVAS) - siehe csv:run-export. Insertpoint/ K1-K4 stehen zwar in export_raw.json fuer jeden Export zur Verfuegung (siehe csv:block-to-json), werden aber ebenfalls nur von diesem Skript (EXPORTCSV) als CSV-Spalten ausgegeben. + +Reihenfolge/Elementnummer: die Zeilen werden am Ende aufsteigend nach +TeileId sortiert (numerische IDs) und die Elementnummer (erste Spalte) +danach neu von 1 an vergeben (siehe teileid_sort_key in process_blocks) - +wurde kein Element geloescht, stimmen Elementnummer und TeileId dadurch +ueberein, geloeschte Elemente hinterlassen Luecken 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. """ import json @@ -816,6 +825,22 @@ def process_blocks(blocks, lookup, dbg=None): elem_nr = build_kreisel_weiche_items(items, planquadrat_cfg, elem_nr, dbg=dbg) build_strecke_schleus_items(items, planquadrat_cfg, elem_nr, dbg=dbg) + # Nach dem Sammelheimen aller Zeilen: sortiert nach TeileId ausgeben, damit + # Elementnummer (erste Spalte) und TeileId nach Moeglichkeit uebereinstimmen + # (siehe format_csv_line/Elementnummer-Neuvergabe unten). Numerische IDs + # zuerst aufsteigend; nicht-numerische IDs (z.B. "autogenerated_of_json" + # der Omniflo-Sum-Zeile oder SEP-/SCN-Fallback-IDs ohne ID-Attribut) + # behalten stabil ihre bisherige Reihenfolge und stehen danach. Wurde kein + # Element geloescht, ergibt sich dadurch Elementnummer == TeileId. + def teileid_sort_key(indexed_item): + index, item = indexed_item + tid = str(item.get("teileid", "")).strip() + return (0, int(tid)) if tid.isdigit() else (1, index) + + items = [item for _, item in sorted(enumerate(items), key=teileid_sort_key)] + for i, item in enumerate(items, start=1): + item["nr"] = i + return items From edfba758015d247c0c4721a1b3c1c2c56760ffb9 Mon Sep 17 00:00:00 2001 From: Michael Stangl Date: Fri, 21 Aug 2026 15:37:50 +0200 Subject: [PATCH 2/4] 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 --- Lisp/count_sep_scan.lsp | 529 ++++++++++++++++++++++++++-------------- Lisp/export.lsp | 29 ++- cfg/export.cfg | 21 ++ lang/de_DE.json | 12 + lang/en_GB.json | 12 + lib/export_csv.py | 14 ++ menu/SSG_LIB.mnu | 2 +- 7 files changed, 429 insertions(+), 190 deletions(-) diff --git a/Lisp/count_sep_scan.lsp b/Lisp/count_sep_scan.lsp index ccecb71..5495751 100644 --- a/Lisp/count_sep_scan.lsp +++ b/Lisp/count_sep_scan.lsp @@ -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) diff --git a/Lisp/export.lsp b/Lisp/export.lsp index c3b0d9b..1ec560b 100644 --- a/Lisp/export.lsp +++ b/Lisp/export.lsp @@ -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)) diff --git a/cfg/export.cfg b/cfg/export.cfg index 0731a07..99f3737 100644 --- a/cfg/export.cfg +++ b/cfg/export.cfg @@ -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 + diff --git a/lang/de_DE.json b/lang/de_DE.json index 28f2537..d5bbebc 100644 --- a/lang/de_DE.json +++ b/lang/de_DE.json @@ -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.", diff --git a/lang/en_GB.json b/lang/en_GB.json index 6fbea5a..4d74fe2 100644 --- a/lang/en_GB.json +++ b/lang/en_GB.json @@ -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.", diff --git a/lib/export_csv.py b/lib/export_csv.py index bc6c89a..4a98247 100644 --- a/lib/export_csv.py +++ b/lib/export_csv.py @@ -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 @@ -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 diff --git a/menu/SSG_LIB.mnu b/menu/SSG_LIB.mnu index 47774c5..839ef3e 100644 --- a/menu/SSG_LIB.mnu +++ b/menu/SSG_LIB.mnu @@ -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 From 9341d4c5c29ae2cdf733944fde8b1c5c2849c390 Mon Sep 17 00:00:00 2001 From: Michael Stangl Date: Fri, 21 Aug 2026 15:54:47 +0200 Subject: [PATCH 3/4] Sensor-Zuordnung: billiger Schnelltest vor teurem Lauf cs-zuordnung-noetig-p prueft ohne jede Boundingbox (nur Attribute lesen + interne Ketten-Separatoren aus der Blockdefinition zaehlen), ob der teure cs-zuordnung-lauf (Boundingbox+Regen je Carrier und Sensor) ueberhaupt noetig ist. Voller Lauf nur bei leeren ZUORDNUNG-Eintraegen oder wenn die Summe ANZAHL_SCANNER/ANZAHL_SEPARATOR ueber alle Carrier nicht zur Anzahl der Scanner-/Separator-Symbole (+ interne Ketten-Separatoren) passt. Sonst wird uebersprungen (neue Meldung sens-skip). csv:run-export ruft die Zuordnung jetzt nur noch bei Bedarf auf. Der interaktive Befehl ZAEHLE_SEP_SCAN rechnet weiterhin immer komplett neu (deckt auch den Fall ab, dass ein Sensor ohne Summenaenderung von einem Carrier zu einem anderen verschoben wurde). Co-Authored-By: Claude Opus 4.8 --- Lisp/count_sep_scan.lsp | 62 +++++++++++++++++++++++++++++++++++++++++ Lisp/export.lsp | 10 ++++++- lang/de_DE.json | 1 + lang/en_GB.json | 1 + 4 files changed, 73 insertions(+), 1 deletion(-) diff --git a/Lisp/count_sep_scan.lsp b/Lisp/count_sep_scan.lsp index 5495751..160fa51 100644 --- a/Lisp/count_sep_scan.lsp +++ b/Lisp/count_sep_scan.lsp @@ -513,6 +513,68 @@ (cons 40 r))) ) +;;; ------------------------------------------------------------ +;;; Schnelltest: Ist ein vollstaendiger cs-zuordnung-lauf ueberhaupt +;;; noetig? Der eigentliche Lauf ist teuer (vla-getboundingbox + Regen +;;; je Carrier UND je Sensor). Diese Vorpruefung kommt ohne jede +;;; Bounding-Box aus - sie liest nur Attribute (entget) und zaehlt die +;;; internen Ketten-Separatoren aus der Blockdefinition (cs-count-nested, +;;; reine Tabellen-Traversierung, kein Regen). +;;; +;;; Rueckgabe T (Lauf noetig), wenn EINE der Bedingungen zutrifft: +;;; a) Ein Scanner-/Separator-Symbol hat ein leeres ZUORDNUNG-Attribut +;;; (noch nicht zugeordnet). +;;; b) Die Summe der ANZAHL_SCANNER ueber alle Carrier weicht von der +;;; Anzahl der Scanner-Symbole in der Zeichnung ab. +;;; c) Die Summe der ANZAHL_SEPARATOR ueber alle Carrier weicht von +;;; (Anzahl Separator-Symbole + interne Ketten-Separatoren) ab. +;;; Sonst nil - die vorhandenen Attribute sind konsistent, der teure +;;; Lauf wird uebersprungen. +;;; +;;; Grenze (bewusst in Kauf genommen): Wird ein Sensor von einem Carrier +;;; zu einem anderen verschoben, OHNE dass sich die GESAMTzahlen aendern +;;; (z.B. ein Scanner von Kreisel A nach Kreisel B), faellt das hier +;;; nicht auf - ZUORDNUNG bleibt ja gesetzt und die Summen stimmen +;;; weiter. In so einem Fall ZAEHLE_SEP_SCAN manuell aufrufen, um die +;;; Zuordnung komplett neu zu berechnen. +;;; ------------------------------------------------------------ +(defun cs-zuordnung-noetig-p ( / ss i ent ed nm sk zuord + sumScan sumSep sumIntern nScanSym nSepSym noetig) + (setq sumScan 0 sumSep 0 sumIntern 0 nScanSym 0 nSepSym 0 noetig nil) + (setq ss (ssget "_X" '((0 . "INSERT")))) + (if (null ss) + nil ;; keine Bloecke -> nichts zu tun + (progn + (setq i 0) + (while (< i (sslength ss)) + (setq ent (ssname ss i)) + (setq ed (entget ent)) + (setq nm (cdr (assoc 2 ed))) + (cond + ((cs-carrier-p nm) + (setq sumScan (+ sumScan (atoi (cs-get-att-str ent "ANZAHL_SCANNER")))) + (setq sumSep (+ sumSep (atoi (cs-get-att-str ent "ANZAHL_SEPARATOR")))) + (setq sumIntern (+ sumIntern (cs-count-nested nm *intern-separator-pattern*)))) + (t + (setq sk (cs-sensor-kind nm)) + (if sk + (progn + (setq zuord (cs-get-att-str ent *zuordnung-tag*)) + (if (or (null zuord) (= zuord "")) (setq noetig T)) + (if (eq sk 'scanner) + (setq nScanSym (1+ nScanSym)) + (setq nSepSym (1+ nSepSym))))) + ) + ) + (setq i (1+ i)) + ) + (or noetig + (/= sumScan nScanSym) + (/= sumSep (+ sumIntern nSepSym))) + ) + ) +) + ;;; ============================================================ ;;; ENGINE - wird sowohl von ZAEHLE_SEP_SCAN als auch automatisch ;;; vor jedem CSV-/Sivas-Export aufgerufen (siehe export.lsp, diff --git a/Lisp/export.lsp b/Lisp/export.lsp index 1ec560b..6ec8403 100644 --- a/Lisp/export.lsp +++ b/Lisp/export.lsp @@ -638,9 +638,17 @@ ;; 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). + ;; + ;; Der Lauf ist teuer (Boundingbox+Regen je Carrier und Sensor), + ;; darum vorher der billige Schnelltest cs-zuordnung-noetig-p (nur + ;; Attribute lesen, keine Boundingboxen): nur bei leeren ZUORDNUNG- + ;; Eintraegen oder abweichenden Summen wird tatsaechlich neu gerechnet. (ssg-ensure "count_sep_scan") (if (atoms-family 1 '("cs-zuordnung-lauf")) - (cs-zuordnung-lauf label nil) + (if (cs-zuordnung-noetig-p) + (cs-zuordnung-lauf label nil) + (princ (ssg-textf "sens-skip" (list label))) + ) ) (princ (ssg-textf "exp-collect-blocks" (list label))) diff --git a/lang/de_DE.json b/lang/de_DE.json index d5bbebc..9746c61 100644 --- a/lang/de_DE.json +++ b/lang/de_DE.json @@ -512,6 +512,7 @@ "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-skip": "\n[%1] Separator-/Scanner-Zuordnung aktuell (ZUORDNUNG gesetzt, Summen stimmen) - Neuberechnung uebersprungen.", "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'", diff --git a/lang/en_GB.json b/lang/en_GB.json index 4d74fe2..9098337 100644 --- a/lang/en_GB.json +++ b/lang/en_GB.json @@ -512,6 +512,7 @@ "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-skip": "\n[%1] Separator/scanner assignment up to date (ZUORDNUNG set, sums match) - recomputation skipped.", "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'", From fe3adc7e5967a4e97af1b031e51dab88bd9acf1d Mon Sep 17 00:00:00 2001 From: Michael Stangl Date: Fri, 21 Aug 2026 17:02:10 +0200 Subject: [PATCH 4/4] Separatoren zaehlen: nur platzierte Symbole, interne Kettenglieder nicht Der intern in der GF/VF-Kette gezeichnete 300-mm-Block Staustrecke_Separator_SP ist ein Foerder-/Uebergabestueck, kein eigenstaendiger Separator. Er wurde bisher als Separator mitgezaehlt (ANZAHL_SEPARATOR = intern + platzierte Symbole), wodurch eine GF mit einem darauf gelegten S-LP-Symbol faelschlich 2 statt 1 ergab. count_sep_scan.lsp: ANZAHL_SEPARATOR zaehlt jetzt nur noch die platzierten Separator_SP/S-LP-Symbole (newP = nsep). Schnelltest cs-zuordnung-noetig-p entsprechend auf reine Symbolzahl je Carrier umgestellt. Die interne Zaehlung (cs-count-nested, *intern-separator-pattern*, internSep) entfaellt. Gefaellestrecke.lsp: GF-Bau setzt ANZAHL_SEPARATOR beim Erstellen mit 0 vor (interne Kettenseparatoren zaehlen nicht) - die echte Anzahl fuellt der Export aus den platzierten Symbolen. Label "Sep=N" entfernt (waere sonst immer 0 bzw. widerspraeche dem Export). n-bogen-gf/gf-make-label entsprechend bereinigt. Hinweis: tests/reference/gefaellestrecke_tests_export.csv (Anzahl_Separator=1 ohne platzierte Symbole) wird dadurch stale -> per TEST_EXPORT_ALL in BricsCAD neu erzeugen. Co-Authored-By: Claude Opus 4.8 --- Lisp/Gefaellestrecke.lsp | 25 ++++--- Lisp/count_sep_scan.lsp | 149 +++++++++++++++++++-------------------- lang/de_DE.json | 2 +- lang/en_GB.json | 2 +- 4 files changed, 90 insertions(+), 88 deletions(-) diff --git a/Lisp/Gefaellestrecke.lsp b/Lisp/Gefaellestrecke.lsp index 955c84d..641a91b 100644 --- a/Lisp/Gefaellestrecke.lsp +++ b/Lisp/Gefaellestrecke.lsp @@ -993,7 +993,7 @@ aktuell-frame nach-aus-pt gf-nummer last-ent L-gf-str delta-l-gf laengen-liste first-l l-item - bL90 bL60 bL30 bR90 bR60 bR30 n-bogen-gf + bL90 bL60 bL30 bR90 bR60 bR30 ein-hz-as rad-h-as rad-v-as xu-entry sep-endpunkt rad-h-es rad-v-es xu-exit sep-frame es-aus-frame @@ -1312,8 +1312,6 @@ ) ) ) - (setq n-bogen-gf (+ bL90 bL60 bL30 bR90 bR60 bR30)) - ;; delta-l-gf bereits vor der Zusammenfassung berechnet ;; Block mit allen Attributen erstellen. Geruest fuer Einzelmodul/ @@ -1327,7 +1325,9 @@ delta-l-gf L-gf-str grade - (1+ n-bogen-gf) + ;; n-separator/n-scanner = 0: interne Ketten-Separatoren zaehlen nicht; + ;; die echte Anzahl setzt der Export aus platzierten Symbolen. + 0 0 bL90 bL60 bL30 bR90 bR60 bR30 @@ -1386,7 +1386,7 @@ ;; L-first-seg: Laenge der ersten geraden Staustrecke in mm. ;; Beschriftung: Mitte der ersten Geraden, 400mm senkrecht rechts versetzt. (defun gf-make-label (gf-nummer hoehe-von hoehe-bis deltaH deltaL - L-gf-str winkel n-separator nach-aus-pt entry-hz L-first-seg / + L-gf-str winkel nach-aus-pt entry-hz L-first-seg / label-txt label-pt chz-l shz-l halb basis-winkel) (setq chz-l (cos (* entry-hz (/ pi 180.0)))) (setq shz-l (sin (* entry-hz (/ pi 180.0)))) @@ -1401,8 +1401,7 @@ " " delta-sym "H=" (itoa (fix (abs deltaH))) "mm" " " delta-sym "L=" (itoa (fix deltaL)) "mm" " L=[" L-gf-str "]m" - " W=" (rtos (float winkel) 2 2) grad-zeichen - " Sep=" (itoa n-separator))) + " W=" (rtos (float winkel) 2 2) grad-zeichen)) ;; Basiswinkel: Text immer lesbar auf 0 Grad (X-Achse, links->rechts) oder ;; 90 Grad (Y-Achse) einrasten - unabhaengig von der tatsaechlichen ;; Fahrtrichtung (nie 180/270, damit der Text nie auf dem Kopf steht). @@ -1439,7 +1438,7 @@ (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 (gf-make-label gf-nummer hoehe-von hoehe-bis deltaH deltaL - L-gf-str winkel n-separator nach-aus-pt entry-hz L-first-seg) + L-gf-str winkel nach-aus-pt entry-hz L-first-seg) ;; TYP: mit Bogen -> "Streckengruppe"; ohne Bogen (einsegmentig) -> ;; "Gefaellestrecke" (reduzierter Attributsatz, siehe ssg-strecke-attrib-defs). (setq typ-str @@ -1518,6 +1517,12 @@ (cons "GF_Bogen_R_90" (itoa bogen-R90)) (cons "GF_Bogen_R_60" (itoa bogen-R60)) (cons "GF_Bogen_R_30" (itoa bogen-R30)) + ;; ANZAHL_SEPARATOR/ANZAHL_SCANNER werden beim Bau mit 0 vorbelegt + ;; (n-separator/n-scanner werden von den Aufrufern als 0 uebergeben): + ;; der intern gezeichnete Ketten-Separator Staustrecke_Separator_SP zaehlt + ;; NICHT als Separator. Die tatsaechliche Anzahl ermittelt der Export aus + ;; den platzierten Separator_SP/S-LP-/Scanner-Symbolen und schreibt sie + ;; hier hinein (cs-zuordnung-lauf in count_sep_scan.lsp). (cons "ANZAHL_SEPARATOR" (itoa n-separator)) (cons "ANZAHL_SCANNER" (itoa n-scanner)) (cons "GERUEST_EINZELMODUL" geruest-einzelmodul) @@ -1637,7 +1642,9 @@ (float deltaL-total) L-gf-str winkel - 1 0 + ;; n-separator/n-scanner = 0: der interne Ketten-Separator zaehlt nicht; + ;; die echte Anzahl setzt der Export aus platzierten Symbolen (s.o.). + 0 0 0 0 0 0 0 0 startpunkt last-ent nach-aus-pt (float hz) (float L_stau) es-seite geruest-einzelmodul geruest-typ)) diff --git a/Lisp/count_sep_scan.lsp b/Lisp/count_sep_scan.lsp index 160fa51..b61cd7e 100644 --- a/Lisp/count_sep_scan.lsp +++ b/Lisp/count_sep_scan.lsp @@ -10,6 +10,13 @@ ;;; Attribute ANZAHL_SCANNER/ANZAHL_SEPARATOR des jeweiligen ;;; Carrier-Blocks. ;;; +;;; WICHTIG: Gezaehlt werden ausschliesslich die eigenstaendig +;;; platzierten Sensor-Symbole. Der intern in der GF/VF-Kette +;;; gezeichnete 300-mm-Block Staustrecke_Separator_SP ist nur ein +;;; Ketten-/Uebergabestueck (kein eigenstaendiger Separator) und wird +;;; NICHT mitgezaehlt - wer einen Separator gezaehlt haben will, setzt +;;; ein Separator_SP/S-LP-Symbol. +;;; ;;; 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 @@ -77,14 +84,6 @@ ;; zugeordneten Carriers geschrieben wird. (setq *zuordnung-tag* "ZUORDNUNG") -;; 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*") - ;; Markierung von Sensoren ohne jeglichen Carrier: Kreis auf eigenem Layer. (setq *mark-layer* "S_SENSOR_UNZUGEORDNET") (setq *mark-color* 2) ;; 2 = Gelb @@ -400,35 +399,6 @@ ok ) -;;; ------------------------------------------------------------ -;;; Zaehlt rekursiv, wie oft ein Block (Muster pat) in der -;;; Definition des Blocks bname verwendet wird - inklusive -;;; verschachtelter Unterbloecke. Damit wird die interne Anzahl -;;; der Kette-Separatoren aus der Geometrie ermittelt. -;;; ------------------------------------------------------------ -(defun cs-count-nested (bname pat / e ed nm n stop) - (setq n 0 stop nil) - (setq e (tblobjname "BLOCK" bname)) - (if e - (progn - (setq e (entnext e)) - (while (and e (not stop)) - (setq ed (entget e)) - (cond - ((= (cdr (assoc 0 ed)) "ENDBLK") (setq stop t)) - ((= (cdr (assoc 0 ed)) "INSERT") - (setq nm (cdr (assoc 2 ed))) - (if (wcmatch (strcase nm) pat) - (setq n (1+ n)) - (setq n (+ n (cs-count-nested nm pat))))) - ) - (if (not stop) (setq e (entnext e))) - ) - ) - ) - n -) - ;;; ------------------------------------------------------------ ;;; Attributwert (String) an einem INSERT lesen. "" wenn nicht ;;; vorhanden. Tag ohne Gross/Klein-Unterscheidung. @@ -517,60 +487,82 @@ ;;; Schnelltest: Ist ein vollstaendiger cs-zuordnung-lauf ueberhaupt ;;; noetig? Der eigentliche Lauf ist teuer (vla-getboundingbox + Regen ;;; je Carrier UND je Sensor). Diese Vorpruefung kommt ohne jede -;;; Bounding-Box aus - sie liest nur Attribute (entget) und zaehlt die -;;; internen Ketten-Separatoren aus der Blockdefinition (cs-count-nested, -;;; reine Tabellen-Traversierung, kein Regen). +;;; Bounding-Box aus - sie liest nur Attribute (entget). ;;; ;;; Rueckgabe T (Lauf noetig), wenn EINE der Bedingungen zutrifft: ;;; a) Ein Scanner-/Separator-Symbol hat ein leeres ZUORDNUNG-Attribut -;;; (noch nicht zugeordnet). -;;; b) Die Summe der ANZAHL_SCANNER ueber alle Carrier weicht von der -;;; Anzahl der Scanner-Symbole in der Zeichnung ab. -;;; c) Die Summe der ANZAHL_SEPARATOR ueber alle Carrier weicht von -;;; (Anzahl Separator-Symbole + interne Ketten-Separatoren) ab. -;;; Sonst nil - die vorhandenen Attribute sind konsistent, der teure +;;; (noch nicht zugeordnet) oder ein ZUORDNUNG, das auf keinen +;;; (mehr) existierenden Carrier zeigt. +;;; b) An einem Carrier passt ANZAHL_SCANNER nicht zur Anzahl der ihm +;;; per ZUORDNUNG zugeschriebenen Scanner-Symbole. +;;; c) An einem Carrier passt ANZAHL_SEPARATOR nicht zur Anzahl der ihm +;;; per ZUORDNUNG zugeschriebenen Separator-Symbole (nur platzierte +;;; Symbole zaehlen - dieselbe Regel wie newP im eigentlichen Lauf; +;;; der interne Staustrecke_Separator_SP zaehlt nicht mit). +;;; Sonst nil - die gespeicherten Attribute sind konsistent, der teure ;;; Lauf wird uebersprungen. ;;; -;;; Grenze (bewusst in Kauf genommen): Wird ein Sensor von einem Carrier -;;; zu einem anderen verschoben, OHNE dass sich die GESAMTzahlen aendern -;;; (z.B. ein Scanner von Kreisel A nach Kreisel B), faellt das hier -;;; nicht auf - ZUORDNUNG bleibt ja gesetzt und die Summen stimmen -;;; weiter. In so einem Fall ZAEHLE_SEP_SCAN manuell aufrufen, um die -;;; Zuordnung komplett neu zu berechnen. +;;; Die Zuordnung Symbol->Carrier wird hier billig ueber das bereits +;;; gesetzte ZUORDNUNG-Attribut rekonstruiert (Carrier-ID), OHNE die +;;; teure Boundingbox-/Distanz-Rechnung des eigentlichen Laufs. Dadurch +;;; erkennt der Test - anders als eine reine Gesamtsummen-Pruefung - auch +;;; das Verschieben eines Sensors von einem Carrier zu einem anderen +;;; (die Per-Carrier-Zahl kippt, ZUORDNUNG zeigt aber noch auf den alten). ;;; ------------------------------------------------------------ -(defun cs-zuordnung-noetig-p ( / ss i ent ed nm sk zuord - sumScan sumSep sumIntern nScanSym nSepSym noetig) - (setq sumScan 0 sumSep 0 sumIntern 0 nScanSym 0 nSepSym 0 noetig nil) +(defun cs-zuordnung-noetig-p ( / ss i ent ed nm sk zuord id + carriers carrier-ids sensors s + sepByCarrier scanByCarrier + noetig expScan expSep rec) + (setq noetig nil carriers nil carrier-ids nil sensors nil + sepByCarrier nil scanByCarrier nil) (setq ss (ssget "_X" '((0 . "INSERT")))) (if (null ss) nil ;; keine Bloecke -> nichts zu tun (progn + ;; Pass 1: Carrier (ename . id) und Sensor-Symbole (kind . ZUORDNUNG) + ;; sammeln - die ssget-Reihenfolge ist beliebig, daher erst sammeln, + ;; dann auswerten. (setq i 0) (while (< i (sslength ss)) - (setq ent (ssname ss i)) - (setq ed (entget ent)) - (setq nm (cdr (assoc 2 ed))) + (setq ent (ssname ss i) ed (entget ent) nm (cdr (assoc 2 ed))) (cond ((cs-carrier-p nm) - (setq sumScan (+ sumScan (atoi (cs-get-att-str ent "ANZAHL_SCANNER")))) - (setq sumSep (+ sumSep (atoi (cs-get-att-str ent "ANZAHL_SEPARATOR")))) - (setq sumIntern (+ sumIntern (cs-count-nested nm *intern-separator-pattern*)))) + (setq id (cs-get-att-str ent "ID")) + (setq carriers (cons (cons ent id) carriers)) + (setq carrier-ids (cons id carrier-ids))) (t (setq sk (cs-sensor-kind nm)) (if sk - (progn - (setq zuord (cs-get-att-str ent *zuordnung-tag*)) - (if (or (null zuord) (= zuord "")) (setq noetig T)) - (if (eq sk 'scanner) - (setq nScanSym (1+ nScanSym)) - (setq nSepSym (1+ nSepSym))))) - ) + (setq sensors + (cons (cons sk (cs-get-att-str ent *zuordnung-tag*)) sensors)))) ) (setq i (1+ i)) ) - (or noetig - (/= sumScan nScanSym) - (/= sumSep (+ sumIntern nSepSym))) + ;; Pass 2: Symbole ueber ZUORDNUNG je Carrier-ID zaehlen. Leeres + ;; ZUORDNUNG oder ZUORDNUNG auf einen nicht (mehr) existierenden + ;; Carrier -> Lauf noetig. + (foreach s sensors + (setq sk (car s) zuord (cdr s)) + (cond + ((or (null zuord) (= zuord "")) (setq noetig T)) + ((not (member zuord carrier-ids)) (setq noetig T)) + ((eq sk 'scanner) (setq scanByCarrier (cs-bump scanByCarrier zuord))) + (t (setq sepByCarrier (cs-bump sepByCarrier zuord))) + ) + ) + ;; Pass 3: je Carrier Soll (= zugeschriebene Symbole) gegen die + ;; gespeicherten Attribute pruefen. + (if (not noetig) + (foreach rec carriers + (setq id (cdr rec)) ;; rec = (ename . carrier-id) + (setq expScan (cond ((assoc id scanByCarrier) (cdr (assoc id scanByCarrier))) (t 0))) + (setq expSep (cond ((assoc id sepByCarrier) (cdr (assoc id sepByCarrier))) (t 0))) + (if (or (/= (atoi (cs-get-att-str (car rec) "ANZAHL_SCANNER")) expScan) + (/= (atoi (cs-get-att-str (car rec) "ANZAHL_SEPARATOR")) expSep)) + (setq noetig T)) + ) + ) + noetig ) ) ) @@ -603,7 +595,7 @@ 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) + en nm2 ns nsep 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) @@ -734,16 +726,19 @@ (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)) + ;; ANZAHL_SEPARATOR = nur die tatsaechlich platzierten Separator- + ;; Symbole (Separator_SP/S-LP), die diesem Carrier zugeordnet sind. + ;; Der intern in der GF/VF-Kette gezeichnete 300-mm-Block + ;; Staustrecke_Separator_SP ist nur ein Kettenglied (Foerder-/ + ;; Uebergabestueck), KEIN eigenstaendiger Separator - er wird bewusst + ;; NICHT mitgezaehlt (Entscheidung: nur platzierte Symbole zaehlen). + (setq newP 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))))) + (list nm2 (nth 9 rec) (itoa newS) (itoa newP))))) (if (not (and okS okP)) (princ (ssg-textf "sens-carrier-fehler" (list nm2)))) ) diff --git a/lang/de_DE.json b/lang/de_DE.json index 9746c61..241b304 100644 --- a/lang/de_DE.json +++ b/lang/de_DE.json @@ -516,7 +516,7 @@ "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-zeile": "\n %1 [%2]: ANZAHL_SCANNER=%3, ANZAHL_SEPARATOR=%4 (nur platzierte Symbole)", "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:", diff --git a/lang/en_GB.json b/lang/en_GB.json index 9098337..57d9607 100644 --- a/lang/en_GB.json +++ b/lang/en_GB.json @@ -516,7 +516,7 @@ "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-zeile": "\n %1 [%2]: ANZAHL_SCANNER=%3, ANZAHL_SEPARATOR=%4 (placed symbols only)", "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:",