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'
|
||||
|
||||
Reference in New Issue
Block a user