Files
dxfmakros/lib/export_neighbors.py
T
m.stangl 6ab698286f CSV-Export: Nachbarschaftserkennung ueber Bounding-Box-Ueberschneidung (STRtree/shapely)
Neue Spalte "Nachbarn" (kommaseparierte TeileId-Liste) in export_csv.py:
zwei Elemente gelten als benachbart, wenn ihre Bounding-Boxes (x/y-Ebene)
sich ueberschneiden. Toleranz konfigurierbar in mm ueber cfg/export.cfg
[Nachbarschaft] -> toleranz_mm, siehe lib/export_neighbors.py.
Nur EXPORTCSV betroffen, EXPORTSIVAS bleibt unveraendert.

shapely als neue Abhaengigkeit in requirements.txt ergaenzt.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-22 15:26:30 +02:00

73 lines
2.7 KiB
Python

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
export_neighbors.py - Ermittelt benachbarte Elemente ueber die Bounding-Box (STRtree).
Zwei Elemente gelten als benachbart, wenn ihre Bounding-Boxes sich in der
x/y-Ebene (Grundriss, Z/Hoehe wird ignoriert) ueberschneiden. Um auch nah
beieinander liegende, aber nicht direkt beruehrende Elemente (z.B. Kreisel)
als benachbart zu erkennen, wird jede Bounding-Box vor der Pruefung um die
Haelfte der konfigurierten Toleranz nach allen Seiten erweitert - zwei
Elemente mit einem Abstand <= toleranz_mm gelten dann ebenfalls als
benachbart. Die Toleranz wird aus cfg/export.cfg ([Nachbarschaft] ->
toleranz_mm) gelesen.
Nur von export_csv.py genutzt (EXPORTCSV), nicht von export_sivas.py.
"""
import configparser
from shapely.geometry import box
from shapely.strtree import STRtree
from export_blockpatterns import cfg_path_from_env
def load_neighbor_tolerance_mm(cfg_path=None):
"""Liest die Nachbarschafts-Toleranz (mm) aus export.cfg. Default: 0."""
if cfg_path is None:
cfg_path = cfg_path_from_env()
parser = configparser.ConfigParser(inline_comment_prefixes=("#",))
parser.read(cfg_path, encoding="utf-8")
return parser.getfloat("Nachbarschaft", "toleranz_mm", fallback=0.0)
def compute_neighbor_ids(items, tolerance_mm):
"""Ermittelt je Item die IDs (item["teileid"]) ueberschneidender Nachbarn.
items = Liste von dict mit "teileid" und optional "_bbox"
({"cx","cy","dx","dy",...}, siehe export.lsp csv:get-bbox / export_csv.py
bbox_columns). Items ohne _bbox (z.B. die synthetische Omniflo-Sum-Zeile)
haben keine Nachbarn.
Rueckgabe: Liste von kommaseparierten Nachbar-ID-Strings, positionsgleich
zu items (nicht ueber die ID dedupliziert, da TeileId nicht zwingend
eindeutig ist).
"""
idx_with_bbox = [i for i, it in enumerate(items) if it.get("_bbox")]
result = [""] * len(items)
if len(idx_with_bbox) < 2:
return result
half_tol = tolerance_mm / 2.0
geoms = []
for i in idx_with_bbox:
bb = items[i]["_bbox"]
minx = bb["cx"] - bb["dx"] / 2.0 - half_tol
maxx = bb["cx"] + bb["dx"] / 2.0 + half_tol
miny = bb["cy"] - bb["dy"] / 2.0 - half_tol
maxy = bb["cy"] + bb["dy"] / 2.0 + half_tol
geoms.append(box(minx, miny, maxx, maxy))
tree = STRtree(geoms)
for local_i, global_i in enumerate(idx_with_bbox):
hits = tree.query(geoms[local_i], predicate="intersects")
neighbor_ids = [
items[idx_with_bbox[int(local_j)]]["teileid"]
for local_j in hits
if int(local_j) != local_i
]
result[global_i] = ", ".join(neighbor_ids)
return result