validate_anlage, stabiler TRO-Schluessel, Verbindungen aus BricsCAD, Zeichnung aus der Anlagen-JSON

- validate_anlage.py (+ bin-Wrapper): Pruefung vor der SCL fuer beide Wege
  (Struktur, Zuordnungen, Aktualitaet der JSON, Korrekturen ohne Ziel);
  scl_skeleton ruft sie auf (Warnung, --strict bricht ab)
- DerivedTro.key: stabiler Schluessel (ID der ID-Datei); hw_overrides.ini,
  <csv>_jam_names.ini und tro_overrides.ini greifen ueber ID oder Schluessel,
  eine Umbenennung in TRO_EDIT laesst sie bestehen; JAM_AREA traegt KEY
- --read-tros liest die CONNECTION_ARROW-Bloecke mit -> [<csv>.edit_connections];
  tro_flow.apply_connection_edits wendet sie nach link_tros an
- tro_annotate --anlage zeichnet Marker/Verbindungen aus der Anlagen-JSON
- INI-Korrekturen auch mit BOM lesbar (utf-8-sig)
- Ablauf CSV/DXF+TIA -> Anlagen-JSON -> (BricsCAD) -> SCL in doc/Python_Scripts.md

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-27 14:32:10 +01:00
parent 6c8c235287
commit 1ac17b95e2
13 changed files with 1100 additions and 48 deletions
+55 -4
View File
@@ -56,6 +56,11 @@ anders als die laufenden Nummern, an denen die alten Abschnitte veraltet sind.
[<csv>.edit_add.<ID>] neuer TRO (TRO_INSERT), Schluessel wie oben
[<csv>.edit_connections] Verbindungen, im Plan (CONNECTION_*) geaendert
c01 = + TRO05 -> TRO07 | transfer dazu (Art optional, Standard transfer)
c02 = - TRO05 -> TRO01 weg
Enden sind die stabilen Schluessel der TROs
Stehen Slots im Abschnitt, gelten sie vollstaendig (fehlende Slots des Typs
bleiben leer); stehen keine, bleiben die Bauteile wie abgeleitet. Die Lage
hinter der TeileId macht den Eintrag exportfest: passt die TeileId im neuen
@@ -220,6 +225,33 @@ class TroEdit:
or self.slots is not None or self.lines is not None)
@dataclass
class ConnectionEdit:
"""Eine im Plan hinzugefuegte (add) oder entfernte Verbindung zwischen
zwei TROs, Enden als stabile Schluessel (DerivedTro.key)."""
add: bool
src: str
dst: str
kind: str = ""
def render(self) -> str:
text = f"{'+' if self.add else '-'} {self.src} -> {self.dst}"
return f"{text} | {self.kind}" if self.add and self.kind else text
@classmethod
def parse(cls, raw: str) -> ConnectionEdit:
text = raw.strip()
if not text or text[0] not in "+-":
raise ValueError(f"'{raw}': erwartet '+ <von> -> <nach> [| Art]' oder "
f"'- <von> -> <nach>'")
pfeil, _, kind = text[1:].partition("|")
src, sep, dst = (t.strip() for t in pfeil.partition("->"))
if not sep or not src or not dst:
raise ValueError(f"'{raw}': erwartet '<von> -> <nach>'")
return cls(add=text[0] == "+", src=src, dst=dst, kind=kind.strip())
@dataclass
class Overrides:
"""Alle Korrekturen zu einer CSV-Datei.
@@ -237,6 +269,8 @@ class Overrides:
candidates: list[TroCandidate] = field(default_factory=list)
# Handkorrekturen aus BricsCAD in Dateireihenfolge (tro_flow.apply_edits)
edits: list[TroEdit] = field(default_factory=list)
# Verbindungen aus BricsCAD (tro_flow.apply_connection_edits)
connection_edits: list[ConnectionEdit] = field(default_factory=list)
# Protokoll der tatsaechlich angewandten Korrekturen (fuer die Doku)
applied: list[str] = field(default_factory=list)
warnings: list[str] = field(default_factory=list)
@@ -278,7 +312,8 @@ def load_overrides(csv_name: str, cfg_dir: Path | None = None) -> Overrides:
parser = configparser.ConfigParser(inline_comment_prefixes=(";",))
# Schluessel sind Kleinschreibung, Sektionsnamen bleiben wie geschrieben.
parser.read(path, encoding="utf-8")
# utf-8-sig: von Hand (Editor, PowerShell) gespeichert oft mit BOM
parser.read(path, encoding="utf-8-sig")
prefix = csv_name.lower()
for section in parser.sections():
@@ -297,6 +332,14 @@ def load_overrides(csv_name: str, cfg_dir: Path | None = None) -> Overrides:
Connection(src=src, dst=dst, kind=rest.strip())
)
continue
# [<csv>.edit_connections] - Verbindungen aus BricsCAD
if section.lower() == f"{prefix}.edit_connections":
for key, raw in parser[section].items():
try:
result.connection_edits.append(ConnectionEdit.parse(raw))
except ValueError as exc:
result.warnings.append(f"[{section}] {key}: {exc}")
continue
parts = section.split(".")
# <csv>.<art>.<name> - der CSV-Name kann selbst einen Punkt enthalten
@@ -475,8 +518,10 @@ def render_edit(csv_name: str, edit: TroEdit) -> str:
return "\n".join(lines)
def save_edits(csv_name: str, edits: list[TroEdit], cfg_dir: Path | None = None) -> Path:
"""Alle edit-/edit_add-Abschnitte zu `csv_name` durch `edits` ersetzen.
def save_edits(csv_name: str, edits: list[TroEdit], cfg_dir: Path | None = None,
connections: list[ConnectionEdit] | None = None) -> Path:
"""Alle edit-/edit_add-Abschnitte und [<csv>.edit_connections] zu
`csv_name` durch `edits` bzw. `connections` ersetzen.
Der Rest der Datei (von Hand gepflegt, mit Kommentaren) bleibt Zeile fuer
Zeile erhalten; der neue Block steht am Ende. Kommentarzeilen direkt vor
@@ -488,6 +533,8 @@ def save_edits(csv_name: str, edits: list[TroEdit], cfg_dir: Path | None = None)
head = _EDIT_HEAD.format(csv=csv_name).lower()
def ist_edit(section: str) -> bool:
if section.lower() == f"{prefix}.edit_connections":
return True
parts = section.split(".")
return (len(parts) >= 3 and parts[-2].lower() in ("edit", "edit_add")
and ".".join(parts[:-2]).lower() == prefix)
@@ -519,13 +566,17 @@ def save_edits(csv_name: str, edits: list[TroEdit], cfg_dir: Path | None = None)
while keep and not keep[-1].strip():
keep.pop()
if edits:
if edits or connections:
if keep:
keep += ["", ""]
keep.append(_EDIT_HEAD.format(csv=csv_name)
+ f" - tro_annotate --read-tros, {datetime.now():%Y-%m-%d}")
for edit in edits:
keep += [render_edit(csv_name, edit), ""]
if connections:
keep.append(f"[{csv_name}.edit_connections]")
keep += [f"c{n:02d} = {c.render()}" for n, c in enumerate(connections, start=1)]
keep.append("")
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text("\n".join(keep).rstrip("\n") + "\n", encoding="utf-8")
return path