tro_annotate --read-tros: TRO-Handkorrekturen aus BricsCAD, Staubereiche vor dem TRO
- Marker tragen KEY (ID der ID-Datei) und SLOTS (Bauteil je Slot), die Zeichnung die Bauteiltabelle SPS_SKEL_PARTS (XRECORD) fuer "Im Plan waehlen" im Dialog TRO_EDIT (dxfmakros) - --read-tros schreibt geaenderte, neue und geloeschte Marker als Abschnitte edit/edit_add nach cfg/tro_overrides.ini; tro_flow.apply_edits wendet nur diese Arten an (Bauteile mit Lage, exportfest) - --emit-lisp schreibt dazu *TRO-SLOTS* und *TRO-SLOT-KINDS* - model: Lagerlinien zaehlen je TRO (Katalog nur Vorgabe), Mubea-Speicher jetzt 20 Linien (Golden-Datei) - Staubereich als stehendes graues Rechteck direkt vor seinem TRO-Marker (jam_abstand statt jam_versatz); Marker 150 mm, Text 120 mm Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
+223
-5
@@ -115,7 +115,10 @@ ausschliesslich derive_tros(), Schritt fuer Schritt:
|
||||
Alle anderen Funktionen (link_tros, locate_tros, derive_boundaries, ...)
|
||||
aendern den Typ nicht mehr. cfg/tro_overrides.ini wird hier seit
|
||||
2026-10-05 nicht mehr angewendet (siehe tro_overrides.py) - ein falsch
|
||||
erkannter Typ wird in derive_tros() korrigiert, nicht per INI.
|
||||
erkannter Typ wird in derive_tros() korrigiert, nicht per INI. Ausnahme seit
|
||||
2026-10-26: die Handkorrekturen aus BricsCAD (Abschnitte edit/edit_add, von
|
||||
tro_annotate --read-tros geschrieben) wendet apply_edits nach derive_tros an;
|
||||
sie haengen an den stabilen IDs der ID-Datei.
|
||||
|
||||
Nicht abgeleitet werden bisher: 1Sep_SSCC (nur Hinweis, Schritt 6),
|
||||
EmptyCarrBuffer und LoadingBoom. Schritt 3c braucht eine stimmige
|
||||
@@ -181,12 +184,23 @@ from material_flow import (
|
||||
resolve_input,
|
||||
)
|
||||
from kreisel_junctions import Junction, find_junctions, separators_before
|
||||
from model import Analysis, DerivedTro, LayoutPart, TroDocument
|
||||
from model import (
|
||||
SLOT_PART_KINDS,
|
||||
Analysis,
|
||||
DerivedTro,
|
||||
LayoutPart,
|
||||
TroDocument,
|
||||
has_lines,
|
||||
slot_kind,
|
||||
slot_names,
|
||||
storage_lines,
|
||||
)
|
||||
from parameter import get_float, get_int
|
||||
from tro_ids import (
|
||||
DEFAULT_TOLERANCE,
|
||||
TroIds,
|
||||
assign_ids,
|
||||
id_problem,
|
||||
ids_path,
|
||||
insert_entries,
|
||||
other_sections,
|
||||
@@ -203,6 +217,7 @@ from tro_catalog import (
|
||||
get_tro,
|
||||
mermaid_classdefs,
|
||||
)
|
||||
from tro_overrides import PartRef, TroEdit, load_overrides
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Konstanten
|
||||
@@ -2229,12 +2244,197 @@ def locate_tros(tros: list[DerivedTro], elements: list[Element], graph: Graph) -
|
||||
tro.position_from = source if tro.position else "keine Position"
|
||||
|
||||
|
||||
def _resolve_part(ref: PartRef, slot: str, elements: list[Element],
|
||||
by_id: dict[str, Element], where: str, tolerance: float,
|
||||
findings: list[str]) -> str:
|
||||
"""TeileId fuer einen Slot der Handkorrektur im aktuellen Export.
|
||||
|
||||
Erst die TeileId selbst (passende Art, Lage innerhalb der Toleranz), sonst
|
||||
das naechste Bauteil derselben Art an der Lage - die TeileId wechselt
|
||||
zwischen zwei Exporten, die Lage nicht. "" wenn nichts passt.
|
||||
"""
|
||||
if not ref.teile_id:
|
||||
return ""
|
||||
kinds = SLOT_PART_KINDS.get(slot_kind(slot), ())
|
||||
base = ref.teile_id.split("-", 1)[0]
|
||||
element = by_id.get(base)
|
||||
if element is not None and element.kind in kinds:
|
||||
if ref.x is None or element.position is None or math.hypot(
|
||||
element.x - ref.x, element.y - ref.y) <= tolerance:
|
||||
return ref.teile_id
|
||||
if ref.x is not None and ref.y is not None:
|
||||
naechste = min(
|
||||
((math.hypot(e.x - ref.x, e.y - ref.y), e) for e in elements
|
||||
if e.kind in kinds and e.position is not None),
|
||||
default=None, key=lambda p: p[0])
|
||||
if naechste is not None and naechste[0] <= tolerance:
|
||||
findings.append(
|
||||
f"{where}: {slot} {ref.teile_id} steht im Export nicht mehr dort - "
|
||||
f"{naechste[1].kind} {naechste[1].teile_id} an derselben Lage "
|
||||
f"uebernommen ({naechste[0]:.0f} mm)")
|
||||
return naechste[1].teile_id
|
||||
findings.append(f"{where}: {slot} {ref.render()} ist kein "
|
||||
f"{'/'.join(kinds) or 'Bauteil'} des Exports - Slot bleibt leer")
|
||||
return ""
|
||||
|
||||
|
||||
def _host_of(sep_id: str, by_id: dict[str, Element], graph: Graph) -> str:
|
||||
"""Flussknoten, auf dem ein Separator sitzt (CSV-Spalte 'Zuordnung')."""
|
||||
element = by_id.get(sep_id)
|
||||
host = element.assignment if element is not None else ""
|
||||
return host if host and host != UNASSIGNED and host in graph.nodes else ""
|
||||
|
||||
|
||||
def _set_parts(tro: DerivedTro, edit: TroEdit, elements: list[Element],
|
||||
by_id: dict[str, Element], graph: Graph, tolerance: float,
|
||||
findings: list[str]) -> None:
|
||||
"""Bauteile eines TRO aus den Slots der Handkorrektur setzen.
|
||||
|
||||
Umkehrung von model.derive_slots: separator<i> -> separators, line<n> ->
|
||||
die Lagerlinien am Ende von separators (je Linie ein Host), switch<i> ->
|
||||
switches, scanner<i> -> scanners, drive -> hosts[0].
|
||||
"""
|
||||
where = f"Handkorrektur {edit.key}"
|
||||
slots = edit.slots or {}
|
||||
names = slot_names(tro.type_name)
|
||||
|
||||
def teil(slot: str) -> str:
|
||||
ref = slots.get(slot)
|
||||
return _resolve_part(ref, slot, elements, by_id, where, tolerance,
|
||||
findings) if ref is not None else ""
|
||||
|
||||
alte_seps = list(tro.separators)
|
||||
if edit.slots is not None:
|
||||
seps = [teil(n) for n in names if n.startswith("separator")]
|
||||
while seps and not seps[-1]:
|
||||
seps.pop()
|
||||
tro.switches = [teil(n) for n in names if n.startswith("switch")]
|
||||
tro.scanners = [s for s in (teil(n) for n in names if n.startswith("scanner")) if s]
|
||||
drive = teil("drive") if "drive" in names else ""
|
||||
else:
|
||||
seps = alte_seps[:len(alte_seps) - len(storage_lines(tro))] \
|
||||
if has_lines(tro.type_name) else alte_seps
|
||||
drive = ""
|
||||
|
||||
if has_lines(tro.type_name):
|
||||
if edit.lines is not None:
|
||||
lines = [s for s in (
|
||||
_resolve_part(r, f"line{n}", elements, by_id, where, tolerance, findings)
|
||||
for n, r in enumerate(edit.lines, start=1)) if s]
|
||||
else:
|
||||
lines = storage_lines(tro)
|
||||
seps = [s for s in seps if s and s not in lines]
|
||||
tro.separators = seps + lines
|
||||
# Je Linie ein Host - danach zaehlt model.storage_lines die Linien
|
||||
tro.hosts = [_host_of(s, by_id, graph) or s for s in lines]
|
||||
else:
|
||||
tro.separators = seps
|
||||
if set(seps) != set(alte_seps):
|
||||
hosts = list(dict.fromkeys(h for s in seps if (h := _host_of(s, by_id, graph))))
|
||||
if hosts:
|
||||
tro.hosts = hosts
|
||||
if drive:
|
||||
tro.hosts = [drive] + [h for h in tro.hosts if h != drive]
|
||||
|
||||
|
||||
def apply_edits(tros: list[DerivedTro], edits: list[TroEdit], elements: list[Element],
|
||||
graph: Graph, findings: list[str], analysis: Analysis,
|
||||
tolerance: float = DEFAULT_TOLERANCE) -> list[DerivedTro]:
|
||||
"""Handkorrekturen aus BricsCAD (tro_overrides.ini, edit/edit_add) anwenden.
|
||||
|
||||
Laeuft nach derive_tros (die IDs aus der ID-Datei stehen fest) und vor
|
||||
Verortung und Verknuepfung, damit Koordinaten und Kanten den korrigierten
|
||||
TRO zeigen. Ein Bauteil, das ein korrigierter TRO bekommt, verliert sein
|
||||
bisheriger TRO; bleibt der ohne Separator, entfaellt er (Befund).
|
||||
Umbenennungen kommen zuletzt, die Schluessel bleiben bis dahin die IDs der
|
||||
ID-Datei. Ergebnis in ``analysis.renamed``/``analysis.added``.
|
||||
"""
|
||||
by_id = {e.teile_id: e for e in elements}
|
||||
tros = list(tros)
|
||||
renames: list[tuple[DerivedTro, TroEdit]] = []
|
||||
# Erst loeschen, dann aendern, zuletzt neu anlegen: ein neuer TRO darf die
|
||||
# Bauteile eines geloeschten uebernehmen, ohne dass der als "verdraengt" gilt.
|
||||
for edit in sorted(edits, key=lambda e: 0 if e.delete else 2 if e.add else 1):
|
||||
where = f"Handkorrektur {edit.key}"
|
||||
tro = next((t for t in tros if t.tro_id == edit.key), None)
|
||||
if edit.add:
|
||||
if tro is not None:
|
||||
findings.append(f"{where}: neuer TRO, aber {edit.key} gibt es schon - "
|
||||
f"ignoriert")
|
||||
continue
|
||||
tro = DerivedTro(tro_id=edit.key, type_name=edit.type_name,
|
||||
label=f"von Hand angelegt ({edit.key})", hosts=[],
|
||||
scanners=[], reason="von Hand in BricsCAD angelegt",
|
||||
confidence="bestaetigt")
|
||||
tros.append(tro)
|
||||
analysis.added.append(edit.key)
|
||||
elif tro is None:
|
||||
findings.append(f"{where}: TRO {edit.key} wird nicht (mehr) abgeleitet - "
|
||||
f"Korrektur ignoriert")
|
||||
continue
|
||||
if edit.delete:
|
||||
tros.remove(tro)
|
||||
findings.append(f"{where}: TRO {edit.key} ({tro.type_name}) von Hand "
|
||||
f"entfernt" + (f" - {edit.reason}" if edit.reason else ""))
|
||||
continue
|
||||
|
||||
if edit.type_name and edit.type_name != tro.type_name:
|
||||
findings.append(f"{where}: Typ {tro.type_name} -> {edit.type_name}")
|
||||
tro.type_name = edit.type_name
|
||||
if edit.slots is not None or edit.lines is not None or edit.add:
|
||||
_set_parts(tro, edit, elements, by_id, graph, tolerance, findings)
|
||||
if not edit.add:
|
||||
tro.reason = f"{tro.reason}; Handkorrektur BricsCAD"
|
||||
if edit.reason:
|
||||
tro.reason = f"{tro.reason} ({edit.reason})"
|
||||
tro.confidence = "bestaetigt"
|
||||
|
||||
# Bauteile, die dieser TRO jetzt hat, den anderen TROs nehmen
|
||||
eigene = {p for p in tro.separators + tro.scanners + tro.switches if p}
|
||||
for other in list(tros):
|
||||
if other is tro:
|
||||
continue
|
||||
weg = eigene & set(other.separators + other.scanners + other.switches)
|
||||
if not weg:
|
||||
continue
|
||||
other.separators = [s for s in other.separators if s not in weg]
|
||||
other.scanners = [s for s in other.scanners if s not in weg]
|
||||
other.switches = ["" if s in weg else s for s in other.switches]
|
||||
if other.separators or not other.items.get(TroItem.SEPARATOR):
|
||||
findings.append(f"{where}: {', '.join(sorted(weg))} von {other.tro_id} "
|
||||
f"zu {tro.tro_id} verschoben")
|
||||
else:
|
||||
tros.remove(other)
|
||||
findings.append(f"{where}: {other.tro_id} hat keinen Separator mehr "
|
||||
f"({', '.join(sorted(weg))} an {tro.tro_id}) - entfaellt")
|
||||
if edit.name and edit.name != tro.tro_id:
|
||||
renames.append((tro, edit))
|
||||
|
||||
for tro, edit in renames:
|
||||
if tro not in tros:
|
||||
continue
|
||||
problem = id_problem(edit.name)
|
||||
if not problem and any(t.tro_id == edit.name for t in tros):
|
||||
problem = f"'{edit.name}' ist schon vergeben"
|
||||
if problem:
|
||||
findings.append(f"Handkorrektur {edit.key}: Umbenennung nicht moeglich - "
|
||||
f"{problem}")
|
||||
continue
|
||||
if tro.tro_id in analysis.added:
|
||||
analysis.added[analysis.added.index(tro.tro_id)] = edit.name
|
||||
else:
|
||||
analysis.renamed[edit.name] = tro.tro_id
|
||||
tro.tro_id = edit.name
|
||||
return tros
|
||||
|
||||
|
||||
def analyse_tros(
|
||||
graph: Graph,
|
||||
elements: list[Element],
|
||||
*,
|
||||
sepliste_counts: dict[str, int] | None = None,
|
||||
tro_ids: TroIds | None = None,
|
||||
edits: list[TroEdit] | None = None,
|
||||
) -> Analysis:
|
||||
"""TROs ermitteln, verknuepfen, verorten und gegen die Typdefinition pruefen.
|
||||
|
||||
@@ -2250,6 +2450,8 @@ def analyse_tros(
|
||||
Roh-JSON (load_sepliste_counts) - nur Gegenprobe.
|
||||
tro_ids: ID-Datei der Anlage (siehe derive_tros); None = neu
|
||||
nummerieren.
|
||||
edits: Handkorrekturen aus BricsCAD (tro_overrides.load_overrides
|
||||
(...).edits), angewandt direkt nach derive_tros (apply_edits).
|
||||
|
||||
Rueckgabe:
|
||||
Analysis mit tros, boundaries, edges/edge_kinds, unknown_types
|
||||
@@ -2259,6 +2461,11 @@ def analyse_tros(
|
||||
analysis = Analysis()
|
||||
analysis.tros = derive_tros(graph, elements, analysis.findings,
|
||||
sepliste_counts=sepliste_counts, tro_ids=tro_ids)
|
||||
if edits:
|
||||
analysis.tros = apply_edits(analysis.tros, edits, elements, graph,
|
||||
analysis.findings, analysis,
|
||||
tolerance=tro_ids.tolerance if tro_ids
|
||||
else DEFAULT_TOLERANCE)
|
||||
analysis.boundaries = derive_boundaries(elements, graph)
|
||||
# Fuer Verortung und Verknuepfung zaehlen TROs und Systemgrenzen zusammen:
|
||||
# link_tros() registriert eine Kante nur zu einem Knoten, der Host eines
|
||||
@@ -3281,16 +3488,20 @@ def write_ids_file(tro_ids: TroIds, analysis: Analysis, elements: list[Element],
|
||||
if result is None:
|
||||
return "unveraendert"
|
||||
anchors = separator_anchors(elements)
|
||||
# Die ID-Datei kennt die TROs unter ihrer abgeleiteten ID: Umbenennungen
|
||||
# und neue TROs aus BricsCAD stehen in tro_overrides.ini, nicht hier.
|
||||
tros = [t.model_copy(update={"tro_id": analysis.edit_key(t.tro_id)})
|
||||
for t in analysis.tros if t.tro_id not in analysis.added]
|
||||
if result.renumbered:
|
||||
tro_ids.path.parent.mkdir(parents=True, exist_ok=True)
|
||||
rest = other_sections(tro_ids.path)
|
||||
text = render_ids(analysis.tros, anchors, source)
|
||||
text = render_ids(tros, anchors, source)
|
||||
tro_ids.path.write_text(text + (f"\n{rest}\n" if rest else ""),
|
||||
encoding="utf-8")
|
||||
return "neu geschrieben" if tro_ids.exists else "angelegt"
|
||||
if result.new:
|
||||
insert_entries(tro_ids.path,
|
||||
render_new_entries(analysis.tros, anchors, result.new, source))
|
||||
render_new_entries(tros, anchors, result.new, source))
|
||||
return f"{len(result.new)} neue(r) TRO(s) angehaengt"
|
||||
return "unveraendert"
|
||||
|
||||
@@ -3379,8 +3590,15 @@ def main(argv: list[str] | None = None) -> int:
|
||||
print(f"FEHLER: ID-Datei nicht lesbar: {exc}", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
# Handkorrekturen aus BricsCAD (nur die Arten edit/edit_add, siehe
|
||||
# tro_overrides.py); die uebrigen Abschnitte der Datei gelten hier nicht.
|
||||
overrides = load_overrides(csv_file.name, env_dir("SKEL_CFG", "cfg"))
|
||||
warnings += [w for w in overrides.warnings if ".edit" in w]
|
||||
if overrides.edits and args.renumber:
|
||||
warnings.append(f"--renumber: {len(overrides.edits)} Handkorrektur(en) in "
|
||||
f"{overrides.source} haengen an den alten IDs - pruefen")
|
||||
analysis = analyse_tros(graph, elements, sepliste_counts=sepliste_counts,
|
||||
tro_ids=tro_ids)
|
||||
tro_ids=tro_ids, edits=overrides.edits)
|
||||
if not analysis.tros:
|
||||
print(f"FEHLER: {csv_file} enthaelt keine Separatoren - keine TROs "
|
||||
f"ableitbar.", file=sys.stderr)
|
||||
|
||||
Reference in New Issue
Block a user