hw_import + merge_hw_layout: Hardwareplanung einlesen und mit dem Layout zusammenfuehren

Stufen 2 und 3 aus doc/Konzept_Layout_Hardware_SCL.md:

- lib/hw_import.py: TIA-Import (*_TIA.xlsx, ohne openpyxl gelesen) +
  Positionsdatei (*_positionsconv.json) -> <projekt>_hw.json: je BMK
  Adresse, Index-Konstante, VERW, Station, Lage, Foerderer (K0102, #106 ->
  K0106); Antriebe MA/FC/GF; Gegenpruefung beider Quellen.
- lib/merge_hw_layout.py: <csv>_tro.json + <projekt>_hw.json ->
  <csv>_anlage.json (model.AnlageDocument): bindings je TRO-ID + Slot +
  Rolle mit Konstante und Adresse, Foerderer per Mehrheit (Mubea: K0102 =
  Kreisel3, K0104 = Kreisel2, K0105 = Kreisel1), Handkorrekturen
  cfg/hw_overrides.ini. Zuordnungslogik aus e_planning.py hierher
  verschoben; e_planning re-exportiert sie, Ergebnis unveraendert.
- TRO-JSON traegt optional `parts` (Lage/Huellbox, tro_flow --json),
  PinStore bekommt Slots line<n>.
- Gespeicherter Lagebezug mit SHA-256 der Positionsdatei; Unterordner in
  Eingabenamen relativ zu SKEL_DATA.
- Doku: Python_Scripts.md 5d/5e, Konzept, E-Planung_Bruecke (Abgleich
  mubea_layout.dxf / CSV / E-Planung).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Samer Ayadi
2026-10-15 16:16:10 +02:00
co-authored by Claude Opus 5.5
parent c4122b4497
commit df24cf5adb
21 changed files with 3226 additions and 442 deletions
+27 -2
View File
@@ -50,6 +50,7 @@ neue Ermittlung.
from __future__ import annotations
import hashlib
import json
import math
from collections import Counter
@@ -69,6 +70,7 @@ __all__ = [
"load_transform",
"save_transform",
"registration_path",
"file_fingerprint",
]
# Rollen fuer den Lagebezug: CSV-Objektart -> Blocknamen im DXF, als Muster
@@ -123,6 +125,10 @@ class Transform(BaseModel):
csv_file: str = Field(default="")
dxf_file: str = Field(default="")
created: str = Field(default="")
# Pruefsumme der Zeichnung/E-Datei beim Ermitteln ("" = unbekannt). Der
# Schluessel enthaelt nur Dateinamen; ein neuer Stand gleichen Namens wird
# daran erkannt (siehe load_transform).
fingerprint: str = Field(default="")
# -- Anwenden ----------------------------------------------------------
@@ -329,8 +335,25 @@ def _key(csv_file: str, dxf_file: str) -> str:
return f"{Path(csv_file).name}|{Path(dxf_file).name}"
def load_transform(cfg_dir: Path, csv_file: str, dxf_file: str) -> Transform | None:
"""Geprueften Lagebezug aus der Konfiguration lesen."""
def file_fingerprint(path: Path) -> str:
"""SHA-256 einer Datei (Hex) - Kennung des Planungsstands."""
digest = hashlib.sha256()
with path.open("rb") as handle:
for chunk in iter(lambda: handle.read(1 << 20), b""):
digest.update(chunk)
return digest.hexdigest()
def load_transform(cfg_dir: Path, csv_file: str, dxf_file: str,
fingerprint: str = "") -> Transform | None:
"""Geprueften Lagebezug aus der Konfiguration lesen.
Mit ``fingerprint``: nur verwenden, wenn der abgelegte Bezug fuer genau
diesen Dateistand ermittelt wurde. Ein Eintrag ohne Pruefsumme (aeltere
Ablage) oder mit anderer gilt als veraltet -> None, der Aufrufer ermittelt
neu. Ohne ``fingerprint`` (z. B. tro_annotate) wie bisher nur ueber den
Dateinamen.
"""
path = registration_path(cfg_dir)
if not path.is_file():
return None
@@ -341,6 +364,8 @@ def load_transform(cfg_dir: Path, csv_file: str, dxf_file: str) -> Transform | N
entry = data.get(_key(csv_file, dxf_file))
if not entry:
return None
if fingerprint and entry.get("fingerprint", "") != fingerprint:
return None
transform = Transform.model_validate(entry)
transform.source = "cfg"
return transform