neuer Schalter dazu

This commit is contained in:
2026-09-07 09:36:35 +02:00
parent 22b08cc0ce
commit 7b677f6bb5
2 changed files with 95 additions and 2 deletions
+94 -2
View File
@@ -70,6 +70,15 @@ CSV_DELIMITER = ";"
CSV_ENCODING = "utf-8-sig"
TEILEART_PREFIX = "ILS 2.0 "
# Dekodierung der CSV-Spalten K1-K4 (Anschluss-Koordinatensysteme eines Blocks):
# 12-Zeichen-Base64-Strings mit je drei 24-Bit-Fixed-Point-Werten (x, y, z),
# nur Position ohne Rotation. Kodierung siehe csv:trans-encode in
# dxfmakros/Lisp/export.lsp (Faktor 10, gleiches Base64-Alphabet wie der
# Insertpoint). Rueckgabe in mm im selben Koordinatensystem wie 'Position'.
KX_B64_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
KX_B64_INDEX = {c: i for i, c in enumerate(KX_B64_CHARS)}
KX_TRANS_FAKTOR = 10.0
# Objektarten, die je einen Materialfluss-Knoten bilden
TRANSPORT_KINDS = ("Gefaellestrecke", "Strecke")
# Objektarten, die als Kreis aus zwei Bahnen (links/rechts) modelliert werden
@@ -165,6 +174,9 @@ class Element:
row: int
position: tuple[float, float, float] | None = None
bbox: tuple[float, float, float] | None = None
# Anschluss-Koordinatensysteme K1..K4 aus den gleichnamigen CSV-Spalten,
# als (label, (x, y)) in mm; leere Spalten fehlen. Nur fuer --show-kx.
kpoints: list[tuple[str, tuple[float, float]]] = field(default_factory=list)
@property
def x(self) -> float | None:
@@ -365,6 +377,32 @@ def resolve_input(name: str) -> Path:
# ---------------------------------------------------------------------------
def decode_kpoint(raw: str) -> tuple[float, float] | None:
"""
K1-K4-Base64-String -> (x, y) in mm, oder None bei leer/ungueltig.
Die Spalten K1..K4 enthalten je 12 Zeichen (drei 24-Bit-Werte x, y, z);
fuer die Draufsicht interessiert nur die xy-Lage. Kodierung siehe
csv:trans-encode in dxfmakros/Lisp/export.lsp (vgl. KX_* oben).
"""
text = (raw or "").strip()
if len(text) < 8: # mindestens x und y (2 * 4 Zeichen)
return None
values: list[int] = []
for start in range(0, len(text) - 3, 4):
word = 0
for char in text[start:start + 4]:
if char not in KX_B64_INDEX:
return None
word = (word << 6) | KX_B64_INDEX[char]
if word >= 8388608: # Zweierkomplement (24 Bit)
word -= 16777216
values.append(word)
if len(values) < 2:
return None
return (values[0] / KX_TRANS_FAKTOR, values[1] / KX_TRANS_FAKTOR)
def parse_position(raw: str) -> tuple[float, float, float] | None:
"""'9510.00, -4882.95, 1942.50' -> (9510.0, -4882.95, 1942.5)"""
parts = [p.strip() for p in (raw or "").split(",")]
@@ -462,6 +500,11 @@ def read_elements(path: Path, warnings: list[str]) -> list[Element]:
row=offset,
position=parse_position(row.get("Position") or ""),
bbox=parse_position(row.get("Boundingbox") or ""),
kpoints=[
(label, xy)
for label in ("K1", "K2", "K3", "K4")
if (xy := decode_kpoint(row.get(label) or "")) is not None
],
)
if teile_id in seen:
@@ -1367,6 +1410,13 @@ BBS_DEFAULT_STYLE = dict(fill="#f2f2f2", stroke="#808080", text="#404040")
BBS_MARGIN_MM = 500.0
BBS_MIN_FONT_MM = 40.0
# Darstellung der Anschlusspunkte K1-K4 (--show-kx): Groesse des Kreuzes und
# der Beschriftung in mm, Verbindungslinie K1->K2->K3->K4 in Blau.
KX_CROSS_MM = 120.0
KX_FONT_MM = 90.0
KX_CROSS_COLOR = "#c00000"
KX_LINK_COLOR = "#1f5fd0"
# Physisches Papierformat der SVG-Ausgabe: DIN A3, Quer- oder Hochformat je
# nach Seitenverhaeltnis der Anlage. Die viewBox bleibt in Anlage-mm - nur
# width/height (das gedruckte Format) werden auf A3 skaliert.
@@ -1381,7 +1431,12 @@ def _xml_escape(text: str) -> str:
)
def render_bbs_svg(elements: list[Element], source: str, warnings: list[str]) -> str:
def render_bbs_svg(
elements: list[Element],
source: str,
warnings: list[str],
show_kx: bool = False,
) -> str:
"""
2D-Draufsicht der ganzen Szene: ein achsparalleles Rechteck je CSV-Zeile,
aus 'Position' (Mittelpunkt) und 'Boundingbox' (Breite/Tiefe in X/Y - die
@@ -1397,6 +1452,10 @@ def render_bbs_svg(elements: list[Element], source: str, warnings: list[str]) ->
bleiben so zueinander proportional); das gedruckte Format (width/height)
wird auf DIN A3 (Quer- oder Hochformat je nach Seitenverhaeltnis) skaliert
- sonst waere die SVG-Datei bei einer 25 m breiten Anlage 25 m breit.
show_kx zeichnet zusaetzlich je Objekt die Anschlusspunkte K1-K4 (rotes
Kreuz + kleine Beschriftung) und verbindet sie in der Reihenfolge
K1->K2->K3->K4 mit einer blauen Linie.
"""
items: list[tuple[Element, float, float, float, float]] = []
for element in elements:
@@ -1451,6 +1510,25 @@ def render_bbs_svg(elements: list[Element], source: str, warnings: list[str]) ->
f'font-family="Segoe UI, sans-serif" font-size="{font_size:.1f}" fill="{style["text"]}" '
f'text-anchor="middle" dominant-baseline="middle">{_xml_escape(name)}</text>')
# Anschlusspunkte K1-K4 zuletzt, damit sie ueber den Rechtecken liegen.
if show_kx:
half = KX_CROSS_MM / 2
for element, *_rest in items:
# Anlage- in viewBox-Koordinaten (X verschoben, Y gespiegelt).
pts = [(label, kx - min_x, max_y - ky) for label, (kx, ky) in element.kpoints]
# Verbindungslinie K1->K2->K3->K4 (nur bei mindestens zwei Punkten).
for (_, x1, y1), (_, x2, y2) in zip(pts, pts[1:]):
add(f' <line x1="{x1:.2f}" y1="{y1:.2f}" x2="{x2:.2f}" y2="{y2:.2f}" '
f'stroke="{KX_LINK_COLOR}" stroke-width="8"/>')
for label, sx, sy in pts:
add(f' <line x1="{sx - half:.2f}" y1="{sy:.2f}" x2="{sx + half:.2f}" y2="{sy:.2f}" '
f'stroke="{KX_CROSS_COLOR}" stroke-width="8"/>')
add(f' <line x1="{sx:.2f}" y1="{sy - half:.2f}" x2="{sx:.2f}" y2="{sy + half:.2f}" '
f'stroke="{KX_CROSS_COLOR}" stroke-width="8"/>')
add(f' <text x="{sx + half + 20:.2f}" y="{sy - half:.2f}" '
f'font-family="Segoe UI, sans-serif" font-size="{KX_FONT_MM:.1f}" '
f'fill="{KX_CROSS_COLOR}" dominant-baseline="middle">{label}</text>')
add("</svg>")
return "\n".join(out) + "\n"
@@ -1857,6 +1935,14 @@ def parse_args(argv: list[str] | None = None) -> argparse.Namespace:
"(Mittelpunkt) und 'Boundingbox' (Breite/Tiefe), mit der Bezeichnung "
"im Zentrum, skaliert auf DIN A3. Braucht kein Graphviz.",
)
parser.add_argument(
"--show-kx",
dest="show_kx",
action="store_true",
help="Nur zusammen mit --show-bbs: je Objekt die Anschlusspunkte K1-K4 als "
"kleines Kreuz mit Beschriftung einzeichnen und in der Reihenfolge "
"K1->K2->K3->K4 mit einer blauen Linie verbinden.",
)
return parser.parse_args(argv)
@@ -1890,12 +1976,18 @@ def main(argv: list[str] | None = None) -> int:
stem = csv_file.stem
if args.show_kx and not args.show_bbs:
print("FEHLER: --show-kx ist nur zusammen mit --show-bbs moeglich.",
file=sys.stderr)
return 1
bbs_file = None
if args.show_bbs:
bbs_target = results / f"{stem}_bbs.svg"
try:
bbs_target.write_text(
render_bbs_svg(elements, csv_file.name, warnings), encoding="utf-8"
render_bbs_svg(elements, csv_file.name, warnings, show_kx=args.show_kx),
encoding="utf-8",
)
except ValueError as exc:
print(f"FEHLER: --show-bbs: {exc}", file=sys.stderr)