Beschriftungen und Anmerkungen ohne Überlappung setzen (#14)
- Gateway-Beschriftungen bekommen eine feste Position (BPMNLabel), weil PICTURE sie aus der Datei übernimmt: 45° wie im PICTURE-Editor, sonst 45° rechts vom Ost-Stub, 315°, 135°, 225° – die erste Stelle ohne Kante, Kantenbeschriftung oder Element. Gesetzt nach allen Kanten. - Kantenbeschriftungen waagrecht aus einem Gateway stehen unter der Linie (darüber steht die Gateway-Beschriftung). - Anmerkungen rechts neben dem Pool untereinander, Höhe passend zum Text (text_zeilen(), anmerkung_groesse()); Pool ohne Anmerkungen bemessen. - Beispiele neu erzeugt. Alle 17 Gateway-Beschriftungen auf 45° bzw. 45° rechts vom Stub, keine im Lane-Kopf oder in einer Nachbar-Lane. - Strukturtests für alle Beispiele (Beschriftung ungeschnitten, in der eigenen Lane; Anmerkungen ohne Überlappung, neben dem Pool, groß genug) und drei Einzeltests. - Tests: python -m pytest -q -> 117 bestanden, 1 übersprungen (leere Parameterliste der bekannten Kreuzungen). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -108,6 +108,11 @@ Jeder Schritt ist eine Zeile der Form:
|
||||
|
||||
Vollständige Liste inkl. Bedeutung: `Elementkatalog.md`.
|
||||
|
||||
Hinweise stehen im Diagramm rechts neben dem Pool untereinander, in einem
|
||||
Kästchen passend zur Textlänge. Gateway-Beschriftungen stehen oben rechts an
|
||||
der Raute (wie im PICTURE-Editor), bei belegtem Platz an der nächsten freien
|
||||
Stelle.
|
||||
|
||||
### Typisierte Ereignisse
|
||||
|
||||
Start-, Zwischen- und Endereignisse können ein zweites Wort für die Art des
|
||||
|
||||
@@ -91,6 +91,9 @@
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="Gateway_S3_di" bpmnElement="Gateway_S3" isMarkerVisible="true">
|
||||
<dc:Bounds x="490" y="335" width="50" height="50"/>
|
||||
<bpmndi:BPMNLabel>
|
||||
<dc:Bounds x="570" y="305.5" width="90" height="42"/>
|
||||
</bpmndi:BPMNLabel>
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="SendTask_S4_di" bpmnElement="SendTask_S4">
|
||||
<dc:Bounds x="680" y="235" width="100" height="80"/>
|
||||
@@ -134,7 +137,7 @@
|
||||
<di:waypoint x="565" y="275"/>
|
||||
<di:waypoint x="680" y="275"/>
|
||||
<bpmndi:BPMNLabel>
|
||||
<dc:Bounds x="537.5" y="340" width="30" height="14"/>
|
||||
<dc:Bounds x="537.5" y="366" width="30" height="14"/>
|
||||
</bpmndi:BPMNLabel>
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge id="Flow_5_di" bpmnElement="Flow_5">
|
||||
|
||||
@@ -66,6 +66,9 @@
|
||||
</bpmndi:BPMNShape>
|
||||
<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"/>
|
||||
</bpmndi:BPMNLabel>
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="SendTask_S4_di" bpmnElement="SendTask_S4">
|
||||
<dc:Bounds x="80" y="590" width="100" height="80"/>
|
||||
@@ -75,6 +78,9 @@
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="Gateway_S6_di" bpmnElement="Gateway_S6" isMarkerVisible="true">
|
||||
<dc:Bounds x="165" y="760" width="50" height="50"/>
|
||||
<bpmndi:BPMNLabel>
|
||||
<dc:Bounds x="202.5" y="744.5" width="90" height="28"/>
|
||||
</bpmndi:BPMNLabel>
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="Task_S7_di" bpmnElement="Task_S7">
|
||||
<dc:Bounds x="80" y="930" width="100" height="80"/>
|
||||
@@ -115,7 +121,7 @@
|
||||
<di:waypoint x="240" y="590"/>
|
||||
<di:waypoint x="130" y="590"/>
|
||||
<bpmndi:BPMNLabel>
|
||||
<dc:Bounds x="212.5" y="425" width="30" height="14"/>
|
||||
<dc:Bounds x="212.5" y="451" width="30" height="14"/>
|
||||
</bpmndi:BPMNLabel>
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge id="Flow_5_di" bpmnElement="Flow_5">
|
||||
@@ -145,7 +151,7 @@
|
||||
<di:waypoint x="240" y="930"/>
|
||||
<di:waypoint x="300" y="930"/>
|
||||
<bpmndi:BPMNLabel>
|
||||
<dc:Bounds x="212.5" y="765" width="30" height="14"/>
|
||||
<dc:Bounds x="212.5" y="791" width="30" height="14"/>
|
||||
</bpmndi:BPMNLabel>
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge id="Flow_9_di" bpmnElement="Flow_9">
|
||||
|
||||
@@ -273,22 +273,22 @@
|
||||
<bpmndi:BPMNDiagram id="Diagram_1">
|
||||
<bpmndi:BPMNPlane id="Plane_1" bpmnElement="Collaboration_1">
|
||||
<bpmndi:BPMNShape id="Participant_1_di" bpmnElement="Participant_1" isHorizontal="true">
|
||||
<dc:Bounds x="0" y="0" width="5480" height="1060"/>
|
||||
<dc:Bounds x="0" y="0" width="5166" height="1060"/>
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="Lane_1_di" bpmnElement="Lane_1" isHorizontal="true">
|
||||
<dc:Bounds x="0" y="0" width="5480" height="340"/>
|
||||
<dc:Bounds x="0" y="0" width="5166" height="340"/>
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="Lane_2_di" bpmnElement="Lane_2" isHorizontal="true">
|
||||
<dc:Bounds x="0" y="340" width="5480" height="190"/>
|
||||
<dc:Bounds x="0" y="340" width="5166" height="190"/>
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="Lane_3_di" bpmnElement="Lane_3" isHorizontal="true">
|
||||
<dc:Bounds x="0" y="530" width="5480" height="190"/>
|
||||
<dc:Bounds x="0" y="530" width="5166" height="190"/>
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="Lane_4_di" bpmnElement="Lane_4" isHorizontal="true">
|
||||
<dc:Bounds x="0" y="720" width="5480" height="340"/>
|
||||
<dc:Bounds x="0" y="720" width="5166" height="340"/>
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="Participant_Ext_1_di" bpmnElement="Participant_Ext_1" isHorizontal="true">
|
||||
<dc:Bounds x="0" y="1120" width="5480" height="100"/>
|
||||
<dc:Bounds x="0" y="1120" width="5166" height="100"/>
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="StartEvent_S1_di" bpmnElement="StartEvent_S1">
|
||||
<dc:Bounds x="110" y="152" width="36" height="36"/>
|
||||
@@ -307,6 +307,9 @@
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="Gateway_S6_di" bpmnElement="Gateway_S6" isMarkerVisible="true">
|
||||
<dc:Bounds x="1060" y="145" width="50" height="50"/>
|
||||
<bpmndi:BPMNLabel>
|
||||
<dc:Bounds x="1140" y="129.5" width="90" height="28"/>
|
||||
</bpmndi:BPMNLabel>
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="Task_S7_di" bpmnElement="Task_S7">
|
||||
<dc:Bounds x="1250" y="130" width="100" height="80"/>
|
||||
@@ -319,30 +322,48 @@
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="Gateway_S10_di" bpmnElement="Gateway_S10" isMarkerVisible="true">
|
||||
<dc:Bounds x="1820" y="145" width="50" height="50"/>
|
||||
<bpmndi:BPMNLabel>
|
||||
<dc:Bounds x="1857.5" y="115.5" width="90" height="42"/>
|
||||
</bpmndi:BPMNLabel>
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="Task_S11_di" bpmnElement="Task_S11">
|
||||
<dc:Bounds x="2010" y="130" width="100" height="80"/>
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="Gateway_S12_di" bpmnElement="Gateway_S12" isMarkerVisible="true">
|
||||
<dc:Bounds x="2200" y="145" width="50" height="50"/>
|
||||
<bpmndi:BPMNLabel>
|
||||
<dc:Bounds x="2237.5" y="129.5" width="90" height="28"/>
|
||||
</bpmndi:BPMNLabel>
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="Gateway_S13_di" bpmnElement="Gateway_S13" isMarkerVisible="true">
|
||||
<dc:Bounds x="2390" y="60" width="50" height="50"/>
|
||||
<bpmndi:BPMNLabel>
|
||||
<dc:Bounds x="2470" y="44.5" width="90" height="28"/>
|
||||
</bpmndi:BPMNLabel>
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="Task_S14_di" bpmnElement="Task_S14">
|
||||
<dc:Bounds x="2580" y="45" width="100" height="80"/>
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="Gateway_S15_di" bpmnElement="Gateway_S15" isMarkerVisible="true">
|
||||
<dc:Bounds x="2770" y="60" width="50" height="50"/>
|
||||
<bpmndi:BPMNLabel>
|
||||
<dc:Bounds x="2807.5" y="44.5" width="90" height="28"/>
|
||||
</bpmndi:BPMNLabel>
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="Task_S16_di" bpmnElement="Task_S16">
|
||||
<dc:Bounds x="2960" y="45" width="100" height="80"/>
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="Gateway_S17_di" bpmnElement="Gateway_S17" isMarkerVisible="true">
|
||||
<dc:Bounds x="3150" y="60" width="50" height="50"/>
|
||||
<bpmndi:BPMNLabel>
|
||||
<dc:Bounds x="3187.5" y="44.5" width="90" height="28"/>
|
||||
</bpmndi:BPMNLabel>
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="Gateway_S18_di" bpmnElement="Gateway_S18" isMarkerVisible="true">
|
||||
<dc:Bounds x="3340" y="60" width="50" height="50"/>
|
||||
<bpmndi:BPMNLabel>
|
||||
<dc:Bounds x="3377.5" y="44.5" width="90" height="28"/>
|
||||
</bpmndi:BPMNLabel>
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="Task_S19_di" bpmnElement="Task_S19">
|
||||
<dc:Bounds x="3530" y="585" width="100" height="80"/>
|
||||
@@ -355,9 +376,15 @@
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="Gateway_S22_di" bpmnElement="Gateway_S22" isMarkerVisible="true">
|
||||
<dc:Bounds x="3910" y="145" width="50" height="50"/>
|
||||
<bpmndi:BPMNLabel>
|
||||
<dc:Bounds x="3947.5" y="129.5" width="90" height="28"/>
|
||||
</bpmndi:BPMNLabel>
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="Gateway_S23_di" bpmnElement="Gateway_S23" isMarkerVisible="true">
|
||||
<dc:Bounds x="3340" y="230" width="50" height="50"/>
|
||||
<bpmndi:BPMNLabel>
|
||||
<dc:Bounds x="3420" y="214.5" width="90" height="28"/>
|
||||
</bpmndi:BPMNLabel>
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="Task_S24_di" bpmnElement="Task_S24">
|
||||
<dc:Bounds x="3530" y="935" width="100" height="80"/>
|
||||
@@ -367,9 +394,15 @@
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="Gateway_S26_di" bpmnElement="Gateway_S26" isMarkerVisible="true">
|
||||
<dc:Bounds x="3720" y="230" width="50" height="50"/>
|
||||
<bpmndi:BPMNLabel>
|
||||
<dc:Bounds x="3757.5" y="214.5" width="90" height="28"/>
|
||||
</bpmndi:BPMNLabel>
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="Gateway_S27_di" bpmnElement="Gateway_S27" isMarkerVisible="true">
|
||||
<dc:Bounds x="4100" y="145" width="50" height="50"/>
|
||||
<bpmndi:BPMNLabel>
|
||||
<dc:Bounds x="4137.5" y="129.5" width="90" height="28"/>
|
||||
</bpmndi:BPMNLabel>
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="Task_S40_di" bpmnElement="Task_S40">
|
||||
<dc:Bounds x="2390" y="215" width="100" height="80"/>
|
||||
@@ -379,15 +412,24 @@
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="Gateway_S42_di" bpmnElement="Gateway_S42" isMarkerVisible="true">
|
||||
<dc:Bounds x="2770" y="230" width="50" height="50"/>
|
||||
<bpmndi:BPMNLabel>
|
||||
<dc:Bounds x="2807.5" y="200.5" width="90" height="42"/>
|
||||
</bpmndi:BPMNLabel>
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="Task_S43_di" bpmnElement="Task_S43">
|
||||
<dc:Bounds x="2960" y="215" width="100" height="80"/>
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="Gateway_S44_di" bpmnElement="Gateway_S44" isMarkerVisible="true">
|
||||
<dc:Bounds x="3150" y="230" width="50" height="50"/>
|
||||
<bpmndi:BPMNLabel>
|
||||
<dc:Bounds x="3187.5" y="214.5" width="90" height="28"/>
|
||||
</bpmndi:BPMNLabel>
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="Gateway_S50_di" bpmnElement="Gateway_S50" isMarkerVisible="true">
|
||||
<dc:Bounds x="4290" y="145" width="50" height="50"/>
|
||||
<bpmndi:BPMNLabel>
|
||||
<dc:Bounds x="4327.5" y="101.5" width="90" height="56"/>
|
||||
</bpmndi:BPMNLabel>
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="Task_S51_di" bpmnElement="Task_S51">
|
||||
<dc:Bounds x="4480" y="130" width="100" height="80"/>
|
||||
@@ -402,16 +444,16 @@
|
||||
<dc:Bounds x="5050" y="152" width="36" height="36"/>
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="TextAnnotation_H0_di" bpmnElement="TextAnnotation_H0">
|
||||
<dc:Bounds x="5240" y="40" width="160" height="60"/>
|
||||
<dc:Bounds x="5206" y="0" width="250" height="60"/>
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="TextAnnotation_H3_di" bpmnElement="TextAnnotation_H3">
|
||||
<dc:Bounds x="5240" y="130" width="160" height="60"/>
|
||||
<dc:Bounds x="5206" y="80" width="250" height="266"/>
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="TextAnnotation_H1_di" bpmnElement="TextAnnotation_H1">
|
||||
<dc:Bounds x="5240" y="220" width="160" height="60"/>
|
||||
<dc:Bounds x="5206" y="366" width="250" height="90"/>
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="TextAnnotation_H2_di" bpmnElement="TextAnnotation_H2">
|
||||
<dc:Bounds x="5240" y="310" width="160" height="60"/>
|
||||
<dc:Bounds x="5206" y="476" width="250" height="106"/>
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNEdge id="Flow_1_di" bpmnElement="Flow_1">
|
||||
<di:waypoint x="146" y="170"/>
|
||||
@@ -451,7 +493,7 @@
|
||||
<di:waypoint x="1845" y="120"/>
|
||||
<di:waypoint x="1845" y="145"/>
|
||||
<bpmndi:BPMNLabel>
|
||||
<dc:Bounds x="1107.5" y="150" width="30" height="14"/>
|
||||
<dc:Bounds x="1107.5" y="176" width="30" height="14"/>
|
||||
</bpmndi:BPMNLabel>
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge id="Flow_8_di" bpmnElement="Flow_8">
|
||||
@@ -500,7 +542,7 @@
|
||||
<di:waypoint x="2275" y="255"/>
|
||||
<di:waypoint x="2390" y="255"/>
|
||||
<bpmndi:BPMNLabel>
|
||||
<dc:Bounds x="2247.5" y="150" width="30" height="14"/>
|
||||
<dc:Bounds x="2247.5" y="176" width="30" height="14"/>
|
||||
</bpmndi:BPMNLabel>
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge id="Flow_15_di" bpmnElement="Flow_15">
|
||||
@@ -519,7 +561,7 @@
|
||||
<di:waypoint x="2795" y="35"/>
|
||||
<di:waypoint x="2795" y="60"/>
|
||||
<bpmndi:BPMNLabel>
|
||||
<dc:Bounds x="2437.5" y="65" width="30" height="14"/>
|
||||
<dc:Bounds x="2437.5" y="91" width="30" height="14"/>
|
||||
</bpmndi:BPMNLabel>
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge id="Flow_17_di" bpmnElement="Flow_17">
|
||||
@@ -557,7 +599,7 @@
|
||||
<di:waypoint x="3365" y="205"/>
|
||||
<di:waypoint x="3365" y="230"/>
|
||||
<bpmndi:BPMNLabel>
|
||||
<dc:Bounds x="3197.5" y="65" width="30" height="14"/>
|
||||
<dc:Bounds x="3197.5" y="91" width="30" height="14"/>
|
||||
</bpmndi:BPMNLabel>
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge id="Flow_22_di" bpmnElement="Flow_22">
|
||||
@@ -575,7 +617,7 @@
|
||||
<di:waypoint x="3415" y="805"/>
|
||||
<di:waypoint x="3530" y="805"/>
|
||||
<bpmndi:BPMNLabel>
|
||||
<dc:Bounds x="3387.5" y="65" width="30" height="14"/>
|
||||
<dc:Bounds x="3387.5" y="91" width="30" height="14"/>
|
||||
</bpmndi:BPMNLabel>
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge id="Flow_24_di" bpmnElement="Flow_24">
|
||||
@@ -619,7 +661,7 @@
|
||||
<di:waypoint x="3415" y="170"/>
|
||||
<di:waypoint x="3530" y="170"/>
|
||||
<bpmndi:BPMNLabel>
|
||||
<dc:Bounds x="3387.5" y="235" width="132" height="14"/>
|
||||
<dc:Bounds x="3387.5" y="261" width="132" height="14"/>
|
||||
</bpmndi:BPMNLabel>
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge id="Flow_30_di" bpmnElement="Flow_30">
|
||||
@@ -675,7 +717,7 @@
|
||||
<di:waypoint x="2845" y="255"/>
|
||||
<di:waypoint x="2960" y="255"/>
|
||||
<bpmndi:BPMNLabel>
|
||||
<dc:Bounds x="2817.5" y="235" width="30" height="14"/>
|
||||
<dc:Bounds x="2817.5" y="261" width="30" height="14"/>
|
||||
</bpmndi:BPMNLabel>
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge id="Flow_38_di" bpmnElement="Flow_38">
|
||||
|
||||
@@ -412,6 +412,55 @@ RUECKSPRUNG_SEITE = 30
|
||||
# im Gang gegenüber den Rücksprungkanten.
|
||||
NACHRICHT_STUB = 30
|
||||
NACHRICHT_VERSATZ = 20
|
||||
# Beschriftungen (#14): grobe Schätzung der Textgröße, damit Kästchen und
|
||||
# Beschriftungen groß genug sind - PICTURE vergrößert sonst beim Import und
|
||||
# überlappt Nachbarn. Anmerkungen stehen rechts neben dem Pool.
|
||||
ZEICHENBREITE = 7
|
||||
ZEILENHOEHE = 16
|
||||
ANMERKUNG_BREITE = 250
|
||||
ANMERKUNG_ABSTAND = 40
|
||||
GATEWAY_LABEL_BREITE = 90
|
||||
GATEWAY_LABEL_ZEILE = 14
|
||||
|
||||
|
||||
def text_zeilen(text, breite):
|
||||
"""Geschätzte Zeilenzahl von text bei Umbruch an Wortgrenzen in einem Feld der
|
||||
angegebenen Breite (abzüglich 10 Innenrand, ZEICHENBREITE je Zeichen)."""
|
||||
je_zeile = max(1, int((breite - 10) // ZEICHENBREITE))
|
||||
zeilen, laenge = 1, 0
|
||||
for wort in text.split():
|
||||
if laenge == 0:
|
||||
laenge = len(wort)
|
||||
elif laenge + 1 + len(wort) <= je_zeile:
|
||||
laenge += 1 + len(wort)
|
||||
else:
|
||||
zeilen, laenge = zeilen + 1, len(wort)
|
||||
while laenge > je_zeile: # überlange Wörter brechen hart um
|
||||
zeilen, laenge = zeilen + 1, laenge - je_zeile
|
||||
return zeilen
|
||||
|
||||
|
||||
def _rechtecke_ueberlappen(a, b):
|
||||
"""True, wenn sich die Rechtecke a und b (x, y, w, h) echt überschneiden."""
|
||||
return a[0] < b[0] + b[2] and b[0] < a[0] + a[2] and a[1] < b[1] + b[3] and b[1] < a[1] + a[3]
|
||||
|
||||
|
||||
def _strecke_schneidet(p, q, box):
|
||||
"""True, wenn die Strecke p-q durchs Innere des Rechtecks box (x, y, w, h) läuft."""
|
||||
x, y, w, h = box
|
||||
(x1, y1), (x2, y2) = p, q
|
||||
if abs(y1 - y2) < 1e-6: # waagrecht
|
||||
return y < y1 < y + h and min(x1, x2) < x + w and max(x1, x2) > x
|
||||
if abs(x1 - x2) < 1e-6: # senkrecht
|
||||
return x < x1 < x + w and min(y1, y2) < y + h and max(y1, y2) > y
|
||||
return any(x < x1 + (x2 - x1) * s / 40 < x + w and y < y1 + (y2 - y1) * s / 40 < y + h
|
||||
for s in range(1, 40))
|
||||
|
||||
|
||||
def anmerkung_groesse(text):
|
||||
"""Breite und Höhe eines Anmerkungs-Kästchens, passend zur Textlänge."""
|
||||
return ANMERKUNG_BREITE, max(DEFAULT_SIZE['TextAnnotation'][1],
|
||||
text_zeilen(text, ANMERKUNG_BREITE) * ZEILENHOEHE + 10)
|
||||
GROESSTE_BREITE = max(w for typ, (w, _) in DEFAULT_SIZE.items() if typ not in UNCONNECTED_TYPES)
|
||||
|
||||
|
||||
@@ -884,10 +933,14 @@ def build_bpmn(meta_md, steps, order, externals):
|
||||
|
||||
def bounds_of(sid):
|
||||
st = steps[sid]
|
||||
if st.type == 'TextAnnotation':
|
||||
w, h = anmerkung_groesse(st.label)
|
||||
else:
|
||||
w, h = DEFAULT_SIZE.get(st.type, (100, 80))
|
||||
return x_pos[sid], y_pos[sid], w, h
|
||||
|
||||
pool_width = max((x_pos[sid] + bounds_of(sid)[2] for sid in order), default=400) + MARGIN
|
||||
im_diagramm = [sid for sid in order if steps[sid].type != 'TextAnnotation']
|
||||
pool_width = max((x_pos[sid] + bounds_of(sid)[2] for sid in im_diagramm), default=400) + MARGIN
|
||||
back_flow_ids = [flow_id for flow_id, _, _, _, is_back in flows if is_back]
|
||||
msg_kanal_x = {}
|
||||
if not has_lanes and msg_flow_records:
|
||||
@@ -910,7 +963,16 @@ def build_bpmn(meta_md, steps, order, externals):
|
||||
pool_height += band
|
||||
lane_geo = (lane_top, lane_height, pool_height)
|
||||
else:
|
||||
pool_height = max((y_pos[sid] + bounds_of(sid)[3] for sid in order), default=300) + MARGIN
|
||||
pool_height = max((y_pos[sid] + bounds_of(sid)[3] for sid in im_diagramm), default=300) + MARGIN
|
||||
|
||||
# Anmerkungen rechts neben dem Pool untereinander, in der Größe ihres Texts:
|
||||
# so kreuzen sie keine Lane-Grenzen und überlappen sich nicht, auch wenn
|
||||
# PICTURE die Position streng aus der Datei übernimmt (#14).
|
||||
anm_y = 0
|
||||
for sid in order:
|
||||
if steps[sid].type == 'TextAnnotation':
|
||||
x_pos[sid], y_pos[sid] = pool_width + ANMERKUNG_ABSTAND, anm_y
|
||||
anm_y += bounds_of(sid)[3] + ANMERKUNG_ABSTAND / 2
|
||||
|
||||
ext_pos = {}
|
||||
if has_collaboration:
|
||||
@@ -931,6 +993,9 @@ def build_bpmn(meta_md, steps, order, externals):
|
||||
'id': f'{eid}_di', 'bpmnElement': eid, 'isHorizontal': 'true',
|
||||
}).append(_bounds_el(0, ex_y, pool_width, 100))
|
||||
|
||||
gateway_shapes = {} # Gateways mit Beschriftung, Position wird zuletzt gewählt
|
||||
hindernisse = [] # Rechtecke von Elementen und Kantenbeschriftungen
|
||||
strecken = [] # alle Kantenstücke als ((x1, y1), (x2, y2))
|
||||
for sid in order:
|
||||
attrs = {'id': f'{node_ids[sid]}_di', 'bpmnElement': node_ids[sid]}
|
||||
if steps[sid].type == 'XorGateway':
|
||||
@@ -940,6 +1005,9 @@ def build_bpmn(meta_md, steps, order, externals):
|
||||
shape = ET.SubElement(plane, q('bpmndi', 'BPMNShape'), attrs)
|
||||
x, y, w, h = bounds_of(sid)
|
||||
shape.append(_bounds_el(x, y, w, h))
|
||||
if SHAPE_KIND.get(steps[sid].type) == 'diamond' and steps[sid].label:
|
||||
gateway_shapes[sid] = shape # Beschriftung erst nach den Kanten, siehe unten
|
||||
hindernisse.append((x, y, w, h))
|
||||
|
||||
# Anschlusspunkte je (Element, Seite) zählen, damit mehrere Kanten an derselben
|
||||
# Seite nicht alle im selben Punkt zusammenlaufen (Modellierregel 4).
|
||||
@@ -1048,18 +1116,24 @@ def build_bpmn(meta_md, steps, order, externals):
|
||||
|
||||
for px, py in points:
|
||||
ET.SubElement(edge, q('di', 'waypoint'), {'x': f'{px:g}', 'y': f'{py:g}'})
|
||||
strecken.extend(zip(points, points[1:]))
|
||||
|
||||
if label:
|
||||
# explizite Label-Position setzen, um Überlappungen mit Pfeilen/Nachbarlabels
|
||||
# zu reduzieren: am ersten Segment, leicht seitlich versetzt
|
||||
(p0x, p0y), (p1x, p1y) = points[0], points[1] if len(points) > 1 else points[0]
|
||||
mx, my = (p0x + p1x) / 2, (p0y + p1y) / 2
|
||||
if abs(p0y - p1y) < 1e-6: # horizontales Segment -> Label darüber
|
||||
if abs(p0y - p1y) < 1e-6 and SHAPE_KIND.get(steps[src].type) == 'diamond':
|
||||
# waagrecht aus einem Gateway -> Label darunter; darüber steht
|
||||
# die Beschriftung des Gateways (#14)
|
||||
lx, ly = mx - 15, my + 6
|
||||
elif abs(p0y - p1y) < 1e-6: # horizontales Segment -> Label darüber
|
||||
lx, ly = mx - 15, my - 20
|
||||
else: # vertikales Segment -> Label seitlich
|
||||
lx, ly = mx + 8, my - 8
|
||||
bpmn_label = ET.SubElement(edge, q('bpmndi', 'BPMNLabel'))
|
||||
bpmn_label.append(_bounds_el(lx, ly, max(30, len(label) * 6), 14))
|
||||
hindernisse.append((lx, ly, max(30, len(label) * 6), 14))
|
||||
|
||||
for mfid, sid, direction, target in msg_flow_records:
|
||||
edge = ET.SubElement(plane, q('bpmndi', 'BPMNEdge'), {'id': f'{mfid}_di', 'bpmnElement': mfid})
|
||||
@@ -1084,6 +1158,28 @@ def build_bpmn(meta_md, steps, order, externals):
|
||||
points = points[::-1]
|
||||
for px, py in points:
|
||||
ET.SubElement(edge, q('di', 'waypoint'), {'x': f'{px:g}', 'y': f'{py:g}'})
|
||||
strecken.extend(zip(points, points[1:]))
|
||||
|
||||
# Gateway-Beschriftungen zuletzt, wenn alle Kanten und Kantenbeschriftungen
|
||||
# feststehen: oben rechts (45°) wie im PICTURE-Editor, sonst die nächste freie
|
||||
# Stelle. Ohne Angabe stünde sie unter der Raute (180°), wo der südliche Pfad
|
||||
# und dessen Beschriftung sie schneiden; PICTURE übernimmt die Position (#14).
|
||||
for sid, shape in gateway_shapes.items():
|
||||
x, y, w, h = bounds_of(sid)
|
||||
lw = GATEWAY_LABEL_BREITE
|
||||
lh = text_zeilen(steps[sid].label, lw) * GATEWAY_LABEL_ZEILE
|
||||
# 45°; 45° rechts vom Ost-Stub (dort steigt oft die eigene Kante auf);
|
||||
# 315°; 135°; 225°
|
||||
ecken = [(x + w * 3 / 4, y + h / 4 - lh), (x + w + GATEWAY_STUB + 5, y + h / 4 - lh),
|
||||
(x + w / 4 - lw, y + h / 4 - lh), (x + w * 3 / 4, y + h * 3 / 4),
|
||||
(x + w / 4 - lw, y + h * 3 / 4)]
|
||||
eigene = (x, y, w, h)
|
||||
frei = [(lx, ly) for lx, ly in ecken
|
||||
if not any(_strecke_schneidet(a, b, (lx, ly, lw, lh)) for a, b in strecken)
|
||||
and not any(_rechtecke_ueberlappen((lx, ly, lw, lh), r) for r in hindernisse if r != eigene)]
|
||||
lx, ly = frei[0] if frei else ecken[0]
|
||||
ET.SubElement(shape, q('bpmndi', 'BPMNLabel')).append(_bounds_el(lx, ly, lw, lh))
|
||||
hindernisse.append((lx, ly, lw, lh))
|
||||
|
||||
return definitions
|
||||
|
||||
|
||||
@@ -621,6 +621,40 @@ def test_nachrichtenfluss_quert_kein_element(text):
|
||||
assert not (x + 1 < px < x + w - 1 and y + 1 < py < y + h - 1), f"{mfid} durch {eid}"
|
||||
|
||||
|
||||
def test_textschaetzung_bricht_an_wortgrenzen_und_bei_ueberlangen_woertern_um():
|
||||
"""Prüft die Zeilenschätzung für Beschriftungen: kurzer Text eine Zeile, Umbruch an Wortgrenzen, ein Wort länger
|
||||
als die Zeile wird hart umbrochen (Issue #14)."""
|
||||
je_zeile = int((100 - 10) // m.ZEICHENBREITE)
|
||||
assert m.text_zeilen("Ja", 100) == 1
|
||||
assert m.text_zeilen("a" * je_zeile + " b", 100) == 2
|
||||
assert m.text_zeilen("x" * (2 * je_zeile + 1), 100) == 3
|
||||
|
||||
|
||||
def test_gateway_beschriftung_steht_ohne_hindernis_oben_rechts():
|
||||
"""Prüft, dass ein Gateway ohne Hindernis seine Beschriftung auf 45° (oben rechts) bekommt, wie im PICTURE-Editor,
|
||||
und nicht darunter, wo der südliche Pfad sie schneiden würde (Issue #14)."""
|
||||
text = """
|
||||
S1 Start: Beginn
|
||||
-> S2
|
||||
S2 Gateway XOR: Antrag vollständig?
|
||||
Ja -> S3
|
||||
S3 Ende: Fertig
|
||||
"""
|
||||
defs, bounds, _ = _baue_und_finde(text)
|
||||
shape = next(s for s in defs.iter(f"{{{m.NS['bpmndi']}}}BPMNShape") if s.get('bpmnElement') == 'Gateway_S2')
|
||||
b = shape.find(f"{{{m.NS['bpmndi']}}}BPMNLabel").find(f"{{{m.NS['dc']}}}Bounds")
|
||||
lx, ly, lh = float(b.get('x')), float(b.get('y')), float(b.get('height'))
|
||||
x, y, w, h = bounds['Gateway_S2']
|
||||
assert lx == x + w * 3 / 4 and ly + lh == y + h / 4
|
||||
|
||||
|
||||
def test_anmerkung_waechst_mit_ihrem_text():
|
||||
"""Prüft, dass eine lange Anmerkung ein höheres Kästchen bekommt als eine kurze (Issue #14)."""
|
||||
kurz_w, kurz_h = m.anmerkung_groesse("Kurz")
|
||||
lang_w, lang_h = m.anmerkung_groesse("Ein langer Hinweis " * 20)
|
||||
assert kurz_w == lang_w and lang_h > kurz_h
|
||||
|
||||
|
||||
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 **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.
|
||||
|
||||
- 25.09.2026 **Nachrichtenflüsse senkrecht und an allen Elementen vorbei** (#13): Ende senkrecht unter dem
|
||||
Start, bei Hindernis über einen freien Gang. Damit gibt es in den Beispielen keine bekannte Kreuzung mehr
|
||||
(`BEKANNTE_KREUZUNGEN` leer).
|
||||
|
||||
@@ -5,6 +5,16 @@ Kurz, konkret, mit Datum und Quelle. Neue Einträge oben in der passenden Rubrik
|
||||
|
||||
## BPMN und PICTURE (Skill sn-bpmn, md_to_bpmn.py)
|
||||
|
||||
- **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
|
||||
Ost-Stub, 315°, 135°, 225° – die erste Stelle, die keine Kante, Kantenbeschriftung oder kein Element schneidet.
|
||||
Dafür werden die Gateway-Beschriftungen **nach** allen Kanten gesetzt. Zu kleine Anmerkungs-Kästchen vergrößert
|
||||
PICTURE beim Import, dann überlappen sie Nachbarn und Lane-Grenzen. Deshalb richtet sich die Höhe nach dem Text
|
||||
(`text_zeilen()`, grobe Schätzung 7 je Zeichen), und die Anmerkungen stehen rechts neben dem Pool.
|
||||
`GATEWAY_LABEL_BREITE = 90` hängt an `AUSWEICHABSTAND` (#8): Bei 100 ragte die Beschriftung über die
|
||||
Ausweichachse bei x + 130. Pool-Name über dem Namen der ersten Lane: nur in der Vorschau beobachtet, in PICTURE
|
||||
kein Problem. (PICTURE 2026.4.1, #14, 25.09.2026)
|
||||
- **Nachrichtenflüsse enden senkrecht unter ihrem Start, nicht an einem verteilten Punkt.** Der externe Pool ist
|
||||
ein breites Rechteck, jeder Punkt an seiner Oberkante ist ein gültiger Anschluss. Die frühere Verteilung der
|
||||
Anschlusspunkte über die Pool-Oberkante erzeugte schräge Linien quer durch das Diagramm. Steht unter dem Schritt
|
||||
|
||||
@@ -121,6 +121,121 @@ def test_bekannte_kreuzung_ist_behoben(modell, kante, element):
|
||||
assert (kante, element) not in kreuzungen(baue(SKILL / "beispiele" / f"{modell}.md"))
|
||||
|
||||
|
||||
def _box(el):
|
||||
b = el.find(f"{{{m.NS['dc']}}}Bounds")
|
||||
return tuple(float(b.get(a)) for a in ("x", "y", "width", "height"))
|
||||
|
||||
|
||||
def _ueberlappen(a, b, rand=0.5):
|
||||
"""True, wenn sich die Rechtecke a und b (x, y, w, h) echt überschneiden (Berührung zählt nicht)."""
|
||||
return (a[0] + rand < b[0] + b[2] and b[0] + rand < a[0] + a[2]
|
||||
and a[1] + rand < b[1] + b[3] and b[1] + rand < a[1] + a[3])
|
||||
|
||||
|
||||
def beschriftungskonflikte(defs):
|
||||
"""Alle Konflikte einer Gateway-Beschriftung mit Kanten, Kantenbeschriftungen oder anderen Elementen."""
|
||||
art = {el.get("id"): el.tag.rpartition("}")[2] for el in defs.iter() if el.get("id")}
|
||||
gw_labels, formen = {}, {}
|
||||
for shape in defs.iter(f"{{{m.NS['bpmndi']}}}BPMNShape"):
|
||||
eid = shape.get("bpmnElement")
|
||||
if art.get(eid) in HINDERNISSE or art.get(eid) == "textAnnotation":
|
||||
formen[eid] = _box(shape)
|
||||
label = shape.find(f"{{{m.NS['bpmndi']}}}BPMNLabel")
|
||||
if art.get(eid) in GATEWAYS and label is not None:
|
||||
gw_labels[eid] = _box(label)
|
||||
konflikte = []
|
||||
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")]
|
||||
label = edge.find(f"{{{m.NS['bpmndi']}}}BPMNLabel")
|
||||
for gid, (x, y, w, h) in gw_labels.items():
|
||||
if label is not None and _ueberlappen(_box(label), (x, y, w, h)):
|
||||
konflikte.append((gid, f"Beschriftung von {kante}"))
|
||||
for (x1, y1), (x2, y2) in zip(punkte, punkte[1:]):
|
||||
schritte = max(8, int(math.hypot(x2 - x1, y2 - y1) / 4))
|
||||
if any(x < x1 + (x2 - x1) * s / schritte < x + w and y < y1 + (y2 - y1) * s / schritte < y + h
|
||||
for s in range(1, schritte)):
|
||||
konflikte.append((gid, kante))
|
||||
break
|
||||
for gid, box in gw_labels.items():
|
||||
konflikte += [(gid, eid) for eid, form in formen.items() if eid != gid and _ueberlappen(box, form)]
|
||||
return sorted(konflikte)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("md", MODELLE, ids=lambda p: p.stem)
|
||||
def test_gateway_beschriftung_wird_von_nichts_geschnitten(md):
|
||||
"""Prüft, dass jede Gateway-Beschriftung eine feste Position hat und weder von einer Kante noch von einer
|
||||
Kantenbeschriftung oder einem anderen Element geschnitten wird (Issue #14; PICTURE übernimmt die Position)."""
|
||||
defs = baue(md)
|
||||
art = {el.get("id"): el.tag.rpartition("}")[2] for el in defs.iter() if el.get("id")}
|
||||
ohne = [s.get("bpmnElement") for s in defs.iter(f"{{{m.NS['bpmndi']}}}BPMNShape")
|
||||
if art.get(s.get("bpmnElement")) in GATEWAYS and s.find(f"{{{m.NS['bpmndi']}}}BPMNLabel") is None
|
||||
and next(el for el in defs.iter() if el.get("id") == s.get("bpmnElement")).get("name")]
|
||||
assert ohne == [], "Gateway mit Namen, aber ohne Beschriftungsposition: " + ", ".join(ohne)
|
||||
konflikte = beschriftungskonflikte(defs)
|
||||
assert konflikte == [], "Gateway-Beschriftung geschnitten: " + ", ".join(f"{g} ← {k}" for g, k in konflikte)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("md", [md for md in MODELLE if md.stem != "beispiel_prozess"], ids=lambda p: p.stem)
|
||||
def test_gateway_beschriftung_bleibt_in_der_eigenen_lane(md):
|
||||
"""Prüft, dass jede Gateway-Beschriftung vollständig in der Lane ihres Gateways liegt und nicht in den Lane-Kopf
|
||||
mit den Lane-Namen oder in eine Nachbar-Lane ragt (Issue #14)."""
|
||||
defs = baue(md)
|
||||
lanes = [_box(s) for s in defs.iter(f"{{{m.NS['bpmndi']}}}BPMNShape") if s.get("bpmnElement").startswith("Lane_")]
|
||||
assert lanes, "Modell ohne Lanes"
|
||||
for shape in defs.iter(f"{{{m.NS['bpmndi']}}}BPMNShape"):
|
||||
label = shape.find(f"{{{m.NS['bpmndi']}}}BPMNLabel")
|
||||
if label is None:
|
||||
continue
|
||||
x, y, w, h = _box(shape)
|
||||
lx, ly, lw, lh = _box(label)
|
||||
lane = next(b for b in lanes if b[1] <= y + h / 2 <= b[1] + b[3])
|
||||
assert lane[1] <= ly and ly + lh <= lane[1] + lane[3], f"{shape.get('bpmnElement')}: ragt aus der Lane"
|
||||
assert lx >= lane[0] + m.LANE_HEADER_WIDTH, f"{shape.get('bpmnElement')}: ragt in den Lane-Kopf"
|
||||
|
||||
|
||||
MIT_ANMERKUNGEN = [md for md in MODELLE if " Hinweis:" in md.read_text(encoding="utf-8")]
|
||||
|
||||
|
||||
def test_es_gibt_ein_modell_mit_anmerkungen():
|
||||
"""Prüft, dass die Anmerkungs-Tests tatsächlich an einem Modell laufen."""
|
||||
assert MIT_ANMERKUNGEN
|
||||
|
||||
|
||||
@pytest.mark.parametrize("md", MIT_ANMERKUNGEN, ids=lambda p: p.stem)
|
||||
def test_anmerkungen_ueberlappen_nichts_und_stehen_neben_dem_pool(md):
|
||||
"""Prüft, dass Anmerkungen (Hinweise) sich nicht gegenseitig überlappen, kein anderes Element überdecken und –
|
||||
wenn es einen Prozess-Pool gibt – rechts daneben stehen, also keine Lane-Grenze kreuzen (Issue #14)."""
|
||||
defs = baue(md)
|
||||
art = {el.get("id"): el.tag.rpartition("}")[2] for el in defs.iter() if el.get("id")}
|
||||
formen = {s.get("bpmnElement"): _box(s) for s in defs.iter(f"{{{m.NS['bpmndi']}}}BPMNShape")}
|
||||
anmerkungen = {eid: box for eid, box in formen.items() if art.get(eid) == "textAnnotation"}
|
||||
ids = sorted(anmerkungen)
|
||||
for i, a in enumerate(ids):
|
||||
for b in ids[i + 1:]:
|
||||
assert not _ueberlappen(anmerkungen[a], anmerkungen[b]), f"{a} überlappt {b}"
|
||||
for eid, form in formen.items():
|
||||
if art.get(eid) in HINDERNISSE:
|
||||
assert not _ueberlappen(anmerkungen[a], form), f"{a} überdeckt {eid}"
|
||||
if "Participant_1" in formen:
|
||||
px, _, pw, _ = formen["Participant_1"]
|
||||
for eid, (x, _, _, _) in anmerkungen.items():
|
||||
assert x >= px + pw, f"{eid} steht im Pool (x {x:g} < {px + pw:g})"
|
||||
|
||||
|
||||
@pytest.mark.parametrize("md", MIT_ANMERKUNGEN, ids=lambda p: p.stem)
|
||||
def test_anmerkung_ist_gross_genug_fuer_ihren_text(md):
|
||||
"""Prüft, dass jedes Anmerkungs-Kästchen seinen Text fassen kann (grob: 7 Einheiten je Zeichen, 16 je Zeile).
|
||||
Sonst vergrößert PICTURE das Kästchen beim Import, und es überlappt Nachbarn und Lane-Grenzen (Issue #14)."""
|
||||
defs = baue(md)
|
||||
formen = {s.get("bpmnElement"): _box(s) for s in defs.iter(f"{{{m.NS['bpmndi']}}}BPMNShape")}
|
||||
for anm in defs.iter(f"{{{m.NS['bpmn']}}}textAnnotation"):
|
||||
text = anm.find(f"{{{m.NS['bpmn']}}}text").text or ""
|
||||
_, _, w, h = formen[anm.get("id")]
|
||||
zeilen = math.ceil(len(text) * 7 / (w - 10))
|
||||
assert h >= zeilen * 16, f"{anm.get('id')}: {len(text)} Zeichen brauchen etwa {zeilen} Zeilen, Höhe nur {h:g}"
|
||||
|
||||
|
||||
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")
|
||||
|
||||
Reference in New Issue
Block a user