Kanten zwischen Gateways umgehen die Ziel-Raute (#8)
- orthogonal_points(): Liegt bei Gateway -> Gateway der Stub des Starts schon jenseits des Ziel-Anschlusspunkts (z. B. Südpunkt unten, Ziel höher mit Nordpunkt), führt die Kante seitlich an der Ziel-Raute vorbei. Gleiches für den waagrechten Spiegelfall (Ost -> West). - _ausweichachse(): Die Ausweichachse liegt im freien Gang direkt vor der Ziel-Spalte (AUSWEICHABSTAND 85), nicht in der Mitte zwischen Quelle und Ziel – dort stand im Referenzmodell die Aufgabe S43. - Referenzmodell neu erzeugt: fünf Kanten korrigiert (vorher liefen alle fünf durch ein Gateway, gemeldet war nur S12->S13), dabei verschwindet auch die Kreuzung Flow_13/S40. Syntaxbeispiele unverändert. - xfail für referenz_gewannenfestlegung entfernt; drei neue Tests in test_md_to_bpmn.py. - Fallstricke: Ausweichachse; Strukturtests melden nur die erste Kreuzung. - Tests: python -m pytest -q -> 92 bestanden. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -486,7 +486,8 @@
|
||||
<bpmndi:BPMNEdge id="Flow_13_di" bpmnElement="Flow_13">
|
||||
<di:waypoint x="2225" y="195"/>
|
||||
<di:waypoint x="2225" y="220"/>
|
||||
<di:waypoint x="2415" y="220"/>
|
||||
<di:waypoint x="2330" y="220"/>
|
||||
<di:waypoint x="2330" y="35"/>
|
||||
<di:waypoint x="2415" y="35"/>
|
||||
<di:waypoint x="2415" y="60"/>
|
||||
<bpmndi:BPMNLabel>
|
||||
@@ -541,7 +542,8 @@
|
||||
<bpmndi:BPMNEdge id="Flow_20_di" bpmnElement="Flow_20">
|
||||
<di:waypoint x="3175" y="110"/>
|
||||
<di:waypoint x="3175" y="135"/>
|
||||
<di:waypoint x="3365" y="135"/>
|
||||
<di:waypoint x="3280" y="135"/>
|
||||
<di:waypoint x="3280" y="35"/>
|
||||
<di:waypoint x="3365" y="35"/>
|
||||
<di:waypoint x="3365" y="60"/>
|
||||
<bpmndi:BPMNLabel>
|
||||
@@ -597,7 +599,8 @@
|
||||
<bpmndi:BPMNEdge id="Flow_27_di" bpmnElement="Flow_27">
|
||||
<di:waypoint x="3935" y="195"/>
|
||||
<di:waypoint x="3935" y="220"/>
|
||||
<di:waypoint x="4125" y="220"/>
|
||||
<di:waypoint x="4040" y="220"/>
|
||||
<di:waypoint x="4040" y="120"/>
|
||||
<di:waypoint x="4125" y="120"/>
|
||||
<di:waypoint x="4125" y="145"/>
|
||||
</bpmndi:BPMNEdge>
|
||||
@@ -641,7 +644,8 @@
|
||||
<bpmndi:BPMNEdge id="Flow_33_di" bpmnElement="Flow_33">
|
||||
<di:waypoint x="4125" y="195"/>
|
||||
<di:waypoint x="4125" y="220"/>
|
||||
<di:waypoint x="4315" y="220"/>
|
||||
<di:waypoint x="4230" y="220"/>
|
||||
<di:waypoint x="4230" y="120"/>
|
||||
<di:waypoint x="4315" y="120"/>
|
||||
<di:waypoint x="4315" y="145"/>
|
||||
</bpmndi:BPMNEdge>
|
||||
@@ -658,7 +662,8 @@
|
||||
<bpmndi:BPMNEdge id="Flow_36_di" bpmnElement="Flow_36">
|
||||
<di:waypoint x="2795" y="280"/>
|
||||
<di:waypoint x="2795" y="305"/>
|
||||
<di:waypoint x="3175" y="305"/>
|
||||
<di:waypoint x="3090" y="305"/>
|
||||
<di:waypoint x="3090" y="205"/>
|
||||
<di:waypoint x="3175" y="205"/>
|
||||
<di:waypoint x="3175" y="230"/>
|
||||
<bpmndi:BPMNLabel>
|
||||
|
||||
@@ -580,6 +580,21 @@ def _orthogonal_middle(ox, oy, src_side, tx, ty, tgt_side):
|
||||
return [(ox, oy), (tx, oy), (tx, ty)]
|
||||
|
||||
|
||||
# Abstand der Ausweichachse von der Mitte der Ziel-Spalte bzw. -Zeile: halbe
|
||||
# Breite der größten Elemente (Aufgabe 100x80) plus halber kleinster Gang
|
||||
# zwischen zwei Spalten/Zeilen (170 - 100 = 70). Der Gang direkt vor dem Ziel
|
||||
# ist damit in beiden Layouts frei; die Mitte zwischen Quelle und Ziel nicht,
|
||||
# dort kann eine Spalte mit einer Aufgabe liegen.
|
||||
AUSWEICHABSTAND = 85
|
||||
|
||||
|
||||
def _ausweichachse(quelle, ziel):
|
||||
"""Koordinate im freien Gang vor der Ziel-Spalte (bzw. -Zeile), auf der Seite
|
||||
der Quelle: dort läuft eine Kante an der Ziel-Raute vorbei, ohne ein anderes
|
||||
Element zu schneiden."""
|
||||
return ziel + AUSWEICHABSTAND if quelle > ziel else ziel - AUSWEICHABSTAND
|
||||
|
||||
|
||||
def orthogonal_points(ox, oy, src_side, tx, ty, tgt_side,
|
||||
src_is_gateway=False, tgt_is_gateway=False):
|
||||
"""Baut einen rechtwinkligen Kantenverlauf zwischen zwei Anschlusspunkten, der
|
||||
@@ -627,7 +642,19 @@ def orthogonal_points(ox, oy, src_side, tx, ty, tgt_side,
|
||||
else:
|
||||
# Beide Enden sind Rauten: von der Stub-Achse des Starts direkt auf
|
||||
# die Stub-Achse des Ziels schwenken, jede nur einmal verlassen.
|
||||
if src_axis == 'v':
|
||||
# Liegt der Stub des Starts schon jenseits des Ziel-Anschlusspunkts
|
||||
# (z. B. Ankunft am Nordpunkt eines höher liegenden Ziels), liefe das
|
||||
# letzte gerade Stück durch die Ziel-Raute - dann seitlich daran
|
||||
# vorbei über eine freie Ausweichachse (Issue #8).
|
||||
if src_axis == 'v' and tgt_axis == 'v' and (
|
||||
(tgt_side == 'N' and sy > ty) or (tgt_side == 'S' and sy < ty)):
|
||||
mx = _ausweichachse(sx, ex)
|
||||
points = [(ox, oy), (sx, sy), (mx, sy), (mx, ey), (ex, ey), (tx, ty)]
|
||||
elif src_axis == 'h' and tgt_axis == 'h' and (
|
||||
(tgt_side == 'W' and sx > tx) or (tgt_side == 'E' and sx < tx)):
|
||||
my = _ausweichachse(sy, ey)
|
||||
points = [(ox, oy), (sx, sy), (sx, my), (ex, my), (ex, ey), (tx, ty)]
|
||||
elif src_axis == 'v':
|
||||
points = [(ox, oy), (sx, sy), (ex, sy), (ex, ey), (tx, ty)]
|
||||
else:
|
||||
points = [(ox, oy), (sx, sy), (sx, ey), (ex, ey), (tx, ty)]
|
||||
|
||||
@@ -353,6 +353,51 @@ def test_kante_fuehrt_nicht_durchs_innere_eines_gateways():
|
||||
assert _kante_kreuzt_gateway(defs, m.NS) is None
|
||||
|
||||
|
||||
def _streckenzug_kreuzt_raute(punkte, raute):
|
||||
"""True, wenn ein Stück des Streckenzugs durchs Innere der Raute (x, y, w, h) läuft."""
|
||||
for (x1, y1), (x2, y2) in zip(punkte, punkte[1:]):
|
||||
for s in range(41):
|
||||
t = s / 40
|
||||
if _liegt_in_rautenflaeche(x1 + (x2 - x1) * t, y1 + (y2 - y1) * t, *raute):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def test_kante_zwischen_gateways_zum_nordpunkt_eines_hoeheren_ziels_umgeht_die_raute():
|
||||
"""Prüft, dass eine Kante vom Südpunkt eines Gateways zum Nordpunkt eines höher liegenden Gateways
|
||||
seitlich an der Ziel-Raute vorbei nach oben führt statt senkrecht durch sie hindurch (Issue #8)."""
|
||||
quelle, ziel = (2200, 145, 50, 50), (2390, 60, 50, 50) # Lage wie S12 -> S13 im Referenzmodell
|
||||
punkte = m.orthogonal_points(2225, 195, 'S', 2415, 60, 'N', src_is_gateway=True, tgt_is_gateway=True)
|
||||
assert punkte[0] == (2225, 195) and punkte[-1] == (2415, 60)
|
||||
assert not _streckenzug_kreuzt_raute(punkte, ziel)
|
||||
assert not _streckenzug_kreuzt_raute(punkte, quelle)
|
||||
assert all(a[0] == b[0] or a[1] == b[1] for a, b in zip(punkte, punkte[1:])), "nicht rechtwinklig"
|
||||
|
||||
|
||||
def test_ausweichweg_zwischen_gateways_meidet_die_spalte_dazwischen():
|
||||
"""Prüft, dass der seitliche Umweg um die Ziel-Raute im freien Gang direkt vor der Ziel-Spalte liegt und
|
||||
nicht mitten zwischen Quelle und Ziel, wo in einer Spalte dazwischen eine Aufgabe stehen kann."""
|
||||
aufgabe, ziel = (2960, 215, 100, 80), (3150, 230, 50, 50) # Lage wie S43 und S44 im Referenzmodell
|
||||
punkte = m.orthogonal_points(2795, 280, 'S', 3175, 230, 'N', src_is_gateway=True, tgt_is_gateway=True)
|
||||
assert not _streckenzug_kreuzt_raute(punkte, ziel)
|
||||
x, y, w, h = aufgabe
|
||||
for (x1, y1), (x2, y2) in zip(punkte, punkte[1:]):
|
||||
for s in range(41):
|
||||
px, py = x1 + (x2 - x1) * s / 40, y1 + (y2 - y1) * s / 40
|
||||
assert not (x < px < x + w and y < py < y + h), f"Kante läuft bei ({px:g}, {py:g}) durch die Aufgabe"
|
||||
|
||||
|
||||
def test_kante_zwischen_gateways_zum_westpunkt_eines_linken_ziels_umgeht_die_raute():
|
||||
"""Prüft den waagrechten Spiegelfall: vom Ostpunkt eines Gateways zum Westpunkt eines weiter links
|
||||
liegenden Gateways führt die Kante ober- oder unterhalb an der Ziel-Raute vorbei."""
|
||||
quelle, ziel = (400, 300, 50, 50), (200, 100, 50, 50)
|
||||
punkte = m.orthogonal_points(450, 325, 'E', 200, 125, 'W', src_is_gateway=True, tgt_is_gateway=True)
|
||||
assert punkte[0] == (450, 325) and punkte[-1] == (200, 125)
|
||||
assert not _streckenzug_kreuzt_raute(punkte, ziel)
|
||||
assert not _streckenzug_kreuzt_raute(punkte, quelle)
|
||||
assert all(a[0] == b[0] or a[1] == b[1] for a, b in zip(punkte, punkte[1:])), "nicht rechtwinklig"
|
||||
|
||||
|
||||
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'
|
||||
|
||||
@@ -31,6 +31,10 @@ Skills sind übernommen, alles QGIS- und Plugin-Spezifische ist entfernt. Kern i
|
||||
|
||||
## Erledigt
|
||||
|
||||
- 25.09.2026 **Keine Kanten mehr durch Gateways im Referenzmodell** (#8): Gateway → Gateway mit Ziel hinter
|
||||
dem Start-Stub weicht im Gang vor der Ziel-Spalte aus; fünf Kanten der Gewannenfestlegung korrigiert, `xfail`
|
||||
entfernt. Offen: Rücksprungkanten und Nachrichtenflüsse durch Aufgaben, Beschriftungsüberlappungen.
|
||||
|
||||
- 25.09.2026 **Prozessbibliothek als eigenes Repository** (#9, ADR 0004): `prozesse/` ist aus Projektwissen
|
||||
entfernt, die Modelle liegen im Klon `Prozessbibliothek/` (`workspace.toml`). `pruefe_prozesse.py` und
|
||||
`einlesen.py` suchen dort; Regeln in `docs/leitfaden/08_prozessbibliothek.md`.
|
||||
|
||||
@@ -5,10 +5,15 @@ Kurz, konkret, mit Datum und Quelle. Neue Einträge oben in der passenden Rubrik
|
||||
|
||||
## BPMN und PICTURE (Skill sn-bpmn, md_to_bpmn.py)
|
||||
|
||||
- **Kanten durch Gateways sind noch nicht ganz behoben.** Im Referenzmodell Gewannenfestlegung läuft die Kante
|
||||
S12→S13 (Gateway → Gateway, Ziel oberhalb der Quelle, Ankunft am Nordpunkt) senkrecht durch die Raute von S13.
|
||||
Der Fix vom 21.09.2026 deckt diese Lage nicht ab. Erfasst als Issue #8 und als `xfail` in
|
||||
`tests/test_referenzmodelle.py`. (23.09.2026)
|
||||
- **Gateway → Gateway mit Ziel „hinter“ dem Start-Stub braucht einen seitlichen Umweg.** Liegt der Stub des
|
||||
Start-Gateways schon jenseits des Ziel-Anschlusspunkts (z. B. Südpunkt unten, Ziel höher mit Nordpunkt), läuft
|
||||
das letzte gerade Stück durch die Ziel-Raute. `orthogonal_points()` weicht dann über `_ausweichachse()` aus.
|
||||
Die Achse liegt **im Gang direkt vor der Ziel-Spalte** (Abstand 85 zur Mitte), **nicht in der Mitte zwischen
|
||||
Quelle und Ziel**: Liegen die beiden zwei Spalten auseinander, steht dort eine Aufgabe (im Referenzmodell S43).
|
||||
(#8, 25.09.2026)
|
||||
- **Die Strukturtests melden nur die erste Kreuzung.** `_kante_kreuzt_gateway()` bricht beim ersten Fund ab. Im
|
||||
Referenzmodell stand deshalb nur S12→S13 im Issue, tatsächlich liefen fünf Kanten durch Gateways. Nach einem
|
||||
Layout-Fix alle Kanten gegen alle Elemente prüfen, nicht nur den gemeldeten Fall. (#8, 25.09.2026)
|
||||
- **Zusammenführende Gateways brauchen eine Beschriftung mit Zuständigkeit**, sonst landen sie in der Lane
|
||||
„Nicht zugeordnet“ (`split_lane()` braucht Text nach dem Doppelpunkt). In BPMN bleiben Zusammenführungen
|
||||
üblicherweise unbeschriftet – ein Fall für eine spätere Konverteränderung. (23.09.2026, Referenzmodell)
|
||||
|
||||
@@ -21,9 +21,7 @@ GATEWAYS = ("exclusiveGateway", "inclusiveGateway", "parallelGateway", "eventBas
|
||||
|
||||
# Bekannte, noch nicht behobene Konverterfehler je Referenzmodell. strict=True: Sobald der Fehler behoben ist,
|
||||
# schlägt der Test als XPASS fehl – dann den Eintrag hier entfernen. Nie verwenden, um neue Fehler zu verstecken.
|
||||
BEKANNTE_KANTEN_DURCH_GATEWAY = {
|
||||
"referenz_gewannenfestlegung": "Issue #8: Kante S12→S13 (Ja) läuft senkrecht durch die Raute von S13",
|
||||
}
|
||||
BEKANNTE_KANTEN_DURCH_GATEWAY = {}
|
||||
|
||||
|
||||
def mit_bekannten_fehlern(bekannt):
|
||||
|
||||
Reference in New Issue
Block a user