neuer Schalter dazu
This commit is contained in:
+94
-2
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user