Aufbau der Planquadrate nur durch Boundingboxen. Ansonsten werden die Bereiche der Lage nicht korrekt erzeugt

This commit is contained in:
2026-09-08 12:37:58 +02:00
parent e78d1cb9ca
commit 0e0f011ea4
4 changed files with 182 additions and 32 deletions
+1 -1
View File
@@ -107,7 +107,7 @@ Detaillierte Dokumentation aller Funktionen und Befehle: siehe `Lisp/README.md`.
| `SSG_LIB_Commands.lsp` | ILS_BTMT_Beladung, ILS_BTMT_Entladung, ILS_SCANNER_INSERT, ILS_SEPARATOR_INSERT, ILS_EINGANG_INSERT, ILS_AUSGANG_INSERT, ILS_ETF_*, ILS_GK*_*, SSG_BLOCKEDIT, SSG_DIM_SWITCH, SSG_SPRACHE | BTMT Be-/Entladung (`ils-insert-station`: wiederholtes Einfuegen von `BTMT-Beladung` bzw. `SC_Entladung`, Drehung interaktiv, Layer aus `LAYER`-Attribut, Dimension via `ssg-ils-block-laden`) + Sensoren (`ils-insert-sensor`) + Eingang-/Ausgang-Symbol (frei platzierbar wie BTMT Be-/Entladung, ueber `ils-insert-station` mit den Rohnamen `Eingang`/`Ausgang`, Bloecke `Eingang_<DIM>.dwg`/`Ausgang_<DIM>.dwg` in data/ils) + Dummy-Befehle fuer Menue + Doppelklick/Kontextmenue-Dispatcher + Konfiguration (2D/3D, Sprache) |
| `Tro_Edit.lsp` | TRO_INSERT, TRO_EDIT | TRO-Marker (Block `TRO_SYM_<Typ>`) fuer sps_skel (lib/tro_annotate.py): TRO_INSERT fuegt eine bestehende Blockdefinition an einem gewaehlten Punkt ein und setzt ID/TYPE (bricht ab, wenn der Typ noch keine Blockdefinition in der Zeichnung hat - die entsteht nur ueber tro_annotate.py); TRO_EDIT aendert ID/TYPE eines bestehenden Markers per Dialog. Typliste aus `tro_types.lsp` (von `tro_annotate.py --emit-lisp` nach DXFM_LISP geschrieben). Der Dispatcher SSG_BLOCKEDIT leitet Blocknamen `TRO_*` zu TRO_EDIT. |
| `menu-inventory.lsp` | MenuInventory | BricsCAD Diagnostik (Menuegruppen, Plugins, Startdateien) |
| `DrawPlanquadrate.lsp` | DRAW_PLANQUADRATE, DELETE_PLANQUADRATE, PLANQUADRATE_TOGGLE | Zeichnet auf Z=0 ein Planquadrat-Linienraster (senkrecht/waagerecht) ueber die aktuellen Zeichnungs-Extents, im Abstand/Zaehlschema der aktiven `[Planquadrate]`-Konfiguration aus `cfg/export.cfg` (dieselbe Quelle wie `lib/export_planquadrat.py` fuer den CSV-Export) - Ursprung (Index 1) aus `x_origin`/`y_origin` bzw. Fallback EXTMIN. Spalten-/Zeilenzahl wird IMMER aufgerundet auf volle Rasterschritte (nicht auf die exakte Extent-Grenze), damit alle senkrechten Linien auf der letzten waagerechten enden und umgekehrt - das Raster bildet immer ein geschlossenes Rechteck. Zaehlung (alphabetisch/numerisch je Achse) als Text am linken/unteren Rand. Das fertige Raster (Linien+Texte) wird per `_.-BLOCK`/`_.INSERT` zu einem einzigen Block `ZZ_PLANQUADRATE` bei (0,0,0) zusammengefasst. Layer aus `cfg/layer.cfg` (`[allgemein]` Key `planquadrate`, Fallback `ZZ_Planquadrate`). DRAW_PLANQUADRATE entfernt vor dem Neuaufbau zuerst einen evtl. vorhandenen Block (`plq-block-entfernen`: INSERTs loeschen + `_-PURGE _B`), DELETE_PLANQUADRATE ruft nur diese Entfernung auf. PLANQUADRATE_TOGGLE ("Planquadrate an/aus", Menue: Konfiguration) prueft per `tblsearch`/`ssget "X"`, ob der Block existiert, und dispatcht auf Zeichnen bzw. Loeschen. Lazy geladen ueber `(ssg-ensure "DrawPlanquadrate")`, nicht in der Core-Ladereihenfolge. |
| `DrawPlanquadrate.lsp` | DRAW_PLANQUADRATE, DELETE_PLANQUADRATE, PLANQUADRATE_TOGGLE | Zeichnet auf Z=0 ein Planquadrat-Linienraster (senkrecht/waagerecht), im Abstand/Zaehlschema der aktiven `[Planquadrate]`-Konfiguration aus `cfg/export.cfg` (dieselbe Quelle wie `lib/export_planquadrat.py` fuer den CSV-Export). Bereich kommt NICHT aus den Zeichnungs-Extents, sondern aus derselben Blockmenge wie EXPORTCSV (`csv:collect-export-blocks`, export.lsp; siehe `plq-export-bbox`): Ursprung (Index 1) UND obere Grenze kommen BEIDE aus der tatsaechlichen Bounding-Box jedes Blocks (`csv:get-bbox`, wie `bbox_columns()`/`planquadrat_column()`/`_bbox_min()` in `lib/export_csv.py` bzw. `lib/export_planquadrat.py`), NICHT aus dem reinen Einfuegepunkt - bei einem gedrehten/exzentrisch eingefuegten Block (z.B. ein um 90 Grad gedrehter Kreisel) kann das Bounding-Box-Zentrum weit vom Einfuegepunkt abweichen; Ursprung und Zellzuordnung muessen auf derselben Referenz beruhen, sonst driften sie auseinander (realer Bug, behoben). Reine Hilfsgeometrie (Kreisel-Umfahrungsboegen als eigene Linien, Texte) zaehlt bewusst nicht mit, sonst weicht das Raster vom CSV-Export ab. `x_origin`/`y_origin` ueberschreiben den Ursprung, Fallback auf EXTMIN/EXTMAX falls keine Export-Bloecke vorhanden sind bzw. `csv:get-bbox` fuer einen Block scheitert. Spalten-/Zeilenzahl wird IMMER aufgerundet auf volle Rasterschritte, damit alle senkrechten Linien auf der letzten waagerechten enden und umgekehrt - das Raster bildet immer ein geschlossenes Rechteck. Zaehlung (alphabetisch/numerisch je Achse) als Text am linken/unteren Rand. Das fertige Raster (Linien+Texte) wird per `_.-BLOCK`/`_.INSERT` zu einem einzigen Block `ZZ_PLANQUADRATE` bei (0,0,0) zusammengefasst. Layer aus `cfg/layer.cfg` (`[allgemein]` Key `planquadrate`, Fallback `ZZ_Planquadrate`). DRAW_PLANQUADRATE entfernt vor dem Neuaufbau zuerst einen evtl. vorhandenen Block (`plq-block-entfernen`: INSERTs loeschen + `_-PURGE _B`), DELETE_PLANQUADRATE ruft nur diese Entfernung auf. PLANQUADRATE_TOGGLE ("Planquadrate an/aus", Menue: Konfiguration) prueft per `tblsearch`/`ssget "X"`, ob der Block existiert, und dispatcht auf Zeichnen bzw. Loeschen. Lazy geladen ueber `(ssg-ensure "DrawPlanquadrate")`, nicht in der Core-Ladereihenfolge. Die Planquadrat-SPALTE im CSV-Export (`export_csv.py::planquadrat_column`/`resolve_origins`, `lib/export_planquadrat.py::compute_planquadrat`/`_bbox_min`) rechnet ebenfalls durchgaengig auf Bounding-Box-Basis (nicht nur Einfuegepunkt) und liefert eine SPANNE ("A/1-J/1") statt nur einer Zelle, wenn ein Element mehr als eine Zelle ueberdeckt (Kreisel, lange VF-/GF-Ketten). |
## DCL-Dialoge
+101 -15
View File
@@ -10,15 +10,33 @@
;; je Spalte die zugehoerige Zaehlung (alphabetisch oder numerisch, je nach
;; x_description/y_description).
;;
;; Bereich: die aktuellen Zeichnungs-Extents (EXTMIN/EXTMAX), aber IMMER auf
;; ein ganzzahliges Vielfaches der Schrittweite ab dem Ursprung aufgerundet -
;; das Raster bildet also stets ein GESCHLOSSENES Rechteck: jede senkrechte
;; Linie endet exakt auf der letzten waagerechten und umgekehrt (kein
;; "Ueberstand" einzelner Linien ueber die letzte volle Zeile/Spalte hinaus).
;; Bereich: aus den EXPORTIERTEN Bloecken (dieselbe Blockmenge, die
;; csv:collect-export-blocks in export.lsp fuer EXPORTCSV sammelt - siehe
;; plq-export-bbox), NICHT die Zeichnungs-Extents (EXTMIN/EXTMAX). Ursprung
;; (Index 1) UND obere Grenze kommen BEIDE aus der tatsaechlichen Bounding-Box
;; jedes Blocks (csv:get-bbox, dieselbe wie bbox_columns()/
;; planquadrat_column()/_bbox_min() in lib/export_csv.py bzw.
;; lib/export_planquadrat.py), NICHT aus dem reinen Einfuegepunkt: bei einem
;; gedrehten oder exzentrisch eingefuegten Block (z.B. ein um 90 Grad
;; gedrehter Kreisel, dessen Insertpunkt an einem Rand statt der Mitte liegt)
;; kann das Bounding-Box-Zentrum weit vom Einfuegepunkt abweichen - Ursprung
;; und Zellzuordnung MUESSEN auf derselben Referenz beruhen, sonst driften sie
;; auseinander (realer Bug: ein Kreisel landete dadurch in der CSV-Spalte
;; "F/3-G/13" statt der laut Zeichnung korrekten Spalte A). Reine
;; Hilfsgeometrie (Kreisel-Umfahrungsboegen als eigene Linien, Texte, das
;; Planquadrat-Raster selbst) zaehlt zu EXTMIN/EXTMAX, gehoert aber zu keinem
;; Export-Block - das BricsCAD-Raster muss daher auf derselben Blockmenge wie
;; der CSV-Export rechnen, sonst landet ein Bauteil im CSV in einer anderen
;; Spalte/Zeile als im Raster in der Zeichnung.
;; Der Bereich wird IMMER auf ein ganzzahliges Vielfaches der Schrittweite ab
;; dem Ursprung aufgerundet - das Raster bildet also stets ein GESCHLOSSENES
;; Rechteck: jede senkrechte Linie endet exakt auf der letzten waagerechten
;; und umgekehrt (kein "Ueberstand" einzelner Linien ueber die letzte volle
;; Zeile/Spalte hinaus).
;; x_origin/y_origin aus [Planquadrate] legen die Koordinate von Index 1 fest
;; (wie im CSV-Export); sind sie leer, faellt der Ursprung auf EXTMIN zurueck
;; (= kleinstes X/Y der Zeichnung, analog zur Auto-Ermittlung in
;; export_planquadrat.resolve_origins).
;; (wie im CSV-Export) und ueberschreiben den automatisch ermittelten
;; Ursprung. Fallback auf EXTMIN/EXTMAX, falls keine exportfaehigen Bloecke in
;; der Zeichnung stehen.
;;
;; Das fertige Raster (Linien + Beschriftungstexte) wird zu EINEM Block
;; (*plq-blockname*) zusammengefasst und bei (0,0,0) eingefuegt - dadurch
@@ -108,6 +126,60 @@
(if pos (vl-string-trim " \t" (substr s 1 pos)) (vl-string-trim " \t" s))
)
;; Ursprung + Ausdehnung aus den EXPORTIERTEN Bloecken ermitteln - dieselbe
;; Blockmenge, die csv:collect-export-blocks (export.lsp) fuer EXPORTCSV
;; sammelt. Der reine EXTMIN/EXTMAX der Zeichnung (alle Entities, auch
;; Hilfsgeometrie wie Kreisel-Umfahrungsboegen) weicht davon ab und fuehrte zu
;; einem im BricsCAD-Raster verschobenen Planquadrat gegenueber der CSV-Spalte
;; - siehe cfg/export.cfg [Planquadrate] und den Hinweis oben in der
;; Moduldoku. Sowohl Ursprung als auch obere Grenze kommen aus der
;; tatsaechlichen Bounding-Box (csv:get-bbox, dieselbe wie
;; bbox_columns()/planquadrat_column()/_bbox_min() in lib/export_csv.py bzw.
;; lib/export_planquadrat.py), NICHT aus dem reinen Einfuegepunkt: bei einem
;; gedrehten oder exzentrisch eingefuegten Block (z.B. ein um 90 Grad
;; gedrehter Kreisel, dessen Insertpunkt an einem Rand statt der Mitte liegt)
;; kann das BBox-Zentrum weit vom Einfuegepunkt abweichen - Ursprung und
;; Zellzuordnung MUESSEN auf derselben Referenz beruhen, sonst driften sie
;; auseinander (genau der Bug, der zur Einfuehrung von plq-export-bbox
;; fuehrte). Fallback auf den Einfuegepunkt (Gruppencode 10) nur, wenn
;; csv:get-bbox fuer einen Block scheitert (z.B. auf eingefrorenem Layer).
;; Rueckgabe: Liste (xmin ymin xmax ymax) oder nil, wenn keine Bloecke.
(defun plq-export-bbox ( / ss i ename ed pt bbox xmin ymin xmax ymax)
(setq ss (csv:collect-export-blocks))
(if ss
(progn
(setq i 0)
(while (setq ename (ssname ss i))
(setq ed (entget ename))
(setq pt (cdr (assoc 10 ed)))
(setq bbox (csv:get-bbox ename))
(if bbox
(progn
(setq xmin (if xmin (min xmin (- (nth 0 bbox) (/ (nth 3 bbox) 2.0)))
(- (nth 0 bbox) (/ (nth 3 bbox) 2.0))))
(setq ymin (if ymin (min ymin (- (nth 1 bbox) (/ (nth 4 bbox) 2.0)))
(- (nth 1 bbox) (/ (nth 4 bbox) 2.0))))
(setq xmax (if xmax (max xmax (+ (nth 0 bbox) (/ (nth 3 bbox) 2.0)))
(+ (nth 0 bbox) (/ (nth 3 bbox) 2.0))))
(setq ymax (if ymax (max ymax (+ (nth 1 bbox) (/ (nth 4 bbox) 2.0)))
(+ (nth 1 bbox) (/ (nth 4 bbox) 2.0))))
)
(if pt
(progn
(setq xmin (if xmin (min xmin (car pt)) (car pt)))
(setq ymin (if ymin (min ymin (cadr pt)) (cadr pt)))
(setq xmax (if xmax (max xmax (car pt)) (car pt)))
(setq ymax (if ymax (max ymax (cadr pt)) (cadr pt)))
)
)
)
(setq i (1+ i))
)
)
)
(if xmin (list xmin ymin xmax ymax))
)
;; Zeichnet eine Linie auf dem aktuellen Layer.
(defun plq-line (p1 p2)
(entmake (list
@@ -135,7 +207,7 @@
;; DRAW_PLANQUADRATE - Raster zeichnen/neu aufbauen
;; ============================================================
(defun c:DRAW_PLANQUADRATE ( / section x-step x-alpha y-step y-alpha
ext-min ext-max x-origin y-origin
ext-min ext-max bbox x-origin y-origin
x0 y0 x1 y1 lay hoehe n-x n-y i x y
label textabstand lastEnt ss e)
(ssg-start "DRAW_PLANQUADRATE" '(("OSMODE")))
@@ -156,28 +228,42 @@
(princ (ssg-text "plq-ungueltige-schrittweite"))
(progn
;; Vorhandenes Raster zuerst entfernen (Block + Definition) -
;; wiederholte Aufrufe erzeugen so keine Dubletten.
;; wiederholte Aufrufe erzeugen so keine Dubletten UND ein alter
;; Planquadrat-Block verfaelscht nicht die Bloecke-BBox unten
;; (Raster-Linien/-Texte zaehlen ohnehin nicht zu den Export-
;; Blockmustern, aber so ist es unabhaengig davon sauber).
(plq-block-entfernen)
;; Referenzbereich: dieselbe Blockmenge wie EXPORTCSV
;; (csv:collect-export-blocks, export.lsp) - siehe plq-export-bbox.
;; Fallback auf die Zeichnungs-Extents (EXTMIN/EXTMAX), falls keine
;; exportfaehigen Bloecke in der Zeichnung stehen.
(setq bbox (plq-export-bbox))
(setq ext-min (getvar "EXTMIN"))
(setq ext-max (getvar "EXTMAX"))
(setq x-origin (export:cfg "Planquadrate" "x_origin" ""))
(setq x-origin (if (and x-origin (/= x-origin "")) (* 1000.0 (atof x-origin)) (car ext-min)))
(setq x-origin (if (and x-origin (/= x-origin ""))
(* 1000.0 (atof x-origin))
(if bbox (nth 0 bbox) (car ext-min))
))
(setq y-origin (export:cfg "Planquadrate" "y_origin" ""))
(setq y-origin (if (and y-origin (/= y-origin "")) (* 1000.0 (atof y-origin)) (cadr ext-min)))
(setq y-origin (if (and y-origin (/= y-origin ""))
(* 1000.0 (atof y-origin))
(if bbox (nth 1 bbox) (cadr ext-min))
))
(setq x0 x-origin)
(setq y0 y-origin)
;; Anzahl Spalten/Zeilen: mind. 1, sonst so viele volle Schritte,
;; bis die Zeichnungs-Extents (mind.) abgedeckt sind - AUFGERUNDET,
;; bis der Referenzbereich (mind.) abgedeckt ist - AUFGERUNDET,
;; damit x1/y1 exakt auf einem Rasterschritt liegen. Damit endet
;; JEDE senkrechte Linie exakt auf der letzten waagerechten (bei
;; y1) und jede waagerechte exakt auf der letzten senkrechten (bei
;; x1) - das Raster bildet immer ein geschlossenes Rechteck.
(setq n-x (max 1 (fix (+ 0.999999 (/ (- (car ext-max) x0) x-step)))))
(setq n-y (max 1 (fix (+ 0.999999 (/ (- (cadr ext-max) y0) y-step)))))
(setq n-x (max 1 (fix (+ 0.999999 (/ (- (if bbox (nth 2 bbox) (car ext-max)) x0) x-step)))))
(setq n-y (max 1 (fix (+ 0.999999 (/ (- (if bbox (nth 3 bbox) (cadr ext-max)) y0) y-step)))))
(setq x1 (+ x0 (* n-x x-step)))
(setq y1 (+ y0 (* n-y y-step)))
+12 -4
View File
@@ -432,20 +432,28 @@ def bbox_columns(block):
def planquadrat_column(block, planquadrat_cfg):
"""Berechnet das Planquadrat aus x/y des Blocks (siehe export_planquadrat.py).
"""Berechnet das Planquadrat aus der Bounding-Box des Blocks, falls
vorhanden (siehe export_planquadrat.py), sonst aus dem Einfuegepunkt.
planquadrat_cfg = Rueckgabe von resolve_origins() fuer den aktuellen
Exportlauf (x_origin_mm/y_origin_mm bereits aufgeloest).
Leerer String, wenn keine Planquadrat-Konfiguration geladen werden konnte
(fehlende/unvollstaendige [Planquadrate]-Sektion in cfg/export.cfg).
Ueberdeckt die Bounding-Box des Elements mehr als eine Zelle (z.B. ein
Kreisel oder eine lange VarioFoerderer-Kette), liefert compute_planquadrat
eine SPANNE "start-ende" (z.B. "A/1-J/1") statt nur der Mittelpunkt-Zelle.
"""
if planquadrat_cfg is None:
return ""
x = block.get("x")
y = block.get("y")
bbox = block.get("bbox") or {}
x = bbox.get("cx", block.get("x"))
y = bbox.get("cy", block.get("y"))
if x is None or y is None:
return ""
return compute_planquadrat(float(x), float(y), planquadrat_cfg)
dx = bbox.get("dx", 0.0)
dy = bbox.get("dy", 0.0)
return compute_planquadrat(float(x), float(y), planquadrat_cfg, dx_mm=float(dx), dy_mm=float(dy))
# ---------------------------------------------------------------------------
+68 -12
View File
@@ -54,17 +54,42 @@ def load_planquadrat_config(cfg_path=None):
}
def _bbox_min(block, axis):
"""Untere Bounding-Box-Kante eines Blocks auf einer Achse ("x"/"y"), oder
dessen Einfuegepunkt, falls kein bbox-Feld vorhanden ist (z.B.
EXPORTSIVAS, das keine Bounding-Box mitschickt - siehe include-bbox in
csv:block-to-json, Lisp/export.lsp).
MUSS dieselbe Referenz (BBox statt nur Einfuegepunkt) wie
compute_planquadrat/planquadrat_column verwenden, sonst driften Ursprung
(Index 1) und Zellzuordnung auseinander: ein Element, dessen Bounding-Box-
Mitte weit von seinem Einfuegepunkt abweicht (z.B. ein um 90 Grad
gedrehter Kreisel, dessen Insertpunkt an einem Rand statt der Mitte
liegt), wuerde sonst in eine andere Spalte/Zeile einsortiert als der
Ursprung erwarten laesst.
"""
bbox = block.get("bbox")
if bbox:
c = bbox.get("c" + axis)
d = bbox.get("d" + axis)
if c is not None and d is not None:
return float(c) - float(d) / 2.0
v = block.get(axis)
return float(v) if v is not None else None
def resolve_origins(cfg, blocks):
"""Loest x_origin_mm/y_origin_mm auf, falls in export.cfg nicht gesetzt.
cfg = Rueckgabe von load_planquadrat_config() (kann None sein).
blocks = alle Bloecke des aktuellen Exportlaufs (Liste von dicts mit
"x"/"y", wie im Roh-JSON von export.lsp).
blocks = alle Bloecke des aktuellen Exportlaufs (Liste von dicts, wie im
Roh-JSON von export.lsp - "x"/"y" und optional "bbox").
Automatik: das am weitesten links liegende Element (min x) setzt
x_origin, das am weitesten unten liegende (min y) setzt y_origin -
beide landen damit exakt in Planquadrat-Index 1 auf ihrer Achse.
Explizit in export.cfg gesetzte Werte werden nicht ueberschrieben.
Automatik: die am weitesten links liegende Bounding-Box-Kante (min x)
setzt x_origin, die am weitesten unten liegende (min y) setzt y_origin -
beide landen damit exakt in Planquadrat-Index 1 auf ihrer Achse. Fehlt
einem Block die Bounding-Box, faellt _bbox_min auf dessen Einfuegepunkt
zurueck. Explizit in export.cfg gesetzte Werte werden nicht ueberschrieben.
Rueckgabe: neues dict (cfg bleibt unveraendert) oder None, wenn cfg None ist.
"""
@@ -72,10 +97,12 @@ def resolve_origins(cfg, blocks):
return None
resolved = dict(cfg)
if resolved.get("x_origin_mm") is None:
xs = [float(b["x"]) for b in blocks if b.get("x") is not None]
xs = [_bbox_min(b, "x") for b in blocks]
xs = [v for v in xs if v is not None]
resolved["x_origin_mm"] = min(xs) if xs else 0.0
if resolved.get("y_origin_mm") is None:
ys = [float(b["y"]) for b in blocks if b.get("y") is not None]
ys = [_bbox_min(b, "y") for b in blocks]
ys = [v for v in ys if v is not None]
resolved["y_origin_mm"] = min(ys) if ys else 0.0
return resolved
@@ -104,10 +131,39 @@ def _index_for(coord_mm, step_mm, origin_mm=0.0):
return int((coord_mm - origin_mm) // step_mm) + 1
def compute_planquadrat(x_mm, y_mm, cfg):
"""Berechnet die Planquadrat-Bezeichnung fuer eine x/y-Koordinate (mm)."""
x_idx = _index_for(x_mm, cfg["x_step_mm"], cfg.get("x_origin_mm", 0.0))
y_idx = _index_for(y_mm, cfg["y_step_mm"], cfg.get("y_origin_mm", 0.0))
def _cell_label(x_idx, y_idx, cfg):
x_label = _index_to_alpha(x_idx) if cfg["x_alpha"] else str(x_idx)
y_label = _index_to_alpha(y_idx) if cfg["y_alpha"] else str(y_idx)
return cfg["format"].format(x=x_label, y=y_label)
def compute_planquadrat(x_mm, y_mm, cfg, dx_mm=0.0, dy_mm=0.0):
"""Berechnet die Planquadrat-Bezeichnung fuer ein Element.
x_mm/y_mm = Mittelpunkt (Bounding-Box-Zentrum bzw. Einfuegepunkt, mm).
dx_mm/dy_mm = Ausdehnung der Bounding-Box (mm), optional (Default 0).
Ohne Ausdehnung (dx_mm/dy_mm <= 0, z.B. Sensoren/Weichen ohne eigene BBox)
wird wie bisher NUR die Zelle des Mittelpunkts geliefert (z.B. "A/1").
Mit Ausdehnung > 0 wird die Zelle der unteren/linken UND der oberen/
rechten Bounding-Box-Ecke berechnet - deckt das Element mehr als eine
Zelle ab (z.B. ein Kreisel ueber A/1 bis J/1, ein VarioFoerderer ueber
B/2 bis C/3), wird eine SPANNE "start-ende" geliefert (z.B. "A/1-J/1",
"B/2-C/3"); liegen beide Ecken in derselben Zelle, wird weiterhin nur
diese eine Zelle geliefert (kein "A/1-A/1").
"""
if dx_mm and dx_mm > 0 and dy_mm and dy_mm > 0:
x_lo_idx = _index_for(x_mm - dx_mm / 2.0, cfg["x_step_mm"], cfg.get("x_origin_mm", 0.0))
x_hi_idx = _index_for(x_mm + dx_mm / 2.0, cfg["x_step_mm"], cfg.get("x_origin_mm", 0.0))
y_lo_idx = _index_for(y_mm - dy_mm / 2.0, cfg["y_step_mm"], cfg.get("y_origin_mm", 0.0))
y_hi_idx = _index_for(y_mm + dy_mm / 2.0, cfg["y_step_mm"], cfg.get("y_origin_mm", 0.0))
if x_lo_idx == x_hi_idx and y_lo_idx == y_hi_idx:
return _cell_label(x_lo_idx, y_lo_idx, cfg)
start = _cell_label(x_lo_idx, y_lo_idx, cfg)
ende = _cell_label(x_hi_idx, y_hi_idx, cfg)
return f"{start}-{ende}"
x_idx = _index_for(x_mm, cfg["x_step_mm"], cfg.get("x_origin_mm", 0.0))
y_idx = _index_for(y_mm, cfg["y_step_mm"], cfg.get("y_origin_mm", 0.0))
return _cell_label(x_idx, y_idx, cfg)