6c8c235287
- 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>
251 lines
11 KiB
Python
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
|