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:
+165
-32
@@ -143,20 +143,29 @@ from model import (
|
||||
AnlageDocument,
|
||||
DerivedTro,
|
||||
JamArea,
|
||||
TroDocument,
|
||||
derive_slots,
|
||||
has_lines,
|
||||
slot_kind,
|
||||
slot_names,
|
||||
)
|
||||
from tro_ids import id_problem
|
||||
from tro_overrides import PartRef, TroEdit, load_overrides, override_path, save_edits
|
||||
from tro_overrides import (
|
||||
ConnectionEdit,
|
||||
PartRef,
|
||||
TroEdit,
|
||||
load_overrides,
|
||||
override_path,
|
||||
save_edits,
|
||||
)
|
||||
from scl_context import (
|
||||
JAM_NAMES_SUFFIX,
|
||||
HwSource,
|
||||
flow_order,
|
||||
jam_key,
|
||||
jam_label,
|
||||
jam_numbers,
|
||||
jam_override,
|
||||
jam_stable_key,
|
||||
load_jam_overrides,
|
||||
save_jam_overrides,
|
||||
)
|
||||
@@ -180,6 +189,8 @@ JAM_BLOCK = "JAM_AREA"
|
||||
PARTS_XRECORD = "SPS_SKEL_PARTS"
|
||||
# Unsichtbare Attribute der Marker (build_symbol)
|
||||
HIDDEN_TAGS = ("UID", "KEY", "ITEMS", "CONFIDENCE", "SLOTS")
|
||||
# Unsichtbare Attribute der Staubereiche (build_jam_symbol)
|
||||
JAM_HIDDEN_TAGS = ("CONVEYOR", "NUMBER", "TRO", "ENTRY", "AREA", "AUTO", "KEY")
|
||||
# Anzeige eines Staubereichs ohne Foerderer (Nummer nicht ableitbar)
|
||||
JAM_OFFEN = "Jam????_?"
|
||||
|
||||
@@ -854,35 +865,49 @@ def build_jam_symbol(doc, text: float) -> str:
|
||||
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).
|
||||
KEY unsichtbar - stabiler Schluessel <TRO-Schluessel>.<Eingang>
|
||||
(scl_context.jam_stable_key), ueberlebt eine Umbenennung
|
||||
KEY (sonst TRO + ENTRY) ist der Schluessel in <csv>_jam_names.ini.
|
||||
"""
|
||||
w, h = text * JAM_HALB_BREITE, text * JAM_HALB_HOEHE
|
||||
if JAM_BLOCK in doc.blocks:
|
||||
# Form aus einem frueheren Lauf (liegendes Rechteck) ersetzen; die
|
||||
# Attributdefinitionen bleiben, damit vorhandene Bloecke gueltig sind.
|
||||
# Attributdefinitionen bleiben, damit vorhandene Bloecke gueltig sind,
|
||||
# fehlende (KEY) kommen dazu.
|
||||
blk = doc.blocks.get(JAM_BLOCK)
|
||||
for e in list(blk):
|
||||
if e.dxftype() != "ATTDEF":
|
||||
blk.delete_entity(e)
|
||||
_jam_form(blk, text)
|
||||
vorhanden = set()
|
||||
for a in blk.query("ATTDEF"):
|
||||
vorhanden.add(a.dxf.tag.upper())
|
||||
if a.dxf.tag.upper() == "NAME":
|
||||
_jam_name(a, text)
|
||||
offset = min((a.dxf.insert.y for a in blk.query("ATTDEF")), default=-h)
|
||||
for tag_name in JAM_HIDDEN_TAGS:
|
||||
if tag_name not in vorhanden:
|
||||
offset -= text
|
||||
_jam_attdef(blk, tag_name, -w, offset, text)
|
||||
return JAM_BLOCK
|
||||
blk = doc.blocks.new(name=JAM_BLOCK)
|
||||
_jam_form(blk, text)
|
||||
w, h = text * JAM_HALB_BREITE, text * JAM_HALB_HOEHE
|
||||
name = blk.add_attdef("NAME", insert=(0, 0), height=text * JAM_TEXT,
|
||||
dxfattribs={"style": "Standard", "text": JAM_OFFEN})
|
||||
_jam_name(name, text)
|
||||
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})
|
||||
for tag_name in JAM_HIDDEN_TAGS:
|
||||
_jam_attdef(blk, tag_name, -w, offset, text)
|
||||
offset -= text
|
||||
return JAM_BLOCK
|
||||
|
||||
|
||||
def _jam_attdef(blk, tag_name: str, x: float, y: float, text: float) -> None:
|
||||
blk.add_attdef(tag_name, insert=(x, y), height=text * 0.7,
|
||||
dxfattribs={"style": "Standard", "color": 256, "text": "",
|
||||
"invisible": 1})
|
||||
|
||||
|
||||
def _jam_form(blk, text: float) -> None:
|
||||
"""Stehendes Rechteck: graue Fuellung, grauer Rand."""
|
||||
w, h = text * JAM_HALB_BREITE, text * JAM_HALB_HOEHE
|
||||
@@ -967,7 +992,7 @@ def place_jams(
|
||||
f"nicht gezeichnet")
|
||||
continue
|
||||
auto = automatisch.get(area.jam_id)
|
||||
nr, k = overrides.get(jam_key(area), auto or ("", 0))
|
||||
nr, k = jam_override(area, layout, overrides) or auto or ("", 0)
|
||||
ref = msp.add_blockref(block, transform.to_dxf(*lage),
|
||||
dxfattribs={"layer": layers["jam"]})
|
||||
ref.add_auto_attribs({
|
||||
@@ -978,6 +1003,8 @@ def place_jams(
|
||||
"ENTRY": str(area.entry),
|
||||
"AREA": area.jam_id,
|
||||
"AUTO": f"{auto[0]}_{auto[1]}" if auto else "",
|
||||
# Schluessel fuer --read-jams: ueberlebt eine Umbenennung des TRO
|
||||
"KEY": jam_stable_key(area, layout),
|
||||
})
|
||||
tag(ref, area.tro)
|
||||
gezeichnet += 1
|
||||
@@ -999,11 +1026,16 @@ def read_jams(dxf_file: Path, ini_file: Path) -> tuple[dict[str, tuple[str, int]
|
||||
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', '')}"
|
||||
# Stabiler Schluessel (KEY, seit 2026-10-27), sonst TRO-ID + Eingang
|
||||
anzeige = f"{a.get('TRO', '')}.{a.get('ENTRY', '')}"
|
||||
key = a.get("KEY") or anzeige
|
||||
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 key != anzeige and anzeige in overrides:
|
||||
# Eintrag unter der alten Anzeige-ID auf den Schluessel umstellen
|
||||
overrides.setdefault(key, overrides.pop(anzeige))
|
||||
if not nr and not k:
|
||||
continue
|
||||
if not (nr.isdigit() and len(nr) == 4 and k.isdigit() and int(k) > 0):
|
||||
@@ -1085,33 +1117,48 @@ def write_parts(doc, elements, transform: Transform) -> int:
|
||||
return len(tags) // 5
|
||||
|
||||
|
||||
def read_markers(dxf_file: Path) -> list[dict[str, str]]:
|
||||
"""Attribute aller TRO-Marker (Bloecke TRO_SYM_*) der Zeichnung."""
|
||||
def read_markers(dxf_file: Path) -> tuple[list[dict[str, str]], list[dict[str, str]]]:
|
||||
"""Attribute aller TRO-Marker (Bloecke TRO_SYM_*) und aller
|
||||
Verbindungsbloecke (CONNECTION_ARROW) der Zeichnung."""
|
||||
doc = ezdxf.readfile(str(dxf_file))
|
||||
markers = []
|
||||
markers, arrows = [], []
|
||||
for ref in doc.modelspace().query("INSERT"):
|
||||
if not ref.dxf.name.upper().startswith(BLOCK_PREFIX):
|
||||
name = ref.dxf.name.upper()
|
||||
if name.startswith(BLOCK_PREFIX):
|
||||
ziel = markers
|
||||
elif name == CONNECTION_BLOCK:
|
||||
ziel = arrows
|
||||
else:
|
||||
continue
|
||||
a = {att.dxf.tag.upper(): (att.dxf.text or "").strip() for att in ref.attribs}
|
||||
a["_BLOCK"] = ref.dxf.name
|
||||
markers.append(a)
|
||||
return markers
|
||||
ziel.append(a)
|
||||
return markers, arrows
|
||||
|
||||
|
||||
def read_tros(markers: list[dict[str, str]], raw: Analysis, elements,
|
||||
csv_name: str, cfg_dir: Path | None = None,
|
||||
write: bool = True) -> tuple[list[TroEdit], list[str]]:
|
||||
write: bool = True,
|
||||
arrows: list[dict[str, str]] | None = None,
|
||||
base: Analysis | None = None) -> tuple[list[TroEdit], list[str]]:
|
||||
"""Marker der Zeichnung mit der Ableitung vergleichen -> Handkorrekturen.
|
||||
|
||||
``raw`` ist die Ableitung OHNE Handkorrekturen (IDs aus der ID-Datei). Je
|
||||
Marker mit KEY eines abgeleiteten TRO entsteht ein edit-Abschnitt, wenn
|
||||
Typ, Kennung oder Bauteile abweichen; ein Marker ohne passenden KEY ist
|
||||
ein neuer TRO (edit_add); ein abgeleiteter TRO mit Lage, zu dem kein
|
||||
Marker mehr da ist, wurde geloescht. Die Abschnitte ersetzen alle
|
||||
bisherigen edit-/edit_add-Abschnitte der CSV in tro_overrides.ini
|
||||
(Begruendungen bleiben). Rueckgabe: (Korrekturen, Meldungen).
|
||||
Marker mehr da ist, wurde geloescht. Mit ``arrows`` (Verbindungsbloecke)
|
||||
kommen die geaenderten Verbindungen dazu (_read_connections, Abschnitt
|
||||
edit_connections). Die Abschnitte ersetzen alle bisherigen
|
||||
edit-/edit_add-/edit_connections-Abschnitte der CSV in tro_overrides.ini
|
||||
(Begruendungen bleiben). Ohne ``arrows`` bleibt ein vorhandener
|
||||
edit_connections-Abschnitt stehen. ``base`` ist die Ableitung MIT den
|
||||
TRO-Korrekturen, aber ohne Verbindungskorrekturen - verglichen werden die
|
||||
Pfeile mit ihr, damit Kanten eines geaenderten oder neuen TRO nicht als
|
||||
Handkorrektur gelten (Standard: ``raw``). Rueckgabe: (Korrekturen, Meldungen).
|
||||
"""
|
||||
meldungen: list[str] = []
|
||||
marker_key: dict[int, str] = {}
|
||||
by_id = {e.teile_id: e for e in elements}
|
||||
alt = {(e.section_kind, e.key): e
|
||||
for e in load_overrides(csv_name, cfg_dir).edits}
|
||||
@@ -1176,6 +1223,8 @@ def read_tros(markers: list[dict[str, str]], raw: Analysis, elements,
|
||||
raw_slots, raw_lines = tro_slots(derived)
|
||||
else:
|
||||
raw_slots, raw_lines = {}, []
|
||||
# Schluessel des Markers fuer die Verbindungen: abgeleitet = KEY, neu = ID
|
||||
marker_key[id(m)] = key if derived is not None else tro_id
|
||||
if lines is None:
|
||||
lines = raw_lines if has_lines(type_name) else []
|
||||
pruefen(where, type_name, slots, lines)
|
||||
@@ -1216,11 +1265,64 @@ def read_tros(markers: list[dict[str, str]], raw: Analysis, elements,
|
||||
edits.append(edit)
|
||||
meldungen.append(_describe_edit(edit))
|
||||
|
||||
if arrows is not None:
|
||||
connections = _read_connections(arrows, markers, marker_key, base or raw, meldungen)
|
||||
else:
|
||||
# ohne Verbindungsbloecke: bisherige Verbindungskorrekturen behalten
|
||||
connections = load_overrides(csv_name, cfg_dir).connection_edits
|
||||
if write:
|
||||
save_edits(csv_name, edits, cfg_dir)
|
||||
save_edits(csv_name, edits, cfg_dir, connections)
|
||||
return edits, meldungen
|
||||
|
||||
|
||||
def _read_connections(arrows: list[dict[str, str]], markers: list[dict[str, str]],
|
||||
marker_key: dict[int, str], base: Analysis,
|
||||
meldungen: list[str]) -> list[ConnectionEdit]:
|
||||
"""Verbindungsbloecke der Zeichnung gegen die abgeleiteten TRO-Kanten.
|
||||
|
||||
Die Enden eines CONNECTION_ARROW werden ueber die Marker derselben
|
||||
Zeichnung zu stabilen Schluesseln: erst ueber den sichtbaren Namen
|
||||
(FROM_TRO/TO_TRO, das sieht der Bearbeiter), sonst ueber die interne
|
||||
Nummer (FROM_UID/TO_UID, bei dxfmakros-Bloecken frueher FROM_ID/TO_ID) -
|
||||
so stimmt es auch nach einer Umbenennung des Markers. Kanten von/zu
|
||||
Stationen tragen keinen Block und bleiben, wie abgeleitet; Kanten zu
|
||||
geloeschten Markern fallen mit dem TRO weg.
|
||||
"""
|
||||
by_name = {m.get("ID", ""): m for m in markers if id(m) in marker_key}
|
||||
by_uid = {m.get("UID", ""): m for m in markers if id(m) in marker_key and m.get("UID")}
|
||||
|
||||
def ende(a: dict[str, str], seite: str) -> str | None:
|
||||
m = by_name.get(a.get(f"{seite}_TRO", ""))
|
||||
if m is None:
|
||||
m = by_uid.get(a.get(f"{seite}_UID", "") or a.get(f"{seite}_ID", ""))
|
||||
return marker_key.get(id(m)) if m is not None else None
|
||||
|
||||
gewollt: dict[tuple[str, str], str] = {}
|
||||
for a in arrows:
|
||||
src, dst = ende(a, "FROM"), ende(a, "TO")
|
||||
if src is None or dst is None:
|
||||
meldungen.append(f"Verbindung {a.get('FROM_TRO') or '?'} -> "
|
||||
f"{a.get('TO_TRO') or '?'}: Ende passt zu keinem Marker - "
|
||||
f"uebersprungen")
|
||||
continue
|
||||
if src == dst:
|
||||
meldungen.append(f"Verbindung {src} -> {dst}: Quelle = Ziel - uebersprungen")
|
||||
continue
|
||||
gewollt[(src, dst)] = "weiche" if a.get("KIND") == "weiche" else ""
|
||||
|
||||
vorhanden = set(marker_key.values())
|
||||
tro_ids = {t.tro_id for t in base.tros}
|
||||
# Kanten der Ableitung, Enden als stabile Schluessel wie die Marker
|
||||
abgeleitet = {(base.edit_key(s), base.edit_key(d)) for s, d in base.edges
|
||||
if s in tro_ids and d in tro_ids}
|
||||
changes = [ConnectionEdit(add=False, src=s, dst=d) for s, d in sorted(abgeleitet)
|
||||
if (s, d) not in gewollt and s in vorhanden and d in vorhanden]
|
||||
changes += [ConnectionEdit(add=True, src=s, dst=d, kind=k)
|
||||
for (s, d), k in sorted(gewollt.items()) if (s, d) not in abgeleitet]
|
||||
meldungen += [f"Verbindung {c.render()}" for c in changes]
|
||||
return changes
|
||||
|
||||
|
||||
def _describe_edit(edit: TroEdit) -> str:
|
||||
if edit.delete:
|
||||
return f"{edit.key}: geloescht"
|
||||
@@ -1462,7 +1564,27 @@ def _main_read_jams(args, csv_file: Path) -> int:
|
||||
return 0
|
||||
|
||||
|
||||
def _derive(args, csv_file: Path, edits: list[TroEdit] | None):
|
||||
def analysis_from_document(doc: TroDocument) -> Analysis:
|
||||
"""Analysis aus einer TRO-/Anlagen-JSON, damit die Zeichenfunktionen
|
||||
(place_markers, draw_connections, place_stations) die JSON zeigen statt
|
||||
einer neuen Ableitung. Umbenennungen kommen aus DerivedTro.key."""
|
||||
tros = [t.model_copy(deep=True) for t in doc.tros]
|
||||
analysis = Analysis(
|
||||
tros=tros,
|
||||
boundaries=[b.model_copy(deep=True) for b in doc.boundaries],
|
||||
edges=[(c.src, c.dst) for c in doc.connections],
|
||||
edge_kinds={(c.src, c.dst): c.kind for c in doc.connections},
|
||||
findings=list(doc.findings),
|
||||
renamed={t.tro_id: t.key for t in tros if t.key and t.key != t.tro_id},
|
||||
method=f"Anlagen-/TRO-JSON {doc.source}",
|
||||
)
|
||||
analysis.unknown_types = sorted({t.type_name for t in tros if t.definition() is None})
|
||||
analysis.isolated = [t for t in tros if not t.connected]
|
||||
return analysis
|
||||
|
||||
|
||||
def _derive(args, csv_file: Path, edits: list[TroEdit] | None,
|
||||
connection_edits: list[ConnectionEdit] | None = None):
|
||||
"""CSV lesen und die TROs ableiten - (Elemente, Analysis) oder None.
|
||||
|
||||
Derselbe Aufruf wie in tro_flow.main (mit der Separator-Gegenprobe aus
|
||||
@@ -1490,7 +1612,8 @@ def _derive(args, csv_file: Path, edits: list[TroEdit] | None):
|
||||
print(f"FEHLER: ID-Datei nicht lesbar: {exc}", file=sys.stderr)
|
||||
return None
|
||||
analysis = analyse_tros(graph, elements, sepliste_counts=sepliste_counts,
|
||||
tro_ids=tro_ids, edits=edits)
|
||||
tro_ids=tro_ids, edits=edits,
|
||||
connection_edits=connection_edits)
|
||||
if not analysis.tros:
|
||||
print(f"FEHLER: keine TROs aus {csv_file} ableitbar.", file=sys.stderr)
|
||||
return None
|
||||
@@ -1514,7 +1637,7 @@ def _main_read_tros(args, csv_file: Path) -> int:
|
||||
print(f"FEHLER: CSV nicht gefunden: {csv_file}", file=sys.stderr)
|
||||
return 1
|
||||
try:
|
||||
markers = read_markers(dxf_file)
|
||||
markers, arrows = read_markers(dxf_file)
|
||||
except (OSError, ezdxf.DXFError) as exc:
|
||||
print(f"FEHLER: {dxf_file} nicht lesbar: {exc}", file=sys.stderr)
|
||||
return 1
|
||||
@@ -1531,8 +1654,15 @@ def _main_read_tros(args, csv_file: Path) -> int:
|
||||
f"mit dem aktuellen tro_annotate neu beschriften?", file=sys.stderr)
|
||||
return 1
|
||||
try:
|
||||
# 1. TRO-Korrekturen aus den Markern; 2. die Pfeile gegen die Ableitung
|
||||
# MIT diesen Korrekturen (sonst gaelten Kanten eines neuen TRO als
|
||||
# von Hand gezogen)
|
||||
edits, _ = read_tros(markers, raw, elements, csv_file.name, cfg_dir, write=False)
|
||||
derived = _derive(args, csv_file, edits)
|
||||
if derived is None:
|
||||
return 1
|
||||
edits, meldungen = read_tros(markers, raw, elements, csv_file.name, cfg_dir,
|
||||
write=not args.check)
|
||||
write=not args.check, arrows=arrows, base=derived[1])
|
||||
except (OSError, configparser.Error) as exc:
|
||||
print(f"FEHLER: {exc}", file=sys.stderr)
|
||||
return 1
|
||||
@@ -1540,7 +1670,7 @@ def _main_read_tros(args, csv_file: Path) -> int:
|
||||
print("================================================================")
|
||||
print("TRO-HANDKORREKTUREN UEBERNEHMEN")
|
||||
print("================================================================")
|
||||
print(f"Zeichnung = {dxf_file} ({len(markers)} Marker)")
|
||||
print(f"Zeichnung = {dxf_file} ({len(markers)} Marker, {len(arrows)} Verbindungen)")
|
||||
ziel = override_path(cfg_dir)
|
||||
print(f"Korrekturen = {len(edits)} -> "
|
||||
f"{'- (--check, nichts geschrieben)' if args.check else ziel}")
|
||||
@@ -1624,7 +1754,7 @@ def main(argv: list[str] | None = None) -> int:
|
||||
# TROs aus dem CSV, mit den Handkorrekturen aus tro_overrides.ini
|
||||
overrides = load_overrides(csv_file.name, cfg_dir)
|
||||
warnings += [w for w in overrides.warnings if ".edit" in w]
|
||||
derived = _derive(args, csv_file, overrides.edits)
|
||||
derived = _derive(args, csv_file, overrides.edits, overrides.connection_edits)
|
||||
if derived is None:
|
||||
return 1
|
||||
elements, analysis = derived
|
||||
@@ -1654,14 +1784,17 @@ def main(argv: list[str] | None = None) -> int:
|
||||
counts = {"marker": 0, "flow": 0, "removed": 0, "layers": 0, "blocks": 0,
|
||||
"first_id": 0, "jams": 0}
|
||||
if anlage is not None:
|
||||
# Mit Anlagen-JSON zeigt die Zeichnung genau sie - das, woraus die SCL
|
||||
# wird. Die Ableitung aus dem CSV dient nur noch als Gegenprobe.
|
||||
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")
|
||||
f"Die Zeichnung zeigt die Anlagen-JSON; die Ableitung aus dem CSV weicht "
|
||||
f"ab (nur CSV: {', '.join(sorted(aus_csv - aus_json)) or '-'}; nur JSON: "
|
||||
f"{', '.join(sorted(aus_json - aus_csv)) or '-'}) - JSON veraltet? "
|
||||
f"tro_flow --json und merge_hw_layout neu laufen lassen")
|
||||
analysis = analysis_from_document(anlage.layout)
|
||||
|
||||
if args.check:
|
||||
counts["marker"] = sum(1 for t in analysis.tros if t.position)
|
||||
|
||||
Reference in New Issue
Block a user