Kanten an Aufgaben rechtwinklig ansetzen (#23)

- orthogonal_points(): Ist nur ein Ende eine Raute, bekommt auch das
  Aufgaben-Ende einen geraden Stub senkrecht zu seiner Seite
  (RECHTECK_STUB = 20); danach wie zwischen zwei Rauten von Stub zu
  Stub. Die beiden Sonderzweige für "nur Quelle/nur Ziel ist Gateway"
  entfallen.
- Beispiele neu erzeugt: 17 Kantenenden (2 + 2 + 13), die parallel zur
  Aufgabenkante ansetzten, sind korrigiert.
- Strukturtest test_kanten_setzen_an_aufgaben_rechtwinklig_an über alle
  Beispiele und Einzeltest für vier Achsen-Kombinationen.
- Belastungstest (126 Fälle): schräge Anschlüsse 378 -> 0; Kreuzungen
  22 -> 24, alle in Fällen mit Querverbindung zwischen Zweigen (#22).
- Tests: python -m pytest -q -> 124 bestanden, 1 übersprungen.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-09-25 18:39:22 +02:00
co-authored by Claude Opus 5.5
parent c9b794a123
commit b53828374e
8 changed files with 138 additions and 41 deletions
+39
View File
@@ -236,6 +236,45 @@ def test_anmerkung_ist_gross_genug_fuer_ihren_text(md):
assert h >= zeilen * 16, f"{anm.get('id')}: {len(text)} Zeichen brauchen etwa {zeilen} Zeilen, Höhe nur {h:g}"
RECHTECKE = HINDERNISSE[len(GATEWAYS + EREIGNISSE):] # Aufgaben, Teilprozesse, Datenelemente
def schraege_anschluesse(defs):
"""Alle Kantenenden an Rechteck-Elementen, deren erstes bzw. letztes Stück nicht rechtwinklig zu der Elementkante
verläuft, auf der der Anschlusspunkt liegt (Ost/West verlangt waagrecht, Nord/Süd senkrecht)."""
art = {el.get("id"): el.tag.rpartition("}")[2] for el in defs.iter() if el.get("id")}
enden = {el.get("id"): (el.get("sourceRef"), el.get("targetRef"))
for el in defs.iter() if art.get(el.get("id")) in KANTEN}
formen = {s.get("bpmnElement"): _box(s) for s in defs.iter(f"{{{m.NS['bpmndi']}}}BPMNShape")}
funde = []
for edge in defs.iter(f"{{{m.NS['bpmndi']}}}BPMNEdge"):
kante = edge.get("bpmnElement")
punkte = [(float(wp.get("x")), float(wp.get("y"))) for wp in edge.findall(f"{{{m.NS['di']}}}waypoint")]
if kante not in enden or len(punkte) < 2:
continue
for element, (a, b) in ((enden[kante][0], punkte[:2]), (enden[kante][1], punkte[:-3:-1])):
if art.get(element) not in RECHTECKE:
continue
x, y, w, h = formen[element]
if abs(a[0] - x) < 0.5 or abs(a[0] - x - w) < 0.5:
rechtwinklig = abs(a[1] - b[1]) < 1e-6 # Ost/West: waagrecht
elif abs(a[1] - y) < 0.5 or abs(a[1] - y - h) < 0.5:
rechtwinklig = abs(a[0] - b[0]) < 1e-6 # Nord/Süd: senkrecht
else:
rechtwinklig = False # Anschluss nicht auf dem Rand
if not rechtwinklig:
funde.append((kante, element))
return sorted(funde)
@pytest.mark.parametrize("md", MODELLE, ids=lambda p: p.stem)
def test_kanten_setzen_an_aufgaben_rechtwinklig_an(md):
"""Prüft, dass jede Kante an einer Aufgabe (bzw. einem anderen Rechteck-Element) rechtwinklig zu der Kante ansetzt,
auf der ihr Anschlusspunkt liegt, statt an ihr entlangzulaufen (Issue #23)."""
funde = schraege_anschluesse(baue(md))
assert funde == [], "nicht rechtwinklig (Kante, Element): " + ", ".join(f"{k} → {e}" for k, e in funde)
def test_kreuzungspruefung_findet_eine_kante_durch_eine_aufgabe():
"""Prüft die Prüfung selbst: eine absichtlich durch eine Aufgabe gelegte Kante wird gefunden, eine daneben nicht."""
defs = baue(SKILL / "beispiele" / "beispiel_prozess.md")