tro_annotate --anlage: Staubereiche in der Zeichnung, --read-jams zurueck

Mit --anlage <csv>_anlage.json zeichnet tro_annotate je Staubereich einen
Block JAM_AREA (Layer TRO_JAM): Rechteck mit dem Namen Jam<NNNN>_<k> wie in
der SCL, jam_versatz vor dem Separator Richtung Vorgaenger. Attribute
CONVEYOR, NUMBER, TRO + ENTRY (stabiler Schluessel), AREA, AUTO. Doppelklick
oeffnet den Dialog JAM_EDIT (dxfmakros).

--read-jams uebernimmt im Dialog geaenderte Nummern aus der beschrifteten
Zeichnung nach cfg/<csv>_jam_names.ini (nur Abweichungen von AUTO);
scl_skeleton wendet die Datei mit der Anlagen-JSON an (scl_context:
jam_numbers + Korrekturen, load/save_jam_overrides).

Dematic: 25 Staubereiche gezeichnet; Rundreise TRO20 -> Jam0054_9 bis in die
SCL geprueft. Gemeinsame Zeichnung mit --offset 0,0 beschriften (automatischer
Lagebezug waehlte dy +360 mm).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-23 12:47:11 +02:00
parent 404d500271
commit e5d1fce41f
7 changed files with 540 additions and 17 deletions
+96 -8
View File
@@ -33,9 +33,11 @@ Wert ersetzen, Merker loeschen.
from __future__ import annotations
import configparser
import re
from collections import deque
from datetime import datetime
from pathlib import Path
from typing import TYPE_CHECKING
from pydantic import BaseModel, ConfigDict, Field
@@ -64,7 +66,13 @@ __all__ = [
"HwSource",
"build_context",
"flow_order",
"JAM_NAMES_SUFFIX",
"jam_key",
"jam_label",
"jam_names",
"jam_numbers",
"load_jam_overrides",
"save_jam_overrides",
"reachable_via",
]
@@ -631,10 +639,88 @@ def _jam_db(area: JamArea | None, hw: HwSource,
return (f'"DB_JamArea{nr}".stJam{nr}_____' if nr else _JAM_DB), False
def jam_names(document: TroDocument, geordnet: list[TroBase],
hw: HwSource) -> dict[str, str]:
JAM_NAMES_SUFFIX = "_jam_names.ini"
# Wert in der Korrekturdatei: Foerderer und laufende Nummer, "0054_3"
_JAM_WERT = re.compile(r"^(\d{4})_(\d+)$")
def jam_key(area: JamArea) -> str:
"""Stabiler Schluessel eines Staubereichs: TRO-ID + Eingang ("TRO17.1").
Die Kennung J<Separator> folgt der TeileId und aendert sich mit jedem
CSV-Export; die TRO-ID bleibt (cfg/<csv>_tro_ids.ini).
"""
return f"{area.tro}.{area.entry}"
def jam_label(nr: str, k: int) -> str:
"""Kurzname fuer Zeichnung und Dialog: Jam0054_3."""
return f"Jam{nr}_{k}"
def load_jam_overrides(path: Path) -> dict[str, tuple[str, int]]:
"""Korrekturen der Staubereichs-Nummern aus ``<csv>_jam_names.ini``.
Abschnitt [jam], je Zeile ``<TRO-ID>.<Eingang> = <NNNN>_<k>``
(``TRO17.1 = 0054_3``). Geschrieben von tro_annotate --read-jams aus den
im Dialog JAM_EDIT geaenderten Staubereichen; von Hand pflegbar.
Fehlt die Datei, gibt es keine Korrekturen.
"""
if not path.is_file():
return {}
parser = configparser.ConfigParser(inline_comment_prefixes=(";", "#"),
interpolation=None)
parser.optionxform = str
with path.open(encoding="utf-8") as fh:
parser.read_file(fh)
result: dict[str, tuple[str, int]] = {}
if parser.has_section("jam"):
for key, value in parser["jam"].items():
m = _JAM_WERT.match(value.strip())
if not m:
raise ValueError(f"{path.name} [jam] {key} = {value!r}: erwartet "
f"<NNNN>_<k>, z. B. 0054_3")
result[key.strip()] = (m.group(1), int(m.group(2)))
return result
def save_jam_overrides(path: Path, overrides: dict[str, tuple[str, int]]) -> None:
"""Korrekturdatei neu schreiben (sortiert nach TRO-ID); leer = Datei weg."""
if not overrides:
if path.is_file():
path.unlink()
return
zeilen = [
"; Staubereichs-Nummern, die von der Ableitung abweichen",
"; (scl_context.jam_names). Geschrieben von tro_annotate --read-jams aus",
"; dem Dialog JAM_EDIT; von Hand pflegbar.",
"; <TRO-ID>.<Eingang> = <Foerderer NNNN>_<laufende Nummer k>",
"",
"[jam]",
]
zeilen += [f"{key} = {nr}_{k}" for key, (nr, k) in sorted(overrides.items())]
path.write_text("\n".join(zeilen) + "\n", encoding="utf-8")
def jam_names(document: TroDocument, geordnet: list[TroBase], hw: HwSource,
overrides: dict[str, tuple[str, int]] | None = None) -> dict[str, str]:
"""Staubereichs-Namen je Kennung (J0048 -> '"DB_JamArea0042".stJam0042_1').
Die abgeleiteten Nummern (jam_numbers), ersetzt durch ``overrides``
(load_jam_overrides) fuer die dort genannten Staubereiche.
"""
nummern = jam_numbers(document, geordnet, hw)
for area in document.jam_areas_or_derived():
if overrides and jam_key(area) in overrides:
nummern[area.jam_id] = overrides[jam_key(area)]
return {jam_id: f'"DB_JamArea{nr}".stJam{nr}_{k}'
for jam_id, (nr, k) in nummern.items()}
def jam_numbers(document: TroDocument, geordnet: list[TroBase],
hw: HwSource) -> dict[str, tuple[str, int]]:
"""Abgeleitete Staubereichs-Nummern je Kennung (J0048 -> ("0042", 1)).
Regel der SPS-Programmierer (Dematic, 2026-10-22): <NNNN> ist der
Foerderer, auf dem der Staubereich liegt (Nummer des Kreisel- bzw.
Vario-Motors), <k> zaehlt auf diesem Foerderer in Fliessrichtung hoch.
@@ -706,11 +792,11 @@ def jam_names(document: TroDocument, geordnet: list[TroBase],
if nr:
je_foerderer.setdefault(nr, []).append(area)
nachfolger = {t.tro_id: list(t.successors) for t in geordnet}
namen: dict[str, str] = {}
nummern: dict[str, tuple[str, int]] = {}
for nr, liste in je_foerderer.items():
for k, area in enumerate(_entlang(liste, nachfolger), start=1):
namen[area.jam_id] = f'"DB_JamArea{nr}".stJam{nr}_{k}'
return namen
nummern[area.jam_id] = (nr, k)
return nummern
def _entlang(areas: list[JamArea], nachfolger: dict[str, list[str]]) -> list[JamArea]:
@@ -908,13 +994,15 @@ def _tro_context(tro: TroBase, alle: dict[str, TroBase],
def build_context(document: TroDocument, source: str, start_id: str = "",
created: str | None = None,
eplan: EplanDocument | None = None,
anlage: AnlageDocument | None = None) -> SclFile:
anlage: AnlageDocument | None = None,
jam_overrides: dict[str, tuple[str, int]] | None = None) -> SclFile:
"""TroDocument -> SclFile (TROs in Fliessrichtung).
anlage: Anlagen-JSON (merge_hw_layout) - ihr Layout ist ``document``,
ihre Zuordnungen liefern die Namen. eplan: alte Zuordnung Layout ->
E-Planung (e_planning.py), nur ohne ``anlage``. Ohne beide bleiben alle
Namen offen und die Ausgabe ist die bisherige.
Namen offen und die Ausgabe ist die bisherige. jam_overrides: Korrekturen
der Staubereichs-Nummern (load_jam_overrides).
"""
if anlage is not None and eplan is not None:
raise ValueError("anlage und eplan schliessen sich aus")
@@ -924,7 +1012,7 @@ def build_context(document: TroDocument, source: str, start_id: str = "",
for area in document.jam_areas_or_derived():
bereiche.setdefault(area.tro, []).append(area)
geordnet, startgrund = flow_order(document.tros, start_id)
namen = jam_names(document, geordnet, hw)
namen = jam_names(document, geordnet, hw, jam_overrides)
return SclFile(
source=source,
created=created or datetime.now().strftime("%Y-%m-%d %H:%M"),
+22 -2
View File
@@ -43,6 +43,7 @@ Aufruf ueber bin/scl_skeleton.bat bzw. bin/scl_skeleton.sh.
from __future__ import annotations
import argparse
import configparser
import json
import sys
from pathlib import Path
@@ -60,7 +61,15 @@ from e_planning import EPLAN_SCHEMA, EplanDocument
from hw_import import HardwareDocument
from material_flow import env_dir
from model import ANLAGE_SCHEMA, SCHEMA, AnlageDocument, TroDocument
from scl_context import TODO, SclFile, SclName, SclTro, build_context
from scl_context import (
JAM_NAMES_SUFFIX,
TODO,
SclFile,
SclName,
SclTro,
build_context,
load_jam_overrides,
)
# Parametrisierte Aufrufstellen je FB (doc/SCL_Templates.md); Dateiname = fbType.
TEMPLATE_DIR = Path(__file__).resolve().parent / "scl_templates"
@@ -466,8 +475,17 @@ def main(argv: list[str] | None = None) -> int:
f"TRO-JSON zu '{document.source}'.", file=sys.stderr)
return 1
# Staubereichs-Nummern, im Dialog JAM_EDIT geaendert und mit
# tro_annotate --read-jams uebernommen (nur mit Anlagen-JSON wirksam)
jam_file = env_dir("SKEL_CFG", "cfg") / f"{Path(document.source).stem}{JAM_NAMES_SUFFIX}"
try:
jam_overrides = load_jam_overrides(jam_file) if anlage is not None else {}
except (OSError, ValueError, configparser.Error) as exc:
print(f"FEHLER: Staubereichs-Korrekturen nicht lesbar: {exc}", file=sys.stderr)
return 1
context = build_context(document, json_file.name, args.start, eplan=eplan,
anlage=anlage)
anlage=anlage, jam_overrides=jam_overrides)
mit, ohne = template_usage(context, templates)
if args.strict and ohne:
print(f"FEHLER: kein Template fuer {', '.join(ohne)} (--strict) - "
@@ -530,6 +548,8 @@ def main(argv: list[str] | None = None) -> int:
print(f"Eingesetzt = {len(known_names(context))} Namen aus der Anlagen-JSON")
if hw_file is not None:
print(f"Gegenpruefung = {len(unbekannt)} unbekannte Namen ({hw_file.name})")
if jam_overrides:
print(f"Staubereiche = {len(jam_overrides)} Korrekturen aus {jam_file}")
print(f"Offene Stellen = {offen} (Marker '{TODO}')")
print(f"SCL = {out_file}")
print("================================================================")
+256 -6
View File
@@ -85,6 +85,7 @@ from __future__ import annotations
import argparse
import collections
import configparser
import csv as csv_module
import fnmatch
import json
@@ -120,6 +121,17 @@ from tro_flow import (
resolve_ids_file,
)
from tro_ids import TroIds
from model import ANLAGE_SCHEMA, AnlageDocument, JamArea
from scl_context import (
JAM_NAMES_SUFFIX,
HwSource,
flow_order,
jam_key,
jam_label,
jam_numbers,
load_jam_overrides,
save_jam_overrides,
)
# ---------------------------------------------------------------------------
# Konstanten
@@ -131,12 +143,18 @@ BLOCK_PREFIX = "TRO_SYM_"
# Verbindungsblock. Der Name beginnt mit CONNECTION_, damit der Dispatcher
# SSG_BLOCKEDIT in dxfmakros ihn ueber "CONNECTION_*" erkennt.
CONNECTION_BLOCK = "CONNECTION_ARROW"
# Staubereich (--anlage). Der Name beginnt mit JAM_, damit SSG_BLOCKEDIT ihn
# ueber "JAM_*" an den Dialog JAM_EDIT (dxfmakros/Lisp/Jam_Edit.lsp) gibt.
JAM_BLOCK = "JAM_AREA"
# Anzeige eines Staubereichs ohne Foerderer (Nummer nicht ableitbar)
JAM_OFFEN = "Jam????_?"
# Zeichnungsgroessen und Restfehler-Grenze des Lagebezugs aus cfg/parameter.ini,
# Abschnitt [tro_annotate] (lib/parameter.py)
DEFAULT_MARKER = get_float("tro_annotate", "default_marker")
DEFAULT_TEXT = get_float("tro_annotate", "default_text")
DEFAULT_MAX_RESIDUAL = get_float("tro_annotate", "default_max_residual")
JAM_VERSATZ = get_float("tro_annotate", "jam_versatz")
def hex_to_rgb(value: str) -> tuple[int, int, int]:
@@ -335,6 +353,7 @@ def build_layers(doc, prefix: str) -> dict[str, str]:
("weiche", "#cc8800"),
("eingang", STATION_FARBE[BOUNDARY_ENTRY]),
("ausgang", STATION_FARBE[BOUNDARY_EXIT]),
("jam", "#7030a0"),
("legend", "#808080")):
name = f"{prefix}{key.upper()}"
names[key] = name
@@ -702,6 +721,150 @@ def draw_connections(
return drawn
def build_jam_symbol(doc, text: float) -> str:
"""
Blockdefinition fuer einen Staubereich: Rechteck, der Name mittig darin.
Attribute:
NAME sichtbar - Jam0054_3 (Foerderer + laufende Nummer)
CONVEYOR unsichtbar - Foerderer NNNN (Kreisel-/Vario-Motor), im Dialog
NUMBER unsichtbar - laufende Nummer k auf dem Foerderer, im Dialog
TRO unsichtbar - TRO, in den der Staubereich fuehrt (TRO17)
ENTRY unsichtbar - Eingang des TRO (1, bei 2Sep-Typen auch 2)
AREA unsichtbar - Kennung der TRO-JSON (J0039)
AUTO unsichtbar - abgeleitete Nummer NNNN_k; --read-jams schreibt
nur, was davon abweicht
TRO + ENTRY ist der stabile Schluessel (scl_context.jam_key).
"""
if JAM_BLOCK in doc.blocks:
return JAM_BLOCK
blk = doc.blocks.new(name=JAM_BLOCK)
w, h = text * 4.2, text * 1.2
blk.add_lwpolyline([(-w, -h), (w, -h), (w, h), (-w, h)], close=True,
dxfattribs={"color": 256})
name = blk.add_attdef("NAME", insert=(0, 0), height=text,
dxfattribs={"style": "Standard", "color": 256,
"text": JAM_OFFEN})
name.set_placement((0, 0), align=TextEntityAlignment.MIDDLE_CENTER)
offset = -h - text
for tag_name in ("CONVEYOR", "NUMBER", "TRO", "ENTRY", "AREA", "AUTO"):
blk.add_attdef(tag_name, insert=(-w, offset), height=text * 0.7,
dxfattribs={"style": "Standard", "color": 256, "text": "",
"invisible": 1})
offset -= text
return JAM_BLOCK
def _jam_position(area: JamArea, anlage: AnlageDocument) -> tuple[float, float] | None:
"""Lage eines Staubereichs (CSV-Koordinaten): vor seinem Separator.
Ausgangspunkt ist der Separator (sonst die TRO-Position); von dort
JAM_VERSATZ in Richtung des ersten Vorgaenger-TRO - gegen den Fluss, wo
die Traeger warten. Liegt der Vorgaenger naeher, nur die halbe Strecke.
"""
layout = anlage.layout
part = layout.part(area.separator) if area.separator else None
if part is not None and part.position is not None:
x, y = part.position[0], part.position[1]
elif area.position is not None:
x, y = area.position[0], area.position[1]
else:
return None
for vor in area.predecessors:
tro = layout.by_id(vor)
if tro is None or not tro.position:
continue
vx, vy = tro.position[0] - x, tro.position[1] - y
dist = math.hypot(vx, vy)
if dist > 1e-6:
schritt = min(JAM_VERSATZ, dist / 2)
return x + vx / dist * schritt, y + vy / dist * schritt
return x, y
def place_jams(
msp, doc, anlage: AnlageDocument, overrides: dict[str, tuple[str, int]],
transform: Transform, layers: dict[str, str], text: float, warnings: list[str],
) -> int:
"""
Je Staubereich der Anlagen-JSON einen JAM_AREA-Block einfuegen.
Die Nummern sind dieselben wie in der SCL (scl_context.jam_numbers, dann
die Korrekturen aus <csv>_jam_names.ini). Ein Staubereich ohne Foerderer
zeigt Jam????_? - im Dialog nachtragen.
"""
layout = anlage.layout
geordnet, _grund = flow_order(layout.tros)
automatisch = jam_numbers(layout, geordnet, HwSource(anlage=anlage))
block = build_jam_symbol(doc, text)
gezeichnet = 0
for area in layout.jam_areas_or_derived():
lage = _jam_position(area, anlage)
if lage is None:
warnings.append(f"Staubereich {area.jam_id} ({area.tro}): keine Lage - "
f"nicht gezeichnet")
continue
auto = automatisch.get(area.jam_id)
nr, k = overrides.get(jam_key(area), auto or ("", 0))
ref = msp.add_blockref(block, transform.to_dxf(*lage),
dxfattribs={"layer": layers["jam"]})
ref.add_auto_attribs({
"NAME": jam_label(nr, k) if nr else JAM_OFFEN,
"CONVEYOR": nr,
"NUMBER": str(k) if nr else "",
"TRO": area.tro,
"ENTRY": str(area.entry),
"AREA": area.jam_id,
"AUTO": f"{auto[0]}_{auto[1]}" if auto else "",
})
tag(ref, area.tro)
gezeichnet += 1
return gezeichnet
def read_jams(dxf_file: Path, ini_file: Path) -> tuple[dict[str, tuple[str, int]], list[str]]:
"""Im Dialog JAM_EDIT geaenderte Staubereichs-Nummern uebernehmen.
Liest die JAM_AREA-Bloecke der beschrifteten Zeichnung. Weicht
CONVEYOR_NUMBER von AUTO ab, kommt der Staubereich in die Korrekturdatei
``<csv>_jam_names.ini``; stimmt er (wieder) mit AUTO ueberein, faellt sein
Eintrag heraus. Eintraege zu Staubereichen, die nicht in der Zeichnung
stehen, bleiben. Rueckgabe: (Korrekturen danach, Meldungen).
"""
meldungen: list[str] = []
overrides = load_jam_overrides(ini_file)
doc = ezdxf.readfile(str(dxf_file))
namen: dict[str, str] = {}
for ref in doc.modelspace().query(f'INSERT[name=="{JAM_BLOCK}"]'):
a = {att.dxf.tag.upper(): (att.dxf.text or "").strip() for att in ref.attribs}
key = f"{a.get('TRO', '')}.{a.get('ENTRY', '')}"
nr, k = a.get("CONVEYOR", ""), a.get("NUMBER", "")
if not a.get("TRO") or not a.get("ENTRY"):
meldungen.append(f"Staubereich ohne TRO/ENTRY ({a.get('NAME', '?')}) - uebersprungen")
continue
if not nr and not k:
continue
if not (nr.isdigit() and len(nr) == 4 and k.isdigit() and int(k) > 0):
meldungen.append(f"{key} ({a.get('AREA', '')}): Foerderer '{nr}' / Nummer '{k}' "
f"ungueltig (erwartet NNNN und k >= 1) - uebersprungen")
continue
wert = f"{nr}_{int(k)}"
if wert in namen.values():
andere = next(s for s, w in namen.items() if w == wert)
meldungen.append(f"{key}: Jam{wert} ist schon {andere} - doppelt vergeben")
namen[key] = wert
alt = overrides.get(key)
if wert == a.get("AUTO", ""):
if alt is not None:
del overrides[key]
meldungen.append(f"{key}: wieder abgeleitet (Jam{wert}) - Korrektur entfernt")
elif alt != (nr, int(k)):
overrides[key] = (nr, int(k))
meldungen.append(f"{key}: Jam{wert} (abgeleitet: Jam{a.get('AUTO') or '-'})")
save_jam_overrides(ini_file, overrides)
return overrides, meldungen
def draw_legend(
msp, analysis: Analysis, placed: dict[str, tuple[float, float]],
layers: dict[str, str], marker: float, text: float, source: str,
@@ -790,6 +953,9 @@ def report(
print(f"TROs = {len(analysis.tros)}, beschriftet: {counts['marker']}")
print(f"Verbindungen = {counts['flow']}"
+ (" (Block CONNECTION_ARROW mit FROM_UID/TO_UID)" if counts["flow"] else ""))
if counts.get("jams"):
print(f"Staubereiche = {counts['jams']} (Block {JAM_BLOCK}, Doppelklick -> "
f"JAM_EDIT; uebernehmen mit --read-jams)")
if counts.get("first_id"):
print(f"Interne IDs ab = {format_internal_id(counts['first_id'])} "
f"(ID-Raum von SSG_LIB, siehe IDSCHECK)")
@@ -820,6 +986,9 @@ def parse_args(argv: list[str] | None = None) -> argparse.Namespace:
epilog="Beispiele:\n"
" tro_annotate.bat --file mubea.csv --dxf 500573_60_1.dxf --check\n"
" tro_annotate.bat --file mubea.csv --dxf 500573_60_1.dxf --flow --fb\n"
" tro_annotate.bat --file dematic_merge.csv --dxf dematic_merge..dxf --flow "
"--anlage dematic_merge_anlage.json\n"
" tro_annotate.bat --file dematic_merge.csv --read-jams\n"
"\n"
"Exit-Codes:\n"
" 0 ok\n"
@@ -855,6 +1024,17 @@ def parse_args(argv: list[str] | None = None) -> argparse.Namespace:
parser.add_argument("--re-register", action="store_true",
help="Lagebezug neu ermitteln, auch wenn in %%SKEL_CFG%% "
"einer abgelegt ist.")
parser.add_argument("--anlage", metavar="NAME",
help="Anlagen-JSON (<csv>_anlage.json aus merge_hw_layout, in "
"%%SKEL_RESULTS%% oder ein Pfad): zusaetzlich die "
"Staubereiche als JAM_AREA-Bloecke einzeichnen "
"(Jam<NNNN>_<k>, Doppelklick -> Dialog JAM_EDIT).")
parser.add_argument("--read-jams", nargs="?", const="", default=None, metavar="DXF",
help="Im Dialog JAM_EDIT geaenderte Staubereichs-Nummern aus der "
"beschrifteten Zeichnung uebernehmen (Standard: "
"<csv>_annotated.dxf in %%SKEL_RESULTS%%) und nach "
"%%SKEL_CFG%%\\<csv>_jam_names.ini schreiben, dann beenden. "
"scl_skeleton liest die Datei.")
parser.add_argument("--check", action="store_true",
help="Nur pruefen und berichten, keine Datei schreiben.")
parser.add_argument("--emit-lisp", action="store_true",
@@ -863,6 +1043,38 @@ def parse_args(argv: list[str] | None = None) -> argparse.Namespace:
return parser.parse_args(argv)
def _main_read_jams(args, csv_file: Path) -> int:
"""--read-jams: Nummern aus der beschrifteten Zeichnung -> <csv>_jam_names.ini."""
results = env_dir("SKEL_RESULTS", "results")
name = args.read_jams or args.out or f"{csv_file.stem}_annotated.dxf"
dxf_file = Path(name)
if not dxf_file.is_absolute() and dxf_file.parent == Path("."):
dxf_file = results / name
if not dxf_file.is_file():
print(f"FEHLER: beschriftete Zeichnung nicht gefunden: {dxf_file}", file=sys.stderr)
print(" In BricsCAD als DXF speichern (Dateiname wie beim Beschriften).",
file=sys.stderr)
return 1
ini_file = env_dir("SKEL_CFG", "cfg") / f"{csv_file.stem}{JAM_NAMES_SUFFIX}"
try:
overrides, meldungen = read_jams(dxf_file, ini_file)
except (OSError, ValueError, configparser.Error, ezdxf.DXFError) as exc:
print(f"FEHLER: {exc}", file=sys.stderr)
return 1
print("")
print("================================================================")
print("STAUBEREICHE UEBERNEHMEN")
print("================================================================")
print(f"Zeichnung = {dxf_file}")
print(f"Korrekturen = {len(overrides)} -> {ini_file if overrides else '- (keine, Datei entfernt)'}")
for m in meldungen:
print(f" {m}")
print("Danach: scl_skeleton mit der Anlagen-JSON neu laufen lassen.")
print("================================================================")
print("")
return 0
def main(argv: list[str] | None = None) -> int:
args = parse_args(argv)
warnings: list[str] = []
@@ -875,16 +1087,19 @@ def main(argv: list[str] | None = None) -> int:
"SSG_LIB-Module (dxfmakros/Lisp), damit TRO_EDIT sie findet.")
return 0
if not args.dxf:
print("FEHLER: --dxf fehlt (nur --emit-lisp geht ohne Zeichnung).",
file=sys.stderr)
return 1
csv_file = resolve_input(args.file)
if csv_file.suffix.lower() != ".csv":
print(f"FEHLER: --file erwartet eine CSV-Datei, keine '{csv_file.suffix}'-Datei: "
f"{csv_file}", file=sys.stderr)
return 1
if args.read_jams is not None:
return _main_read_jams(args, csv_file)
if not args.dxf:
print("FEHLER: --dxf fehlt (nur --emit-lisp und --read-jams gehen ohne "
"Zeichnung).", file=sys.stderr)
return 1
if not csv_file.is_file():
print(f"FEHLER: CSV nicht gefunden: {csv_file}", file=sys.stderr)
return 1
@@ -904,6 +1119,29 @@ def main(argv: list[str] | None = None) -> int:
cfg_dir = env_dir("SKEL_CFG", "cfg")
results.mkdir(parents=True, exist_ok=True)
# Anlagen-JSON fuer die Staubereiche (optional)
anlage = None
jam_overrides: dict[str, tuple[str, int]] = {}
if args.anlage:
anlage_file = Path(args.anlage)
if not anlage_file.is_absolute() and anlage_file.parent == Path("."):
anlage_file = results / args.anlage
try:
text = anlage_file.read_text(encoding="utf-8")
if json.loads(text).get("schema") != ANLAGE_SCHEMA:
raise ValueError(f"kein Schema '{ANLAGE_SCHEMA}' - erwartet wird die "
f"Anlagen-JSON aus merge_hw_layout")
anlage = AnlageDocument.from_json(text)
jam_overrides = load_jam_overrides(
cfg_dir / f"{csv_file.stem}{JAM_NAMES_SUFFIX}")
except (OSError, ValueError, configparser.Error) as exc:
print(f"FEHLER: Anlagen-JSON {anlage_file} nicht lesbar: {exc}", file=sys.stderr)
return 1
if Path(anlage.layout.source).name != csv_file.name:
print(f"FEHLER: {anlage_file.name} gehoert zu '{anlage.layout.source}', "
f"nicht zu '{csv_file.name}'.", file=sys.stderr)
return 1
# TROs aus dem CSV
read_warnings: list[str] = []
try:
@@ -955,7 +1193,16 @@ def main(argv: list[str] | None = None) -> int:
return 4
counts = {"marker": 0, "flow": 0, "removed": 0, "layers": 0, "blocks": 0,
"first_id": 0}
"first_id": 0, "jams": 0}
if anlage is not None:
aus_csv = {t.tro_id for t in analysis.tros}
aus_json = {t.tro_id for t in anlage.layout.tros}
if aus_csv != aus_json:
warnings.append(
f"TROs der Zeichnung und der Anlagen-JSON weichen ab (nur CSV: "
f"{', '.join(sorted(aus_csv - aus_json)) or '-'}; nur JSON: "
f"{', '.join(sorted(aus_json - aus_csv)) or '-'}) - tro_flow --json und "
f"merge_hw_layout neu laufen lassen")
if args.check:
counts["marker"] = sum(1 for t in analysis.tros if t.position)
@@ -986,6 +1233,9 @@ def main(argv: list[str] | None = None) -> int:
msp, doc, analysis, {**placed, **stationen}, internal, layers,
args.marker_size, args.text_height, next_id=next_id + len(placed),
)
if anlage is not None:
counts["jams"] = place_jams(msp, doc, anlage, jam_overrides, transform, layers,
args.text_height, warnings)
if args.legend:
draw_legend(msp, analysis, placed, layers, args.marker_size,
args.text_height, csv_file.name)