Version: 9.15.0
graph.py
Go to the documentation of this file.
1 # Copyright (C) 2006-2025 CEA, EDF
2 #
3 # This library is free software; you can redistribute it and/or
4 # modify it under the terms of the GNU Lesser General Public
5 # License as published by the Free Software Foundation; either
6 # version 2.1 of the License, or (at your option) any later version.
7 #
8 # This library is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11 # Lesser General Public License for more details.
12 #
13 # You should have received a copy of the GNU Lesser General Public
14 # License along with this library; if not, write to the Free Software
15 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
16 #
17 # See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
18 #
19 
20 import sys
21 import pilot
22 from . import Item
23 from qt import *
24 from qtcanvas import *
25 from .GraphViewer import GraphViewer
26 from . import Editor
27 from . import CItems
28 import pygraphviz
29 from pygraphviz import graphviz as gv
30 import traceback
31 from . import CONNECTOR
32 import bisect
33 
35  def customEvent(self,event):
36  object=event.data()
37  object.customEvent(event)
38  self.update()
39 
40 class Graph:
41  def __init__(self,item,parent):
42  self.parentparent=parent
43  self.itemitem=item
44  self.nodenode=item.node
45  #initial canvas size : 1000x1000
46  self.canvascanvas=MyCanvas(1000,1000)
47  self.editoreditor=GraphViewer(self.canvascanvas,parent,"example",0)
48  self.createGraphcreateGraph()
49  root=self.nodenode.getRootNode()
50  rootItem=Item.adapt(root)
51  CONNECTOR.Connect(rootItem,"selected",self.selectItemselectItem,())
52  CONNECTOR.Connect(self.itemitem,"add",self.addItemaddItem,())
53  CONNECTOR.Connect(self.itemitem.datalinks,"add",self.addLinkaddLink,())
54 
55  def createGraph(self):
56  #citems dict helps finding items in canvas from swig proxy
57  #To find an item node make : citems[node.ptr()]
58  citems={}
59  self.citemscitems=citems
60  #pitems dict helps finding items in canvas from swig proxy
61  #To find an item port make : pitems[port.ptr()]
62  pitems={}
63 
64  y=0
65  lnode=self.nodenode.edGetDirectDescendants()
66  for n in lnode:
67  c=CItems.Cell(n,self.canvascanvas)
68  citems[n.ptr()]=c
69  c.show()
70 
71  for k,n in list(citems.items()):
72  for p in n.inports:
73  pitems[p.port.ptr()]=p
74  for p in n.outports:
75  pitems[p.port.ptr()]=p
76 
77  for pout,pin in self.nodenode.getSetOfInternalLinks():
78  if pout.getNode().getFather() != self.nodenode and pin.getNode().getFather() != self.nodenode:
79  continue
80  po=pitems.get(pout.ptr())
81  pi=pitems.get(pin.ptr())
82  if pi and po:
83  CItems.LinkItem(po,pi,self.canvascanvas)
84 
85  for n in lnode:
86  itemup=citems[n.ptr()]
87  for ndown in n.getOutNodes():
88  itemdown=citems[ndown.ptr()]
89  CItems.ControlLinkItem(itemup.outgate,itemdown.ingate,self.canvascanvas)
90 
91  self.layoutlayout("LR")
92 
93  def addLink(self,link):
94  print("graph.addLink",link)
95  #CItem for outport
96  nodeS=self.citemscitems[link.pout.getNode().ptr()]
97  nodeE=self.citemscitems[link.pin.getNode().ptr()]
98  po=pi=None
99  for p in nodeS.outports:
100  if p.port == link.pout:
101  po=p
102  break
103  for p in nodeE.inports:
104  if p.port == link.pin:
105  pi=p
106  break
107 
108  if pi and po:
109  l=CItems.LinkItem(po,pi,self.canvascanvas)
110  self.canvascanvas.update()
111 
112  def addItem(self,item):
113  #print "graph.addItem",item
114  node=CItems.Cell(item.node,self.canvascanvas)
115  self.citemscitems[item.node.ptr()]=node
116  node.show()
117  self.canvascanvas.update()
118 
119  def selectItem(self,item):
120  #print "graph.selectItem",item
121  self.editoreditor.selectItem(item)
122 
123  def layout(self,rankdir):
124  """Compute graph layout with graphviz package"""
125  G=pygraphviz.AGraph(strict=False,directed=True)
126  G.graph_attr["rankdir"]=rankdir
127  G.graph_attr["dpi"]="72"
128  dpi=72.
129  aspect=dpi/72
130  for k,n in list(self.citemscitems.items()):
131  #k is node address (YACS)
132  #n is item in canvas
133  G.add_node(k)
134 
135  for pout,pin in self.nodenode.getSetOfInternalLinks():
136  if pout.getNode().ptr() not in self.citemscitems :
137  continue
138  if pin.getNode().ptr() not in self.citemscitems:
139  continue
140  G.add_edge(pout.getNode().ptr(),pin.getNode().ptr())
141 
142  for k,n in list(self.citemscitems.items()):
143  for ndown in n.node.getOutNodes():
144  G.add_edge(n.node.ptr(),ndown.ptr())
145 
146  #By default graphviz uses 96.0 pixel per inch (dpi=96.0)
147  for n in G.nodes():
148  item=self.citemscitems[int(n)]
149  h=item.height()/dpi #height in inch
150  w=item.width()/dpi #width in inch
151  n.attr['height']=str(h)
152  n.attr['width']=str(w)
153  n.attr['fixedsize']="true"
154  n.attr['shape']="box"
155  #n.attr['label']=item.node.getName()
156 
157  G.layout(prog='dot') # use dot
158  #G.write("layout.dot")
159  #G.draw("layout.png")
160 
161  graph_attr=dict(attrs(G))
162  bbox=graph_attr["bb"]
163  x1,y1,x2,y2=eval(bbox)
164  h=self.canvascanvas.height()
165  w=self.canvascanvas.width()
166  h2=max(h,y2-y1+100)
167  w2=max(w,x2-x1+100)
168  if h2 > h or w2 > w:
169  self.canvascanvas.resize(w2,h2)
170 
171  for n in G:
172  pos=n.attr['pos'] #position is given in points (72 points par inch, so 1 point = dpi/72=1.34)
173  x,y=eval(pos)
174  x=aspect*x
175  y=aspect*y
176  item=self.citemscitems[int(n)]
177  x0=item.x()
178  y0=item.y()
179  x=x-x0
180  y=y-y0
181  item.moveBy(x,y)
182 
183  self.canvascanvas.update()
184 
185  def clearLinks(self):
186  items=list(self.citemscitems.values())
187  for node in items:
188  for port in node.outports:
189  if not hasattr(port,"links"):
190  continue
191  for link in port.links():
192  link.clearPoints()
193  self.canvascanvas.update()
194 
195  def orthoLinks(self):
196  items=list(self.citemscitems.values())
197  g=grid(items)
198  for node in items:
199  for port in node.outports:
200  if not hasattr(port,"links"):
201  continue
202  for link in port.links():
203  #clear all intermediate points of the link
204  link.clearPoints()
205  #if isinstance(link,CItems.ControlLinkItem):
206  # print port.port.getNode().getName() +"->"+link.toPort.port.getNode().getName()
207  #else:
208  # print port.port.getNode().getName() +":"+port.port.getName()+"->"+link.toPort.port.getNode().getName()+":"+link.toPort.port.getName()
209  #print (port.x(),port.y()),(link.toPort.x(),link.toPort.y())
210  x0,y0=port.x()+5,port.y()
211  while g.get((x0,y0)).blocked:
212  x0=x0+1
213  x1,y1=link.toPort.x()-5,link.toPort.y()
214  while g.get((x1,y1)).blocked:
215  x1=x1-1
216  path=g.findPath((x0,y0),(x1,y1))
217  #print path
218  if len(path)==1:
219  if port.y() == link.toPort.y():
220  #near ports face to face
221  continue
222  else:
223  x,y=path[0]
224  path=[(x,port.y()),(x,link.toPort.y())]
225  elif len(path)==2:
226  x1,y1=path[0]
227  x2,y2=path[1]
228  if x1 == x2:
229  #vertical line
230  path=[(x1,port.y()),(x1,link.toPort.y())]
231  else:
232  #horizontal line
233  if port.y() == link.toPort.y():
234  #near ports face to face
235  continue
236  else:
237  #transform it into a vertical line
238  x=(x1+x2)/2
239  path=[(x,port.y()),(x,link.toPort.y())]
240 
241  #adjust the first point to the same y as port
242  x0,y0=path[0]
243  x1,y1=path[1]
244  if y0==y1:
245  #remove first point and adjust second one
246  del path[0]
247  x0=x1
248  path[0]=x0,port.y()
249  #adjust the last point to the same y as link.toPort
250  x0,y0=path[-1]
251  x1,y1=path[-2]
252  if y0==y1:
253  #remove last point and adjust new last one
254  del path[-1]
255  x0=x1
256  path[-1]=x0,link.toPort.y()
257  #print path
258 
259  #add intermediate points
260  for x,y in path:
261  line=link.lines[-1]
262  link.splitLine(line,x,y)
263  self.canvascanvas.update()
264 
265 def attrs(g,t=gv.AGRAPH):
266  ah=None
267  while 1:
268  ah=gv.agnxtattr(g.handle,t,ah)
269  value=gv.agxget(g.handle,ah)
270  yield gv.agattrname(ah),value
271 
272 def h(x,y,destx,desty):
273  return abs(destx-x)+abs(desty-y)
274 
275 def distance(node,new_node):
276  x,y=node.coord
277  x1,y1=new_node.coord
278  d= abs(x1-x)+abs(y1-y)
279  if node.parent != None:
280  x0,y0=node.parent.coord
281  if (x1-x)*(y0-y)-(y1-y)*(x0-x) != 0:
282  #corner add some cost to penalize
283  d=d+1
284  return d
285 
286 class node:
287  def __init__(self,coord,index):
288  self.coordcoord=coord
289  self.indexindex=index
290  self.blockedblocked=0
291  self.totaltotal=0
292  self.path_costpath_cost=0
293  self.parentparent=None
294  self.openopen=0
295  self.closeclose=0
296 
297 class grid:
298  def __init__(self,graph):
299  self.graphgraph=graph
300  xs=set()
301  ys=set()
302  xmargin=5
303  ymargin=5
304  for n in graph:
305  h=n.height()
306  w=n.width()
307  x=n.x()
308  y=n.y()
309  xs.add(x-xmargin)
310  xs.add(x+w+xmargin)
311  ys.add(y-ymargin)
312  ys.add(y+h+ymargin)
313 
314  xs=list(xs)
315  xs.sort()
316  x0=xs[0]-36
317  xs.insert(0,x0)
318  x0=xs[-1]+36
319  xs.append(x0)
320 
321  ys=list(ys)
322  ys.sort()
323  y0=ys[0]-36
324  ys.insert(0,y0)
325  y0=ys[-1]+36
326  ys.append(y0)
327 
328  self.xsxs=xs
329  self.ysys=ys
330  self.colscols=[]
331  for w in range(len(xs)-1):
332  col=[]
333  x=(xs[w]+xs[w+1])/2
334  for h in range(len(ys)-1):
335  y=(ys[h]+ys[h+1])/2
336  col.append(node((x,y),(w,h)))
337  self.colscols.append(col)
338 
339  for n in graph:
340  h=n.height()
341  w=n.width()
342  x=n.x()
343  y=n.y()
344  l1,l2=bisect.bisect_left(ys,y-ymargin),bisect.bisect_left(ys,y+h+ymargin)
345  i1,i2=bisect.bisect_left(xs,x-xmargin),bisect.bisect_left(xs,x+w+xmargin)
346  for c in self.colscols[i1:i2]:
347  for e in c[l1:l2]:
348  e.blocked=1
349 
350  #for col in self.cols:
351  # print [e.coord +(e.blocked,) for e in col]
352 
353  def reset(self):
354  for c in self.colscols:
355  for n in c:
356  n.open=n.close=0
357  n.total= n.path_cost=0
358  n.parent=None
359 
360  def get(self,coord):
361  x,y=coord
362  col= bisect.bisect_left(self.xsxs,x)-1
363  if col < 0 or col >= len(self.colscols):
364  return None
365  col=self.colscols[col]
366  row=bisect.bisect_left(self.ysys,y)-1
367  if row < 0 or row >= len(col):
368  return None
369  return col[row]
370 
371  def getPath(self,node):
372  path=[node.coord]
373  parent=node.parent
374  while parent:
375  prev=node
376  node=parent
377  parent=node.parent
378  if parent:
379  #if points are aligned don't keep the middle point
380  x,y=node.coord
381  x0,y0=prev.coord
382  x1,y1=parent.coord
383  if (x1-x)*(y0-y)-(y1-y)*(x0-x) == 0:
384  continue
385  path.append(node.coord)
386  path.reverse()
387  return path
388 
389  def neighbours(self,node):
390  col,row=node.index
391  l=[]
392  steps=((0, +1), (+1, 0), (0, -1), (-1, 0 ))
393  for step in steps:
394  c=col+step[0]
395  r=row+step[1]
396  if c < 0 or c >=len(self.colscols):
397  continue
398  co=self.colscols[c]
399  if r <0 or r >= len(co):
400  continue
401  n=co[r]
402  if n.blocked:
403  continue
404  l.append(n)
405  return l
406 
407  def findPath(self,fromLoc,toLoc):
408  """Find shortest path from fromLoc to toLoc"""
409  self.resetreset()
410  self.openopen=[]
411  fromNode=self.getget(fromLoc)
412  self.openopen.append((fromNode.total,fromNode))
413  toNode=self.getget(toLoc)
414  if toNode.blocked:
415  print("toNode is blocked")
416  return []
417  destx,desty=toNode.coord
418  while self.openopen:
419  #if open is not void, take the best node (the first one)
420  t,node=self.openopen.pop(0)
421  node.close=1
422  if node == toNode:
423  #got toLoc
424  return self.getPathgetPath(node)
425 
426  for new_node in self.neighboursneighbours(node):
427  if new_node.close :
428  continue
429  x,y =new_node.coord
430  path_cost=node.path_cost+distance(node,new_node)
431  total=path_cost+h(x,y,destx,desty)
432  if new_node.open :
433  #the node is already in open
434  if total < new_node.total:
435  self.openopen.remove((new_node.total,new_node))
436  new_node.path_cost=path_cost
437  new_node.parent=node
438  new_node.total=total
439  bisect.insort(self.openopen,(new_node.total,new_node))
440  else:
441  #the node is not yet in open
442  new_node.path_cost=path_cost
443  new_node.parent=node
444  new_node.total=total
445  bisect.insort(self.openopen,(new_node.total,new_node))
446  new_node.open=1
447 
448  return []
449 
def addLink(self, link)
Definition: graph.py:93
def addItem(self, item)
Definition: graph.py:112
def orthoLinks(self)
Definition: graph.py:195
def clearLinks(self)
Definition: graph.py:185
def __init__(self, item, parent)
Definition: graph.py:41
def selectItem(self, item)
Definition: graph.py:119
def createGraph(self)
Definition: graph.py:55
def layout(self, rankdir)
Definition: graph.py:123
def customEvent(self, event)
Definition: graph.py:35
def getPath(self, node)
Definition: graph.py:371
def reset(self)
Definition: graph.py:353
def findPath(self, fromLoc, toLoc)
Definition: graph.py:407
def neighbours(self, node)
Definition: graph.py:389
def get(self, coord)
Definition: graph.py:360
def __init__(self, graph)
Definition: graph.py:298
def __init__(self, coord, index)
Definition: graph.py:287
def distance(node, new_node)
Definition: graph.py:275
def h(x, y, destx, desty)
Definition: graph.py:272
def attrs(g, t=gv.AGRAPH)
Definition: graph.py:265