model.py: gemeinsames TRO-Modell; Hauptweg CSV -> tro_flow --json -> scl_skeleton
- lib/model.py (neu, pydantic): TroBase -> DerivedTro, Connection, OpenPoint, Analysis (aus tro_flow verschoben), TroDocument = TRO-JSON (Schema sps_skel-tro-v2). items/fbType werden immer ueber den Typ aus tro_catalog gelesen, nie gespeichert; abweichende Werte in einer JSON sind ein Fehler. JSON-Schluessel wie bisher (id, type, fbType ...), extra="forbid". - tro_flow: Tro/Analysis aus model.py; neuer Schalter --json schreibt <name>_tro.json (TroDocument) - scl_skeleton: liest die JSON ueber TroDocument statt per Dict-Zugriff, Bauteile ueber TroItem-Konstanten; ungueltige JSON -> Exit 1 ohne SCL, alte tro_extract-JSON (v1) mit Hinweis auf tro_flow --json - tro_annotate: nur noch Ansicht; derselbe analyse_tros-Aufruf wie tro_flow (inkl. sepliste-Gegenprobe), damit Zeichnung und JSON uebereinstimmen - tro_extract: geparkt (unveraendert) - tests/test_model.py + conftest.py, pytest in requirements.txt aktiviert - Doku: Python_Scripts.md (§6b model.py), Commandlines.md, CLAUDE.md, Aufrufkommentare in bin/ (scl_skeleton.bat zeigte tro_flow-Aufrufe) Geprueft: alle tro_flow/tro_annotate-Ausgaben (8 CSVs) bis auf Zeitstempel identisch zum vorherigen Stand; scl_skeleton alt/neu auf derselben JSON byte-identisch (export, mubea, HundM05); 10 Tests gruen. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
90cafec99c
commit
97a0db6707
+106
-75
@@ -7,11 +7,14 @@ scl_skeleton.py - FB_Main-Geruest als SCL aus der TRO-Liste erzeugen.
|
||||
Letzter Schritt der Kette:
|
||||
|
||||
material_flow.py Objekte -> Materialfluss
|
||||
tro_flow.py Fluss -> TROs
|
||||
tro_annotate.py TROs -> Marker in der Zeichnung
|
||||
tro_extract.py Zeichnung -> JSON
|
||||
tro_flow.py Fluss -> TROs -> <name>_tro.json (--json)
|
||||
scl_skeleton.py JSON -> FB_Main.scl <- hier
|
||||
|
||||
Die TRO-JSON wird ueber das gemeinsame Modell lib/model.py (TroDocument)
|
||||
eingelesen und dabei geprueft: ein falscher oder umbenannter Schluessel bricht
|
||||
mit einer Fehlermeldung ab, statt still einen Standardwert zu nehmen. Bauteile
|
||||
(items) und FB-Typ kommen aus tro_catalog, ueber den TRO-Typ.
|
||||
|
||||
Erzeugt wird ein **Geruest**, kein fertiges Programm: je TRO eine REGION mit
|
||||
Instanzaufruf und allen Parameterzeilen in der richtigen Reihenfolge, so wie es
|
||||
die realen Aufrufstellen in doc/TRO_Katalog/scl_templates/*.scl zeigen.
|
||||
@@ -41,7 +44,11 @@ from collections import deque
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
|
||||
from pydantic import ValidationError
|
||||
|
||||
from material_flow import env_dir
|
||||
from model import SCHEMA, TroBase, TroDocument
|
||||
from tro_catalog import TroItem
|
||||
|
||||
TODO = "TODO(E-Planung)"
|
||||
|
||||
@@ -55,11 +62,12 @@ DEFAULT_SEP_TIMES = (
|
||||
)
|
||||
|
||||
|
||||
def load_tros(path: Path) -> dict:
|
||||
return json.loads(path.read_text(encoding="utf-8"))
|
||||
def load_document(path: Path) -> TroDocument:
|
||||
"""TRO-JSON einlesen und gegen das Modell pruefen."""
|
||||
return TroDocument.from_json(path.read_text(encoding="utf-8"))
|
||||
|
||||
|
||||
def flow_order(tros: list[dict], start_id: str = "") -> tuple[list[dict], str]:
|
||||
def flow_order(tros: list[TroBase], start_id: str = "") -> tuple[list[TroBase], str]:
|
||||
"""TROs in Fliessrichtung sortieren.
|
||||
|
||||
Startpunkt ist ein TRO ohne Vorgaenger - also die Stelle, an der Material
|
||||
@@ -70,14 +78,14 @@ def flow_order(tros: list[dict], start_id: str = "") -> tuple[list[dict], str]:
|
||||
--start ueberschreiben). Nicht erreichte TROs haengt die Funktion hinten
|
||||
an, damit nie einer verlorengeht.
|
||||
"""
|
||||
nach_id = {t["id"]: t for t in tros}
|
||||
ohne_vorgaenger = sorted(t["id"] for t in tros if not t.get("predecessors"))
|
||||
nach_id = {t.tro_id: t for t in tros}
|
||||
ohne_vorgaenger = sorted(t.tro_id for t in tros if not t.predecessors)
|
||||
# Materialeingang ueber eine Station (SYSn von tro_flow.py): der TRO, den
|
||||
# sie speist, ist der natuerliche Anfang - auch wenn er zusaetzlich einen
|
||||
# TRO-Vorgaenger hat (Kreisel-Umlauf).
|
||||
eingaenge = sorted({
|
||||
t["id"] for t in tros
|
||||
for vor in t.get("predecessors", []) if vor not in nach_id
|
||||
t.tro_id for t in tros
|
||||
for vor in t.predecessors if vor not in nach_id
|
||||
})
|
||||
|
||||
if start_id and start_id in nach_id:
|
||||
@@ -86,7 +94,7 @@ def flow_order(tros: list[dict], start_id: str = "") -> tuple[list[dict], str]:
|
||||
start, grund = ohne_vorgaenger, "TRO ohne Vorgaenger (Materialeingang)"
|
||||
elif eingaenge:
|
||||
stationen = sorted({
|
||||
vor for i in eingaenge for vor in nach_id[i]["predecessors"]
|
||||
vor for i in eingaenge for vor in nach_id[i].predecessors
|
||||
if vor not in nach_id
|
||||
})
|
||||
start = eingaenge
|
||||
@@ -103,7 +111,7 @@ def flow_order(tros: list[dict], start_id: str = "") -> tuple[list[dict], str]:
|
||||
if tro_id in gesehen:
|
||||
continue
|
||||
gesehen.append(tro_id)
|
||||
for nxt in nach_id.get(tro_id, {}).get("successors", []):
|
||||
for nxt in nach_id[tro_id].successors if tro_id in nach_id else []:
|
||||
# SYSn (Materialausgang) ist kein TRO - keine REGION, keine Instanz.
|
||||
if nxt in nach_id and nxt not in gesehen:
|
||||
queue.append(nxt)
|
||||
@@ -113,7 +121,7 @@ def flow_order(tros: list[dict], start_id: str = "") -> tuple[list[dict], str]:
|
||||
return [nach_id[i] for i in gesehen], grund
|
||||
|
||||
|
||||
def reachable_via(start: str, first: str, tros: dict[str, dict]) -> list[str]:
|
||||
def reachable_via(start: str, first: str, tros: dict[str, TroBase]) -> list[str]:
|
||||
"""Welche TROs sind ueber diesen Abgang erreichbar?
|
||||
|
||||
Das ist die Zielliste eines Weichenabgangs: von `first` aus weiterlaufen,
|
||||
@@ -127,25 +135,26 @@ def reachable_via(start: str, first: str, tros: dict[str, dict]) -> list[str]:
|
||||
if node in gesehen or node == start:
|
||||
continue
|
||||
gesehen.add(node)
|
||||
queue.extend(tros.get(node, {}).get("successors", []))
|
||||
if node in tros:
|
||||
queue.extend(tros[node].successors)
|
||||
return sorted(gesehen)
|
||||
|
||||
|
||||
def nachbar(node: str, alle: dict[str, dict]) -> str:
|
||||
def nachbar(node: str, alle: dict[str, TroBase]) -> str:
|
||||
"""Nachbar fuer Kommentare; SYSn als Station kenntlich machen."""
|
||||
return node if node in alle else f"{node} (Station, kein TRO)"
|
||||
|
||||
|
||||
def region_title(tro: dict) -> str:
|
||||
seps = ", ".join(tro.get("separators", []))
|
||||
def region_title(tro: TroBase) -> str:
|
||||
seps = ", ".join(tro.separators)
|
||||
teile = f" Sep {seps}" if seps else ""
|
||||
return f"{tro['id']} ({tro['type']}){teile}"
|
||||
return f"{tro.tro_id} ({tro.type_name}){teile}"
|
||||
|
||||
|
||||
def emit_separators(add, tro: dict, anzahl: int, hat_scanner: bool) -> None:
|
||||
seps = tro.get("separators", [])
|
||||
def emit_separators(add, tro: TroBase, anzahl: int, hat_scanner: bool) -> None:
|
||||
seps = tro.separators
|
||||
for i in range(1, anzahl + 1):
|
||||
pfad = f"#{tro['id']}.stInSeparator{i}"
|
||||
pfad = f"#{tro.tro_id}.stInSeparator{i}"
|
||||
layout = seps[i - 1] if i <= len(seps) else "?"
|
||||
add(f" // Separator {i} = Bauteil {layout} aus dem Layout")
|
||||
add(f' {pfad}.Settings.nSeparatorNo := "cSep_____"; // {TODO}')
|
||||
@@ -162,10 +171,10 @@ def emit_separators(add, tro: dict, anzahl: int, hat_scanner: bool) -> None:
|
||||
add("")
|
||||
|
||||
|
||||
def emit_switches(add, tro: dict, anzahl: int, alle: dict[str, dict]) -> None:
|
||||
abgaenge = tro.get("successors", [])
|
||||
def emit_switches(add, tro: TroBase, anzahl: int, alle: dict[str, TroBase]) -> None:
|
||||
abgaenge = tro.successors
|
||||
for j in range(1, anzahl + 1):
|
||||
pfad = f"#{tro['id']}.stInSwitch{j}"
|
||||
pfad = f"#{tro.tro_id}.stInSwitch{j}"
|
||||
add(f' {pfad}.Settings.nSwitchNo := "cSwi_____"; // {TODO}')
|
||||
add(f" {pfad}.Settings.xPluggedExSen3 := FALSE;")
|
||||
add(f" {pfad}.Settings.xPluggedExSen4 := FALSE;")
|
||||
@@ -185,13 +194,13 @@ def emit_switches(add, tro: dict, anzahl: int, alle: dict[str, dict]) -> None:
|
||||
add(" // Zielliste je Abgang (aus dem TRO-Graphen berechnet) -")
|
||||
add(" // Grundlage fuer die Zuordnung Ziel -> Richtung in FC_Direction:")
|
||||
for abgang in abgaenge:
|
||||
ziele = reachable_via(tro["id"], abgang, alle)
|
||||
ziele = reachable_via(tro.tro_id, abgang, alle)
|
||||
add(f" // ueber {abgang}: {', '.join(ziele) if ziele else '-'}")
|
||||
add("")
|
||||
|
||||
|
||||
def emit_vario(add, tro: dict) -> None:
|
||||
pfad = f"#{tro['id']}.stInVario"
|
||||
def emit_vario(add, tro: TroBase) -> None:
|
||||
pfad = f"#{tro.tro_id}.stInVario"
|
||||
add(f" {pfad}.Settings.xContinousEmptying := FALSE;")
|
||||
add(f" {pfad}.Settings.tWaitForJog := T#300ms; // {TODO}: pruefen")
|
||||
add(f" {pfad}.Settings.tConvEmpty := T#45000ms; // {TODO}: pruefen")
|
||||
@@ -208,10 +217,10 @@ def emit_vario(add, tro: dict) -> None:
|
||||
add("")
|
||||
|
||||
|
||||
def emit_barcode(add, tro: dict, anzahl: int) -> None:
|
||||
def emit_barcode(add, tro: TroBase, anzahl: int) -> None:
|
||||
for i in range(1, anzahl + 1):
|
||||
suffix = "" if anzahl == 1 else str(i)
|
||||
pfad = f"#{tro['id']}.stInBarcodeReader{suffix}"
|
||||
pfad = f"#{tro.tro_id}.stInBarcodeReader{suffix}"
|
||||
add(f" {pfad}.Settings.xActivateCheckASCI := TRUE;")
|
||||
add(f" {pfad}.Settings.tTimeout := T#3000ms;")
|
||||
add(f' // {TODO}: Cognex-Interface verdrahten '
|
||||
@@ -219,21 +228,21 @@ def emit_barcode(add, tro: dict, anzahl: int) -> None:
|
||||
add("")
|
||||
|
||||
|
||||
def emit_call(add, tro: dict, items: dict[str, int], alle: dict[str, dict]) -> None:
|
||||
def emit_call(add, tro: TroBase, items: dict[str, int], alle: dict[str, TroBase]) -> None:
|
||||
"""Aufrufstelle schreiben.
|
||||
|
||||
Die Zeilen werden als (Code, Kommentar) gefuehrt und das Komma erst beim
|
||||
Zusammensetzen gesetzt - sonst landet es hinter dem "//" und ist Teil des
|
||||
Kommentars, womit die Parameterliste nicht mehr uebersetzt.
|
||||
"""
|
||||
tro_id = tro["id"]
|
||||
tro_id = tro.tro_id
|
||||
zeilen: list[tuple[str, str]] = [
|
||||
('nInMainTroNo := "cMainTro____"', TODO),
|
||||
('xInSftyOk := "DB_InterfaceSafety".stFromSafety.EStopMain.Q', TODO),
|
||||
("xInAllRdyToStart := TRUE", ""),
|
||||
(f'xInRelease := "DB_TRO_TEST".{tro_id}', ""),
|
||||
]
|
||||
if items.get("VarioDrive"):
|
||||
if items.get(TroItem.VARIO_DRIVE):
|
||||
zeilen.append(('xInMotorProtection := "FC____"', TODO))
|
||||
zeilen.append(("xInAutomatic := #t_automatik", ""))
|
||||
zeilen.append(("stInOutMachineState := #stInOutControlUnitCabinet", ""))
|
||||
@@ -242,20 +251,20 @@ def emit_call(add, tro: dict, items: dict[str, int], alle: dict[str, dict]) -> N
|
||||
# Staubereiche: je ankommender/abgehender Verbindung einer. Welche
|
||||
# DB-Struktur dahintersteht, ist eine Planungsentscheidung - hier steht
|
||||
# nur, WIE VIELE es sein muessen und zu welchem Nachbarn sie gehoeren.
|
||||
for n, vor in enumerate(tro.get("predecessors", []), start=1):
|
||||
for n, vor in enumerate(tro.predecessors, start=1):
|
||||
zeilen.append((f'arInOutJamEntr{n} := "DB_JamArea____".stJam____.arCarrier',
|
||||
f"{TODO} - Staubereich von {nachbar(vor, alle)}"))
|
||||
zeilen.append((f'stInOutJamEntr{n} := "DB_JamArea____".stJam____.stData', ""))
|
||||
for n, nach in enumerate(tro.get("successors", []), start=2):
|
||||
for n, nach in enumerate(tro.successors, start=2):
|
||||
zeilen.append((f'arInOutJamExit{n} := "DB_JamArea____".stJam____.arCarrier',
|
||||
f"{TODO} - Staubereich nach {nachbar(nach, alle)}"))
|
||||
zeilen.append((f'stInOutJamExit{n} := "DB_JamArea____".stJam____.stData', ""))
|
||||
|
||||
for i in range(1, items.get("Separator", 1) + 1):
|
||||
for i in range(1, items.get(TroItem.SEPARATOR, 1) + 1):
|
||||
zeilen.append((f'xOutStopper{i} => "MB____"', TODO))
|
||||
if items.get("VarioDrive"):
|
||||
if items.get(TroItem.VARIO_DRIVE):
|
||||
zeilen.append(('xOutVarioMotor => "MA____"', TODO))
|
||||
for j in range(1, items.get("Switch", 0) + 1):
|
||||
for j in range(1, items.get(TroItem.SWITCH, 0) + 1):
|
||||
zeilen.append((f'xOutSw{j}ExTo3 => "MB____"', TODO))
|
||||
zeilen.append((f'stInOutHMI := "DB_Interface_HMI".stTRO.{tro_id}', ""))
|
||||
zeilen.append(('nOutStateLast => "DB_GLOB_TroState".arTroState["cMainTro____"]',
|
||||
@@ -270,13 +279,13 @@ def emit_call(add, tro: dict, items: dict[str, int], alle: dict[str, dict]) -> N
|
||||
add(f"{text} // {kommentar}" if kommentar else text)
|
||||
|
||||
|
||||
def emit_direction(add, tro: dict, anzahl: int) -> None:
|
||||
def emit_direction(add, tro: TroBase, anzahl: int) -> None:
|
||||
"""Weichenlogik - je Weiche ein FC_Direction-Aufruf."""
|
||||
add("")
|
||||
add(" REGION Direction")
|
||||
for j in range(1, anzahl + 1):
|
||||
ziel = (f"#{tro['id']}.nInDirection" if anzahl == 1
|
||||
else f"#{tro['id']}.nInDirection{j}")
|
||||
ziel = (f"#{tro.tro_id}.nInDirection" if anzahl == 1
|
||||
else f"#{tro.tro_id}.nInDirection{j}")
|
||||
add(f' "FC_Direction"(nInSwitchNo := "cSwi_____", // {TODO}')
|
||||
add(f" nOutDirection => {ziel},")
|
||||
add(f' InOutCarrier := "DB_JamArea____".stJam____'
|
||||
@@ -286,9 +295,9 @@ def emit_direction(add, tro: dict, anzahl: int) -> None:
|
||||
add(" END_REGION")
|
||||
|
||||
|
||||
def render(daten: dict, quelle: str, start_id: str = "") -> str:
|
||||
tros = daten["tros"]
|
||||
alle = {t["id"]: t for t in tros}
|
||||
def render(document: TroDocument, quelle: str, start_id: str = "") -> str:
|
||||
tros = document.tros
|
||||
alle = {t.tro_id: t for t in tros}
|
||||
geordnet, startgrund = flow_order(tros, start_id)
|
||||
|
||||
out: list[str] = []
|
||||
@@ -310,7 +319,7 @@ def render(daten: dict, quelle: str, start_id: str = "") -> str:
|
||||
add("// (DB_JamArea...) und die Zeiten, soweit als 'pruefen' vermerkt.")
|
||||
add("//")
|
||||
add("// Reihenfolge der REGIONs = Materialfluss.")
|
||||
add(f"// Anfang bei {geordnet[0]['id']}: {startgrund}.")
|
||||
add(f"// Anfang bei {geordnet[0].tro_id}: {startgrund}.")
|
||||
add("// ============================================================")
|
||||
add("")
|
||||
add('FUNCTION_BLOCK "FB_Main"')
|
||||
@@ -318,45 +327,44 @@ def render(daten: dict, quelle: str, start_id: str = "") -> str:
|
||||
add("VERSION : 0.1")
|
||||
add(" VAR ")
|
||||
for tro in geordnet:
|
||||
fb = tro.get("fbType") or "FB_UNBEKANNT"
|
||||
kommentar = f" // {tro['type']}"
|
||||
if tro.get("separators"):
|
||||
kommentar += f", Sep {', '.join(tro['separators'])}"
|
||||
add(f' {tro["id"]} : "{fb}";{kommentar}')
|
||||
fb = tro.fb_block or "FB_UNBEKANNT"
|
||||
kommentar = f" // {tro.type_name}"
|
||||
if tro.separators:
|
||||
kommentar += f", Sep {', '.join(tro.separators)}"
|
||||
add(f' {tro.tro_id} : "{fb}";{kommentar}')
|
||||
add(" END_VAR")
|
||||
add("")
|
||||
add("BEGIN")
|
||||
|
||||
for tro in geordnet:
|
||||
items = tro.get("items", {})
|
||||
n_sep = items.get("Separator", 1)
|
||||
n_swi = items.get("Switch", 0)
|
||||
n_scan = items.get("Scanner", 0)
|
||||
items = tro.items
|
||||
n_sep = items.get(TroItem.SEPARATOR, 1)
|
||||
n_swi = items.get(TroItem.SWITCH, 0)
|
||||
n_scan = items.get(TroItem.SCANNER, 0)
|
||||
|
||||
add("")
|
||||
add(f"REGION {region_title(tro)}")
|
||||
pos = tro.get("position")
|
||||
if pos:
|
||||
add(f" // Anlagenkoordinate x/y: {pos['x']:.0f} / {pos['y']:.0f} mm")
|
||||
add(f" // Vorgaenger: {', '.join(tro.get('predecessors', [])) or '-'}")
|
||||
add(f" // Nachfolger: {', '.join(tro.get('successors', [])) or '-'}")
|
||||
if tro.get("confidence"):
|
||||
add(f" // Einstufung: {tro['confidence']}")
|
||||
if tro.position:
|
||||
add(f" // Anlagenkoordinate x/y: {tro.x:.0f} / {tro.y:.0f} mm")
|
||||
add(f" // Vorgaenger: {', '.join(tro.predecessors) or '-'}")
|
||||
add(f" // Nachfolger: {', '.join(tro.successors) or '-'}")
|
||||
if tro.confidence:
|
||||
add(f" // Einstufung: {tro.confidence}")
|
||||
add("")
|
||||
|
||||
emit_separators(add, tro, n_sep, hat_scanner=bool(n_scan))
|
||||
if n_swi:
|
||||
emit_switches(add, tro, n_swi, alle)
|
||||
if items.get("VarioDrive"):
|
||||
if items.get(TroItem.VARIO_DRIVE):
|
||||
emit_vario(add, tro)
|
||||
if n_scan:
|
||||
emit_barcode(add, tro, n_scan)
|
||||
if items.get("StorageLine"):
|
||||
add(f" // {TODO}: Linienspeicher mit {items['StorageLine']} Linien - "
|
||||
if items.get(TroItem.STORAGE_LINE):
|
||||
add(f" // {TODO}: Linienspeicher mit {items[TroItem.STORAGE_LINE]} Linien - "
|
||||
f"Parametrierung je Linie")
|
||||
add("")
|
||||
|
||||
add(" #%s.stInPriorityManager.xReleaseOk := TRUE;" % tro["id"])
|
||||
add(" #%s.stInPriorityManager.xReleaseOk := TRUE;" % tro.tro_id)
|
||||
add("")
|
||||
emit_call(add, tro, items, alle)
|
||||
if n_swi:
|
||||
@@ -378,32 +386,47 @@ def main(argv: list[str] | None = None) -> int:
|
||||
json_file = results / args.json
|
||||
if not json_file.is_file():
|
||||
print(f"FEHLER: JSON nicht gefunden: {json_file}", file=sys.stderr)
|
||||
print(" Zuerst bin/tro_extract.bat laufen lassen.", file=sys.stderr)
|
||||
print(" Zuerst bin/tro_flow.bat --file <name>.csv --json laufen lassen.",
|
||||
file=sys.stderr)
|
||||
return 1
|
||||
|
||||
try:
|
||||
daten = load_tros(json_file)
|
||||
except (OSError, ValueError) as exc:
|
||||
document = load_document(json_file)
|
||||
except OSError as exc:
|
||||
print(f"FEHLER: {json_file} nicht lesbar: {exc}", file=sys.stderr)
|
||||
return 1
|
||||
if not daten.get("tros"):
|
||||
except ValidationError as exc:
|
||||
schema = _schema_of(json_file)
|
||||
if schema and schema != SCHEMA:
|
||||
# Anderes Format (z. B. die alte tro_extract-JSON) - die einzelnen
|
||||
# Feldfehler helfen dann nicht weiter.
|
||||
print(f"FEHLER: {json_file} hat das Schema '{schema}', erwartet wird "
|
||||
f"'{SCHEMA}' ({exc.error_count()} Abweichungen).", file=sys.stderr)
|
||||
print(" Mit bin/tro_flow.bat --file <name>.csv --json neu erzeugen.",
|
||||
file=sys.stderr)
|
||||
return 1
|
||||
print(f"FEHLER: {json_file} passt nicht zum TRO-Modell (lib/model.py):",
|
||||
file=sys.stderr)
|
||||
print(exc, file=sys.stderr)
|
||||
return 1
|
||||
if not document.tros:
|
||||
print(f"FEHLER: {json_file} enthaelt keine TROs.", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
text = render(daten, json_file.name, args.start)
|
||||
text = render(document, json_file.name, args.start)
|
||||
out_file = results / (args.out or f"{json_file.stem.replace('_tro', '')}_FB_Main.scl")
|
||||
out_file.write_text(text, encoding="utf-8")
|
||||
|
||||
offen = text.count(TODO)
|
||||
geordnet, startgrund = flow_order(daten["tros"], args.start)
|
||||
geordnet, startgrund = flow_order(document.tros, args.start)
|
||||
print("")
|
||||
print("================================================================")
|
||||
print("FB_MAIN-GERUEST")
|
||||
print("================================================================")
|
||||
print(f"Quelle = {json_file}")
|
||||
print(f"TROs = {len(daten['tros'])}")
|
||||
print(f"Reihenfolge = {' -> '.join(t['id'] for t in geordnet)}")
|
||||
print(f"Anfang = {geordnet[0]['id']} ({startgrund})")
|
||||
print(f"TROs = {len(document.tros)}")
|
||||
print(f"Reihenfolge = {' -> '.join(t.tro_id for t in geordnet)}")
|
||||
print(f"Anfang = {geordnet[0].tro_id} ({startgrund})")
|
||||
print(f"Offene Stellen = {offen} (Marker '{TODO}')")
|
||||
print(f"SCL = {out_file}")
|
||||
print("================================================================")
|
||||
@@ -411,10 +434,18 @@ def main(argv: list[str] | None = None) -> int:
|
||||
return 0
|
||||
|
||||
|
||||
def _schema_of(path: Path) -> str:
|
||||
"""Schema-Kennung einer JSON-Datei, nur fuer die Fehlermeldung ("" wenn unlesbar)."""
|
||||
try:
|
||||
return str(json.loads(path.read_text(encoding="utf-8")).get("schema", ""))
|
||||
except (OSError, ValueError, AttributeError):
|
||||
return ""
|
||||
|
||||
|
||||
def parse_args(argv: list[str] | None = None) -> argparse.Namespace:
|
||||
parser = argparse.ArgumentParser(
|
||||
prog="scl_skeleton",
|
||||
description="Erzeugt aus der extrahierten TRO-Liste ein FB_Main-Geruest "
|
||||
description="Erzeugt aus der TRO-JSON (tro_flow --json) ein FB_Main-Geruest "
|
||||
"als SCL zum Import in TIA Portal.",
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
epilog="Beispiele:\n"
|
||||
@@ -429,7 +460,7 @@ def parse_args(argv: list[str] | None = None) -> argparse.Namespace:
|
||||
"--json",
|
||||
default="export_tro.json",
|
||||
metavar="NAME",
|
||||
help="TRO-JSON aus tro_extract.py in %%SKEL_RESULTS%% "
|
||||
help="TRO-JSON aus tro_flow.py --json in %%SKEL_RESULTS%% "
|
||||
"(oder ein vollstaendiger Pfad). Standard: %(default)s",
|
||||
)
|
||||
parser.add_argument(
|
||||
|
||||
Reference in New Issue
Block a user