2020Contact: info@processintelligence.solutions
2121'''
2222from pm4py .objects .bpmn .obj import BPMN
23- from typing import Optional , Dict , Any
24-
23+ from typing import Optional , Dict , Any , Tuple , List
2524from copy import copy
2625import tempfile
2726
2827
28+ def _get_node_center (layout , node ) -> Tuple [float , float ]:
29+ """Get the center point of a node."""
30+ node_layout = layout .get (node )
31+ x = node_layout .get_x () + node_layout .get_width () / 2
32+ y = node_layout .get_y () + node_layout .get_height () / 2
33+ return (x , y )
34+
35+
36+ def _get_node_boundary_point (layout , node , target_point : Tuple [float , float ]) -> Tuple [float , float ]:
37+ """
38+ Calculate the intersection point on the node boundary
39+ towards a target point.
40+ """
41+ node_layout = layout .get (node )
42+ x = node_layout .get_x ()
43+ y = node_layout .get_y ()
44+ w = node_layout .get_width ()
45+ h = node_layout .get_height ()
46+
47+ cx , cy = x + w / 2 , y + h / 2
48+ tx , ty = target_point
49+
50+ dx = tx - cx
51+ dy = ty - cy
52+
53+ if dx == 0 and dy == 0 :
54+ return (cx , y ) # Default to top edge
55+
56+ # Calculate intersection with rectangle boundary
57+ if dx == 0 :
58+ return (cx , y if dy < 0 else y + h )
59+ if dy == 0 :
60+ return (x if dx < 0 else x + w , cy )
61+
62+ # Check intersection with each edge
63+ scale_x = (w / 2 ) / abs (dx )
64+ scale_y = (h / 2 ) / abs (dy )
65+ scale = min (scale_x , scale_y )
66+
67+ return (cx + dx * scale , cy + dy * scale )
68+
69+
2970def apply (
30- bpmn_graph : BPMN , parameters : Optional [Dict [Any , Any ]] = None
71+ bpmn_graph : BPMN , parameters : Optional [Dict [Any , Any ]] = None
3172) -> BPMN :
3273 """
33- Layouts the BPMN graphviz using directly the information about node positioning
34- and edges waypoints provided in the SVG obtained from Graphviz.
35-
36- Parameters
37- -----------------
38- bpmn_graph
39- BPMN graph
40- parameters
41- Optional parameters of the method
42-
43- Returns
44- ----------------
45- layouted_bpmn
46- Layouted BPMN
74+ Layouts the BPMN graph using node positioning and edge waypoints from Graphviz SVG.
75+ Ensures edges connect properly to node boundaries.
4776 """
4877 if parameters is None :
4978 parameters = {}
@@ -53,7 +82,7 @@ def apply(
5382
5483 layout = bpmn_graph .get_layout ()
5584
56- filename_svg = tempfile .NamedTemporaryFile (suffix = ".svg" )
85+ filename_svg = tempfile .NamedTemporaryFile (suffix = ".svg" , delete = False )
5786 filename_svg .close ()
5887
5988 vis_parameters = copy (parameters )
@@ -64,36 +93,45 @@ def apply(
6493 gviz = bpmn_visualizer .apply (bpmn_graph , parameters = vis_parameters )
6594 bpmn_visualizer .save (gviz , filename_svg .name )
6695
67- # print(filename_svg.name)
68-
6996 nodes_p , edges_p = svg_pos_parser .apply (filename_svg .name )
7097
98+ # First pass: layout all nodes
7199 for node in list (bpmn_graph .get_nodes ()):
72100 node_id = str (id (node ))
73101 if node_id in nodes_p :
74102 node_info = nodes_p [node_id ]
75103 if node_info ["polygon" ] is not None :
76- min_x = min (x [0 ] for x in node_info ["polygon" ])
77- max_x = max (x [0 ] for x in node_info ["polygon" ])
78- min_y = min (x [1 ] for x in node_info ["polygon" ])
79- max_y = max (x [1 ] for x in node_info ["polygon" ])
104+ min_x = min (p [0 ] for p in node_info ["polygon" ])
105+ max_x = max (p [0 ] for p in node_info ["polygon" ])
106+ min_y = min (p [1 ] for p in node_info ["polygon" ])
107+ max_y = max (p [1 ] for p in node_info ["polygon" ])
80108
81- width = max_x - min_x
82- height = max_y - min_y
83-
84- layout .get (node ).set_width (width )
85- layout .get (node ).set_height (height )
109+ layout .get (node ).set_width (max_x - min_x )
110+ layout .get (node ).set_height (max_y - min_y )
86111 layout .get (node ).set_x (min_x )
87112 layout .get (node ).set_y (min_y )
88113
114+ # Second pass: layout edges with proper connection points
89115 for flow in list (bpmn_graph .get_flows ()):
90116 flow_id = (str (id (flow .source )), str (id (flow .target )))
91117 if flow_id in edges_p :
92118 flow_info = edges_p [flow_id ]
93- if flow_info ["waypoints" ] is not None :
119+ if flow_info ["waypoints" ] is not None and len ( flow_info [ "waypoints" ]) >= 2 :
94120 flow .del_waypoints ()
95121
96- for wayp in flow_info ["waypoints" ]:
122+ waypoints = list (flow_info ["waypoints" ])
123+
124+ # Adjust start point to source node boundary
125+ if len (waypoints ) > 1 :
126+ start_boundary = _get_node_boundary_point (layout , flow .source , waypoints [1 ])
127+ waypoints [0 ] = start_boundary
128+
129+ # Adjust end point to target node boundary
130+ if len (waypoints ) > 1 :
131+ end_boundary = _get_node_boundary_point (layout , flow .target , waypoints [- 2 ])
132+ waypoints [- 1 ] = end_boundary
133+
134+ for wayp in waypoints :
97135 flow .add_waypoint (wayp )
98136
99137 return bpmn_graph
0 commit comments