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
@@ -118,14 +118,16 @@
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_2_di" bpmnElement="Flow_2">
<di:waypoint x="400" y="360"/>
<di:waypoint x="400" y="310"/>
<di:waypoint x="420" y="360"/>
<di:waypoint x="420" y="310"/>
<di:waypoint x="515" y="310"/>
<di:waypoint x="515" y="335"/>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_3_di" bpmnElement="Flow_3">
<di:waypoint x="515" y="385"/>
<di:waypoint x="515" y="410"/>
<di:waypoint x="680" y="410"/>
<di:waypoint x="660" y="410"/>
<di:waypoint x="660" y="445"/>
<di:waypoint x="680" y="445"/>
<bpmndi:BPMNLabel>
<dc:Bounds x="523" y="389.5" width="30" height="14"/>
@@ -135,6 +137,7 @@
<di:waypoint x="540" y="360"/>
<di:waypoint x="565" y="360"/>
<di:waypoint x="565" y="275"/>
<di:waypoint x="660" y="275"/>
<di:waypoint x="680" y="275"/>
<bpmndi:BPMNLabel>
<dc:Bounds x="537.5" y="366" width="30" height="14"/>
@@ -67,7 +67,7 @@
<bpmndi:BPMNShape id="Gateway_S3_di" bpmnElement="Gateway_S3" isMarkerVisible="true">
<dc:Bounds x="165" y="420" width="50" height="50"/>
<bpmndi:BPMNLabel>
<dc:Bounds x="245" y="390.5" width="90" height="42"/>
<dc:Bounds x="202.5" y="390.5" width="90" height="42"/>
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="SendTask_S4_di" bpmnElement="SendTask_S4">
@@ -102,7 +102,8 @@
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_2_di" bpmnElement="Flow_2">
<di:waypoint x="215" y="330"/>
<di:waypoint x="215" y="395"/>
<di:waypoint x="215" y="350"/>
<di:waypoint x="190" y="350"/>
<di:waypoint x="190" y="395"/>
<di:waypoint x="190" y="420"/>
</bpmndi:BPMNEdge>
@@ -110,6 +111,7 @@
<di:waypoint x="190" y="470"/>
<di:waypoint x="190" y="495"/>
<di:waypoint x="300" y="495"/>
<di:waypoint x="300" y="570"/>
<di:waypoint x="300" y="590"/>
<bpmndi:BPMNLabel>
<dc:Bounds x="198" y="474.5" width="30" height="14"/>
@@ -118,7 +120,8 @@
<bpmndi:BPMNEdge id="Flow_4_di" bpmnElement="Flow_4">
<di:waypoint x="215" y="445"/>
<di:waypoint x="240" y="445"/>
<di:waypoint x="240" y="590"/>
<di:waypoint x="240" y="570"/>
<di:waypoint x="130" y="570"/>
<di:waypoint x="130" y="590"/>
<bpmndi:BPMNLabel>
<dc:Bounds x="212.5" y="451" width="30" height="14"/>
@@ -132,7 +135,8 @@
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_6_di" bpmnElement="Flow_6">
<di:waypoint x="300" y="670"/>
<di:waypoint x="300" y="735"/>
<di:waypoint x="300" y="690"/>
<di:waypoint x="190" y="690"/>
<di:waypoint x="190" y="735"/>
<di:waypoint x="190" y="760"/>
</bpmndi:BPMNEdge>
@@ -140,6 +144,7 @@
<di:waypoint x="190" y="810"/>
<di:waypoint x="190" y="835"/>
<di:waypoint x="130" y="835"/>
<di:waypoint x="130" y="910"/>
<di:waypoint x="130" y="930"/>
<bpmndi:BPMNLabel>
<dc:Bounds x="198" y="814.5" width="30" height="14"/>
@@ -148,7 +153,8 @@
<bpmndi:BPMNEdge id="Flow_8_di" bpmnElement="Flow_8">
<di:waypoint x="215" y="785"/>
<di:waypoint x="240" y="785"/>
<di:waypoint x="240" y="930"/>
<di:waypoint x="240" y="910"/>
<di:waypoint x="300" y="910"/>
<di:waypoint x="300" y="930"/>
<bpmndi:BPMNLabel>
<dc:Bounds x="212.5" y="791" width="30" height="14"/>
@@ -473,14 +473,16 @@
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_5_di" bpmnElement="Flow_5">
<di:waypoint x="970" y="170"/>
<di:waypoint x="970" y="120"/>
<di:waypoint x="990" y="170"/>
<di:waypoint x="990" y="120"/>
<di:waypoint x="1085" y="120"/>
<di:waypoint x="1085" y="145"/>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_6_di" bpmnElement="Flow_6">
<di:waypoint x="1085" y="195"/>
<di:waypoint x="1085" y="220"/>
<di:waypoint x="1250" y="220"/>
<di:waypoint x="1230" y="220"/>
<di:waypoint x="1230" y="170"/>
<di:waypoint x="1250" y="170"/>
<bpmndi:BPMNLabel>
<dc:Bounds x="1093" y="199.5" width="30" height="14"/>
@@ -510,18 +512,21 @@
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_10_di" bpmnElement="Flow_10">
<di:waypoint x="1730" y="170"/>
<di:waypoint x="1750" y="170"/>
<di:waypoint x="1795" y="170"/>
<di:waypoint x="1820" y="170"/>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_11_di" bpmnElement="Flow_11">
<di:waypoint x="1845" y="195"/>
<di:waypoint x="1845" y="220"/>
<di:waypoint x="2010" y="220"/>
<di:waypoint x="1990" y="220"/>
<di:waypoint x="1990" y="170"/>
<di:waypoint x="2010" y="170"/>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_12_di" bpmnElement="Flow_12">
<di:waypoint x="2110" y="170"/>
<di:waypoint x="2110" y="120"/>
<di:waypoint x="2130" y="170"/>
<di:waypoint x="2130" y="120"/>
<di:waypoint x="2225" y="120"/>
<di:waypoint x="2225" y="145"/>
</bpmndi:BPMNEdge>
@@ -540,6 +545,7 @@
<di:waypoint x="2250" y="170"/>
<di:waypoint x="2275" y="170"/>
<di:waypoint x="2275" y="255"/>
<di:waypoint x="2370" y="255"/>
<di:waypoint x="2390" y="255"/>
<bpmndi:BPMNLabel>
<dc:Bounds x="2247.5" y="176" width="30" height="14"/>
@@ -548,7 +554,8 @@
<bpmndi:BPMNEdge id="Flow_15_di" bpmnElement="Flow_15">
<di:waypoint x="2415" y="110"/>
<di:waypoint x="2415" y="135"/>
<di:waypoint x="2580" y="135"/>
<di:waypoint x="2560" y="135"/>
<di:waypoint x="2560" y="85"/>
<di:waypoint x="2580" y="85"/>
<bpmndi:BPMNLabel>
<dc:Bounds x="2423" y="114.5" width="30" height="14"/>
@@ -566,18 +573,21 @@
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_17_di" bpmnElement="Flow_17">
<di:waypoint x="2680" y="85"/>
<di:waypoint x="2700" y="85"/>
<di:waypoint x="2745" y="85"/>
<di:waypoint x="2770" y="85"/>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_18_di" bpmnElement="Flow_18">
<di:waypoint x="2795" y="110"/>
<di:waypoint x="2795" y="135"/>
<di:waypoint x="2960" y="135"/>
<di:waypoint x="2940" y="135"/>
<di:waypoint x="2940" y="85"/>
<di:waypoint x="2960" y="85"/>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_19_di" bpmnElement="Flow_19">
<di:waypoint x="3060" y="85"/>
<di:waypoint x="3060" y="35"/>
<di:waypoint x="3080" y="85"/>
<di:waypoint x="3080" y="35"/>
<di:waypoint x="3175" y="35"/>
<di:waypoint x="3175" y="60"/>
</bpmndi:BPMNEdge>
@@ -605,7 +615,8 @@
<bpmndi:BPMNEdge id="Flow_22_di" bpmnElement="Flow_22">
<di:waypoint x="3365" y="110"/>
<di:waypoint x="3365" y="135"/>
<di:waypoint x="3530" y="135"/>
<di:waypoint x="3510" y="135"/>
<di:waypoint x="3510" y="625"/>
<di:waypoint x="3530" y="625"/>
<bpmndi:BPMNLabel>
<dc:Bounds x="3373" y="114.5" width="144" height="14"/>
@@ -615,6 +626,7 @@
<di:waypoint x="3390" y="85"/>
<di:waypoint x="3415" y="85"/>
<di:waypoint x="3415" y="805"/>
<di:waypoint x="3510" y="805"/>
<di:waypoint x="3530" y="805"/>
<bpmndi:BPMNLabel>
<dc:Bounds x="3387.5" y="91" width="30" height="14"/>
@@ -628,13 +640,15 @@
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_25_di" bpmnElement="Flow_25">
<di:waypoint x="3820" y="85"/>
<di:waypoint x="3820" y="120"/>
<di:waypoint x="3840" y="85"/>
<di:waypoint x="3840" y="120"/>
<di:waypoint x="3935" y="120"/>
<di:waypoint x="3935" y="145"/>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_26_di" bpmnElement="Flow_26">
<di:waypoint x="3630" y="805"/>
<di:waypoint x="3885" y="805"/>
<di:waypoint x="3650" y="805"/>
<di:waypoint x="3650" y="170"/>
<di:waypoint x="3885" y="170"/>
<di:waypoint x="3910" y="170"/>
</bpmndi:BPMNEdge>
@@ -649,7 +663,8 @@
<bpmndi:BPMNEdge id="Flow_28_di" bpmnElement="Flow_28">
<di:waypoint x="3365" y="280"/>
<di:waypoint x="3365" y="305"/>
<di:waypoint x="3530" y="305"/>
<di:waypoint x="3510" y="305"/>
<di:waypoint x="3510" y="975"/>
<di:waypoint x="3530" y="975"/>
<bpmndi:BPMNLabel>
<dc:Bounds x="3373" y="284.5" width="30" height="14"/>
@@ -659,6 +674,7 @@
<di:waypoint x="3390" y="255"/>
<di:waypoint x="3415" y="255"/>
<di:waypoint x="3415" y="170"/>
<di:waypoint x="3510" y="170"/>
<di:waypoint x="3530" y="170"/>
<bpmndi:BPMNLabel>
<dc:Bounds x="3387.5" y="261" width="132" height="14"/>
@@ -666,13 +682,15 @@
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_30_di" bpmnElement="Flow_30">
<di:waypoint x="3630" y="975"/>
<di:waypoint x="3630" y="205"/>
<di:waypoint x="3650" y="975"/>
<di:waypoint x="3650" y="205"/>
<di:waypoint x="3745" y="205"/>
<di:waypoint x="3745" y="230"/>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_31_di" bpmnElement="Flow_31">
<di:waypoint x="3630" y="170"/>
<di:waypoint x="3695" y="170"/>
<di:waypoint x="3650" y="170"/>
<di:waypoint x="3650" y="255"/>
<di:waypoint x="3695" y="255"/>
<di:waypoint x="3720" y="255"/>
</bpmndi:BPMNEdge>
@@ -697,7 +715,8 @@
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_35_di" bpmnElement="Flow_35">
<di:waypoint x="2680" y="255"/>
<di:waypoint x="2680" y="205"/>
<di:waypoint x="2700" y="255"/>
<di:waypoint x="2700" y="205"/>
<di:waypoint x="2795" y="205"/>
<di:waypoint x="2795" y="230"/>
</bpmndi:BPMNEdge>
@@ -715,6 +734,7 @@
<bpmndi:BPMNEdge id="Flow_37_di" bpmnElement="Flow_37">
<di:waypoint x="2820" y="255"/>
<di:waypoint x="2845" y="255"/>
<di:waypoint x="2940" y="255"/>
<di:waypoint x="2960" y="255"/>
<bpmndi:BPMNLabel>
<dc:Bounds x="2817.5" y="261" width="30" height="14"/>
@@ -722,6 +742,7 @@
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_38_di" bpmnElement="Flow_38">
<di:waypoint x="3060" y="255"/>
<di:waypoint x="3080" y="255"/>
<di:waypoint x="3125" y="255"/>
<di:waypoint x="3150" y="255"/>
</bpmndi:BPMNEdge>
@@ -735,7 +756,8 @@
<bpmndi:BPMNEdge id="Flow_40_di" bpmnElement="Flow_40">
<di:waypoint x="4315" y="195"/>
<di:waypoint x="4315" y="220"/>
<di:waypoint x="4480" y="220"/>
<di:waypoint x="4460" y="220"/>
<di:waypoint x="4460" y="170"/>
<di:waypoint x="4480" y="170"/>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_41_di" bpmnElement="Flow_41">
+17 -19
View File
@@ -615,6 +615,9 @@ GATEWAY_OUTWARD = {'N': (0, -1), 'S': (0, 1), 'E': (1, 0), 'W': (-1, 0)}
# Halbe Kantenlaenge der Raute (Standardgroesse 50x50) reicht, um deren Kontur
# in jedem Fall sicher zu verlassen, bevor die Kante abbiegt.
GATEWAY_STUB = 25
# Gerades Stück an einer Aufgabe, wenn das andere Ende eine Raute ist (#23); passt
# in den kleinsten Gang (70) zwischen zwei Spalten bzw. Zeilen, auch von beiden Seiten.
RECHTECK_STUB = 20
def _dedupe_consecutive(points):
@@ -688,35 +691,30 @@ def orthogonal_points(ox, oy, src_side, tx, ty, tgt_side,
dieses Schritts (die einzige garantiert freie Koordinate) beim nächsten
Abbiegen zunächst unverändert bleibt. Sonst würde die Kante beim
Umbiegen zum Ziel auf demselben Weg zurück durch die Raute laufen, den
sie gerade erst verlassen hat."""
sie gerade erst verlassen hat.
Ist nur ein Ende eine Raute, bekommt auch das andere Ende (Aufgabe) einen
kurzen geraden Schritt senkrecht zu seiner Seite (RECHTECK_STUB); danach
wird wie zwischen zwei Rauten von Stub zu Stub geführt. Sonst liefe die
Kante am Aufgaben-Ende parallel zu dessen Kante, wenn die Anschlusspunkte
auf verschiedenen Achsen liegen (#23)."""
gemischt = src_is_gateway != tgt_is_gateway
sx, sy = ox, oy
if src_is_gateway:
if src_is_gateway or gemischt:
dx, dy = GATEWAY_OUTWARD[src_side]
sx, sy = ox + dx * GATEWAY_STUB, oy + dy * GATEWAY_STUB
stub = GATEWAY_STUB if src_is_gateway else RECHTECK_STUB
sx, sy = ox + dx * stub, oy + dy * stub
ex, ey = tx, ty
if tgt_is_gateway:
if tgt_is_gateway or gemischt:
dx, dy = GATEWAY_OUTWARD[tgt_side]
ex, ey = tx + dx * GATEWAY_STUB, ty + dy * GATEWAY_STUB
stub = GATEWAY_STUB if tgt_is_gateway else RECHTECK_STUB
ex, ey = tx + dx * stub, ty + dy * stub
src_axis = 'v' if src_side in ('N', 'S') else 'h'
tgt_axis = 'v' if tgt_side in ('N', 'S') else 'h'
if not src_is_gateway and not tgt_is_gateway:
points = _orthogonal_middle(ox, oy, src_side, tx, ty, tgt_side)
elif src_is_gateway and not tgt_is_gateway:
# sy/sx (die Stub-Achse) fixieren, solange die andere Koordinate zum
# Ziel hin schwenkt - erst danach auf der Stub-Achse zum Ziel.
if src_axis == 'v':
points = [(ox, oy), (sx, sy), (tx, sy), (tx, ty)]
else:
points = [(ox, oy), (sx, sy), (sx, ty), (tx, ty)]
elif tgt_is_gateway and not src_is_gateway:
# Spiegelbildlich: erst auf der Stub-Achse des Ziels ankommen, erst
# danach dorthin schwenken.
if tgt_axis == 'v':
points = [(ox, oy), (ox, ey), (ex, ey), (tx, ty)]
else:
points = [(ox, oy), (ex, oy), (ex, ey), (tx, ty)]
else:
# Beide Enden sind Rauten: von der Stub-Achse des Starts direkt auf
# die Stub-Achse des Ziels schwenken, jede nur einmal verlassen.
+19
View File
@@ -655,6 +655,25 @@ def test_anmerkung_waechst_mit_ihrem_text():
assert kurz_w == lang_w and lang_h > kurz_h
@pytest.mark.parametrize("src_side, tgt_side, src_gw, tgt_gw", [
('E', 'N', False, True), # Aufgabe (Ostseite) -> Gateway (Nordpunkt), Lanes-Layout
('S', 'W', True, False), # Gateway (Südpunkt) -> Aufgabe (Westseite), Lanes-Layout
('S', 'E', False, True), # Aufgabe (Südseite) -> Gateway (Ostpunkt), Layout ohne Lanes
('W', 'N', True, False), # Gateway (Westpunkt) -> Aufgabe (Nordseite), Layout ohne Lanes
])
def test_kante_zwischen_aufgabe_und_gateway_setzt_an_beiden_enden_rechtwinklig_an(src_side, tgt_side, src_gw, tgt_gw):
"""Prüft, dass eine Kante zwischen Aufgabe und Gateway an beiden Enden rechtwinklig zur jeweiligen Seite ansetzt,
auch wenn die Seiten auf verschiedenen Achsen liegen (Issue #23)."""
punkte = m.orthogonal_points(100, 100, src_side, 400, 300, tgt_side, src_is_gateway=src_gw, tgt_is_gateway=tgt_gw)
def achse(seite):
return 'waagrecht' if seite in ('E', 'W') else 'senkrecht'
def verlauf(a, b):
return 'waagrecht' if abs(a[1] - b[1]) < 1e-6 else 'senkrecht'
assert verlauf(punkte[0], punkte[1]) == achse(src_side)
assert verlauf(punkte[-2], punkte[-1]) == achse(tgt_side)
assert all(verlauf(a, b) and (a[0] == b[0] or a[1] == b[1]) for a, b in zip(punkte, punkte[1:]))
def test_ausgabe_ist_wohlgeformtes_xml(tmp_path):
"""Prüft, dass die erzeugte .bpmn-Datei sich als valides XML parsen laesst."""
src = tmp_path / 'prozess.md'
+3
View File
@@ -31,6 +31,9 @@ Skills sind übernommen, alles QGIS- und Plugin-Spezifische ist entfernt. Kern i
## Erledigt
- 25.09.2026 **Kanten setzen an Aufgaben rechtwinklig an** (#23): Stub am Aufgaben-Ende auch bei Kanten zu
bzw. von Gateways; 17 Kantenenden in den Beispielen korrigiert.
- 25.09.2026 **Beschriftungen überlappen nicht mehr** (#14): Gateway-Beschriftungen mit fester Position (45°,
sonst nächste freie Stelle), Anmerkungen rechts neben dem Pool in passender Größe, Kantenbeschriftungen
waagrecht aus Gateways unter der Linie.
+7
View File
@@ -5,6 +5,13 @@ Kurz, konkret, mit Datum und Quelle. Neue Einträge oben in der passenden Rubrik
## BPMN und PICTURE (Skill sn-bpmn, md_to_bpmn.py)
- **Auch das Aufgaben-Ende einer Gateway-Kante braucht ein gerades Stück.** Das Gateway-Ende bekommt einen festen
Anschlusspunkt (N/O/S/W), das Aufgaben-Ende die Layout-Seite (mit Lanes O/W, ohne Lanes N/S). Liegen beide auf
verschiedenen Achsen, lief die Kante früher direkt an der Aufgabenkante entlang. Jetzt bekommt die Aufgabe einen
Stub (`RECHTECK_STUB = 20`) senkrecht zu ihrer Seite, danach geht es wie zwischen zwei Rauten von Stub zu Stub.
Geprüft mit `test_kanten_setzen_an_aufgaben_rechtwinklig_an` über alle Beispiele. Im Belastungstest aus #12 sind
378 schräge Anschlüsse verschwunden. Dafür laufen in 4 künstlichen Fällen mit Querverbindung zwischen Zweigen
Kanten durch eine Aufgabe, in 2 anderen nicht mehr (Art von #22). (#23, 25.09.2026)
- **PICTURE übernimmt Beschriftungspositionen und Kästchengrößen aus der Datei.** Ohne `BPMNLabel` setzt es die
Gateway-Beschriftung auf 180° (unter die Raute), wo der südliche Pfad und dessen Beschriftung sie schneiden. Im
PICTURE-Editor steht sie auf 45°. Der Konverter setzt die Position deshalb selbst: 45°, sonst 45° rechts vom
+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")