Files
sps_skel/tests/test_tro_edits.py
T
s.ayadi 6c8c235287 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>
2026-10-26 14:35:04 +01:00

251 lines
11 KiB
Python

# -*- coding: utf-8 -*-
"""Tests fuer die Handkorrekturen aus BricsCAD (Dialog TRO_EDIT).
Weg: Marker der Zeichnung (Attribute KEY/ID/TYPE/SLOTS) -> tro_annotate.read_tros
-> Abschnitte edit/edit_add in tro_overrides.ini (tro_overrides.save_edits) ->
tro_flow.apply_edits beim naechsten Lauf.
Fixture: Testanlage test_system (TRO01 3Sep2Swi, TRO02 2Sep1Swi, TRO03 2Sep2Swi,
TRO04-06 1Sep1Swi an den Separatoren 0012/0011/0010).
"""
from __future__ import annotations
from pathlib import Path
import pytest
from material_flow import build_graph, read_elements
from model import TroDocument, derive_slots
from tro_annotate import emit_lisp, format_slots, parse_slots, read_tros, tro_slots
from tro_flow import analyse_tros
from tro_overrides import PartRef, TroEdit, load_overrides, save_edits
FIXTURE = Path(__file__).parent / "fixtures" / "test_system_export.csv"
CSV = FIXTURE.name
@pytest.fixture(scope="module")
def layout():
elements = read_elements(FIXTURE, [])
return elements, build_graph(elements, CSV, [])
def _analyse(layout, edits=None):
elements, graph = layout
return analyse_tros(graph, elements, edits=edits)
def _by_sep(analysis, sep: str):
return next(t for t in analysis.tros if sep in t.separators)
def _markers(analysis) -> list[dict[str, str]]:
"""Marker, wie tro_annotate.place_markers sie in die Zeichnung schreibt."""
return [{"ID": t.tro_id, "KEY": analysis.edit_key(t.tro_id), "TYPE": t.type_name,
"SLOTS": format_slots(*tro_slots(t)), "_BLOCK": f"TRO_SYM_{t.type_name}"}
for t in analysis.tros]
# ---------------------------------------------------------------------------
# INI: lesen und schreiben
# ---------------------------------------------------------------------------
def test_partref_hin_und_zurueck():
ref = PartRef.parse("0010 @ 1234.5 -567.8")
assert (ref.teile_id, ref.x, ref.y) == ("0010", 1234.5, -567.8)
assert PartRef.parse(ref.render()) == ref
assert PartRef.parse("-") == PartRef()
assert PartRef.parse("0010").x is None
with pytest.raises(ValueError):
PartRef.parse("0010 @ 1")
def test_load_overrides_liest_edit_abschnitte(tmp_path):
(tmp_path / "tro_overrides.ini").write_text(
f"[{CSV}.edit.TRO04]\ntype = 2Sep1Swi\nname = Weiche_K8\n"
f"separator1 = 0012 @ 1 2\nswitch1 = -\nreason = Begehung\n\n"
f"[{CSV}.edit_add.TRO99]\ntype = 1Sep\nseparator1 = 0011\n\n"
f"[{CSV}.edit.TRO05]\ndelete = true\n\n"
f"[{CSV}.edit.TRO06]\nweiche = 0023\n\n"
f"[{CSV}.edit_add.TRO98]\nseparator1 = 0011\n",
encoding="utf-8")
overrides = load_overrides(CSV, tmp_path)
edits = {e.key: e for e in overrides.edits}
assert set(edits) == {"TRO04", "TRO99", "TRO05"}
assert edits["TRO04"].name == "Weiche_K8"
assert edits["TRO04"].slots == {"separator1": PartRef("0012", 1.0, 2.0),
"switch1": PartRef()}
assert edits["TRO04"].reason == "Begehung"
assert edits["TRO99"].add and edits["TRO99"].lines is None
assert edits["TRO05"].delete
# unbekannter Schluessel und neuer TRO ohne Typ: gemeldet, nicht angewandt
assert len(overrides.warnings) == 2
# material_flow --connect sieht die Handkorrekturen nicht
assert not overrides
def test_save_edits_ersetzt_nur_die_eigenen_abschnitte(tmp_path):
ini = tmp_path / "tro_overrides.ini"
ini.write_text(
"; Kopf\n\n"
f"[{CSV}.tro.TRO03]\ntype = 1Sep\n\n"
f"[{CSV}.edit.TRO01]\ntype = 1Sep\n\n"
"; Kommentar zum naechsten Abschnitt\n"
f"[andere.csv.edit.TRO01]\ntype = 1Sep\n",
encoding="utf-8")
save_edits(CSV, [TroEdit(key="TRO02", name="Neu"),
TroEdit(key="TRO07", add=True, type_name="1Sep",
slots={"separator1": PartRef("0010", 5, 6)})], tmp_path)
text = ini.read_text(encoding="utf-8")
assert f"[{CSV}.edit.TRO01]" not in text
assert f"[{CSV}.tro.TRO03]" in text and "[andere.csv.edit.TRO01]" in text
assert "; Kommentar zum naechsten Abschnitt\n[andere.csv.edit.TRO01]" in text
assert text.index("[andere.csv") < text.index(f"[{CSV}.edit.TRO02]")
assert "separator1 = 0010 @ 5.0 6.0" in text
assert {e.key for e in load_overrides(CSV, tmp_path).edits} == {"TRO02", "TRO07"}
# erneut schreiben ersetzt den Block, leer entfernt ihn samt Kopfzeile
save_edits(CSV, [TroEdit(key="TRO02", name="Neu")], tmp_path)
assert ini.read_text(encoding="utf-8").count("Handkorrekturen aus BricsCAD") == 1
save_edits(CSV, [], tmp_path)
text = ini.read_text(encoding="utf-8")
assert "Handkorrekturen" not in text and f"{CSV}.edit." not in text
assert text.startswith("; Kopf\n")
# ---------------------------------------------------------------------------
# tro_flow.apply_edits
# ---------------------------------------------------------------------------
def test_typ_und_bauteile_aendern_verdraengt_den_nachbarn(layout):
raw = _analyse(layout)
key = _by_sep(raw, "0012").tro_id
verdraengt = _by_sep(raw, "0011").tro_id
edit = TroEdit(key=key, type_name="2Sep1Swi", slots={
"separator1": PartRef("0012"), "separator2": PartRef("0011"),
"switch1": PartRef("0024")})
analysis = _analyse(layout, [edit])
tro = analysis.by_id(key)
assert (tro.type_name, tro.separators, tro.switches) == ("2Sep1Swi", ["0012", "0011"],
["0024"])
assert tro.confidence == "bestaetigt"
assert analysis.by_id(verdraengt) is None
assert any(f"{verdraengt} hat keinen Separator mehr" in f for f in analysis.findings)
# 2Sep1Swi hat dazu zwei Scanner-Slots, hier leer
assert [s.layout_id for s in derive_slots(tro)] == ["0012", "0011", "0024", "", ""]
def test_umbenennen_loeschen_und_neu_anlegen(layout):
raw = _analyse(layout)
a, b = _by_sep(raw, "0012").tro_id, _by_sep(raw, "0010").tro_id
edits = [
TroEdit(key=a, name="Weiche_K8"),
TroEdit(key="TRO99", add=True, type_name="1Sep",
slots={"separator1": PartRef("0010")}),
TroEdit(key=b, delete=True),
TroEdit(key=_by_sep(raw, "0011").tro_id, name="SYS1"),
]
analysis = _analyse(layout, edits)
assert analysis.by_id("Weiche_K8") is not None and analysis.by_id(a) is None
assert analysis.renamed == {"Weiche_K8": a}
assert analysis.edit_key("Weiche_K8") == a
neu = analysis.by_id("TRO99")
assert neu.separators == ["0010"] and neu.position is not None
assert analysis.added == ["TRO99"]
assert analysis.by_id(b) is None
# das Loeschen laeuft vor dem Anlegen: 0010 wird nicht "verdraengt"
assert not any("verschoben" in f or "keinen Separator" in f for f in analysis.findings)
assert any("Umbenennung nicht moeglich" in f for f in analysis.findings)
# die korrigierten TROs laufen durch das Modell der TRO-JSON
TroDocument.from_json(TroDocument.from_analysis(analysis, CSV).to_json())
def test_teileid_aus_altem_export_wird_ueber_die_lage_gefunden(layout):
elements, _ = layout
sep = next(e for e in elements if e.teile_id == "0012")
raw = _analyse(layout)
key = _by_sep(raw, "0012").tro_id
edit = TroEdit(key=key, slots={"separator1": PartRef("9999", sep.x, sep.y),
"switch1": PartRef("0024")})
analysis = _analyse(layout, [edit])
assert analysis.by_id(key).separators == ["0012"]
assert any("9999 steht im Export nicht mehr dort" in f for f in analysis.findings)
# ---------------------------------------------------------------------------
# tro_annotate.read_tros
# ---------------------------------------------------------------------------
def test_slots_attribut_hin_und_zurueck():
slots, lines = {"separator1": "0010", "switch1": ""}, ["0066", "0069"]
assert format_slots(slots, lines) == "separator1=0010;switch1=;lines=0066,0069"
assert parse_slots(format_slots(slots, lines)) == (slots, lines)
assert parse_slots("separator1=0010") == ({"separator1": "0010"}, None)
def test_unveraenderte_zeichnung_ergibt_keine_korrektur(layout, tmp_path):
elements, _ = layout
raw = _analyse(layout)
edits, meldungen = read_tros(_markers(raw), raw, elements, CSV, tmp_path)
assert edits == []
assert not (tmp_path / "tro_overrides.ini").read_text(encoding="utf-8").strip()
def test_dialogaenderungen_werden_zu_korrekturen(layout, tmp_path):
elements, _ = layout
raw = _analyse(layout)
markers = _markers(raw)
by_key = {m["KEY"]: m for m in markers}
a = _by_sep(raw, "0012").tro_id
b = _by_sep(raw, "0010").tro_id
by_key[a].update(ID="Weiche_K8", TYPE="2Sep1Swi",
SLOTS="separator1=0012;separator2=;switch1=;scanner1=0010")
markers.remove(by_key[b])
markers.append({"ID": "TRO99", "KEY": "", "TYPE": "1Sep",
"SLOTS": "separator1=0010", "_BLOCK": "TRO_SYM_1Sep"})
edits, meldungen = read_tros(markers, raw, elements, CSV, tmp_path)
by = {(e.section_kind, e.key): e for e in edits}
assert set(by) == {("edit", a), ("edit_add", "TRO99"), ("edit", b)}
assert by[("edit", a)].name == "Weiche_K8"
assert by[("edit", a)].type_name == "2Sep1Swi"
assert by[("edit", a)].slots["separator2"] == PartRef()
assert by[("edit", a)].slots["switch1"] == PartRef()
assert any("scanner1 = 0010 ist ein Separator" in m for m in meldungen)
assert by[("edit", b)].delete
# Lage aus dem CSV hinter der TeileId
assert by[("edit_add", "TRO99")].slots["separator1"].x is not None
assert any("separator2 ist leer" in m for m in meldungen)
# Rundlauf: angewandt, neu beschriftet, wieder gelesen -> dieselben Korrekturen
edited = _analyse(layout, load_overrides(CSV, tmp_path).edits)
again, _ = read_tros(_markers(edited), raw, elements, CSV, tmp_path, write=False)
assert {(e.section_kind, e.key) for e in again} == set(by)
def test_kopierter_marker_wird_neuer_tro(layout, tmp_path):
elements, _ = layout
raw = _analyse(layout)
markers = _markers(raw)
kopie = dict(markers[0], ID="TRO77")
edits, meldungen = read_tros(markers + [kopie], raw, elements, CSV, tmp_path,
write=False)
assert [(e.section_kind, e.key) for e in edits] == [("edit_add", "TRO77")]
assert any("doppelt" in m for m in meldungen)
# ---------------------------------------------------------------------------
# Slot-Listen fuer den Dialog
# ---------------------------------------------------------------------------
def test_emit_lisp_schreibt_slots_je_typ(tmp_path):
text = emit_lisp(tmp_path).read_text(encoding="utf-8")
assert ('("2Sep1Swi" 0 "separator1" "separator2" "switch1" "scanner1" "scanner2")'
in text)
assert '("PinStore_Auto" 19 "separator1" "scanner1")' in text
assert '("switch" "Ausschleus" "Weiche")' in text