site_graphlogo
- Terms of Use and Privacy
Source Code
rss
site_graphlogo
- Terms of Use and Privacy
Source Code
rss

Source Code | Triple Pub Console

For a video on how this works, see this demonstration. Also, see Triple Pub.

#!/usr/bin/python3
# coding=utf-8
# Triple Pub Console
# The person who associated a work with this deed has dedicated
# the work to the public domain by waiving all of his or her rights
# to the work worldwide under copyright law, including all related
# and neighboring rights, to the extent allowed by law.
# You can copy, modify, distribute and perform the work, even for
# commercial purposes, all without asking permission.
# In no way are the patent or trademark rights of any person affected by
# CC0, nor are the rights that other persons may have in the work or in
# how the work is used, such as publicity or privacy rights.
# Unless expressly stated otherwise, the person who associated a work with
# this deed makes no warranties about the work, and disclaims liability
# for all uses of the work, to the fullest extent permitted by applicable law.
# When using or citing the work, you should not imply endorsement by the 
# author or the affirmer.
# https://creativecommons.org/publicdomain/zero/1.0/

import paho.mqtt.client as mqttc
import paho.mqtt.subscribe as subscribe
import paho.mqtt.publish as publish
import wx
import sys
import time
import pygraphviz as pgv
import wx.lib.ogl as ogl
import re
from datetime import datetime,date
from wxasync import WxAsyncApp, AsyncBind,StartCoroutine
import asyncio
from asyncio.events import get_event_loop
from queue import Queue
aid=sys.argv[1].rstrip()
#aid no spaces - designation of application/user
mqttbroker=sys.argv[2].rstrip()
q=Queue()
dfd={}
graph='0'
dfd[graph]= pgv.AGraph()
scale={'0':80}
left={'0':50}
top={'0':50}
accel=False
last_icon='x'
mouse_up=True
node_grf={}
node_lbl={}
lvl_highnum={'0':''}
log={}
gvizlay={'0':'sfdp'}
colr={'orange':'#EE7733','blue':'#0077BB','cyan':'#33BBEE','magenta':'#EE3377',
      'red':'#CC3311','teal':'#009988','grey':'#BBBBBB','black':'#000000'}
#palette for color blindness from https://personal.sron.nl/~pault/
def route_message(pri,mess):
   ts=datetime.utcnow().isoformat(sep='T', timespec='milliseconds').replace(':','').replace('-','').replace(' ','')+'Z'
   q.put(pri+' '+ts+' '+aid+' '+mess)
   publish.single("allorg/dfd/full",pri+' '+ts+' '+aid+' '+graph+' '+aid+' 🛎ī¸ '+aid,hostname=mqttbroker)
   #we double usage as a pub/sub to go into the wx loop, so need to notify there is a new message
class clr(wx.Pen):
   def __init__(self,color,width):
      wx.Pen.__init__(self)
      self.SetColour(colr[color])
      self.SetWidth(width)
class brsh(wx.Brush):
   def __init__(self,color):
      wx.Brush.__init__(self)
      self.SetColour(colr[color])
class GaneSarson(ogl.DrawnShape):
   def __init__(self,gsid,label):
      ogl.DrawnShape.__init__(self)
      global lvl_highnum
      self.SetDrawnBrush(wx.WHITE_BRUSH)
      self.SetDrawnTextColour(wx.BLACK)
      self.SetDrawnFont(wx.Font(10, wx.FONTFAMILY_SWISS, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL))
      mo=re.search('\d|\.',gsid)
      lines=label.split('\\n')
      if gsid.find('D')!=-1 and mo:
         self.SetDrawnPen(clr('orange',2))
         hr=((len(max(lines,key=len))*8)+4)/2+16
         vr=(max(len(gsid)*12,len(lines)*12)+3)/2+1
         self.DrawLine((-hr, -vr), (hr, -vr))
         self.DrawLine((-hr, -vr), (-hr, vr))
         self.DrawLine((-hr+16, -vr), (-hr+16, vr))
         self.DrawLine((-hr, vr), (hr, vr))
         li=len(gsid)*-6.6
         for c in gsid:
            self.DrawText(c,(-hr+3,li))
            li+=12
         ll=len(lines)*-6.6
         for ln in lines:
            self.DrawText(ln,(-hr+19,ll))
            ll+=12
      elif mo:
         self.SetDrawnPen(clr('magenta',2))
         if graph not in lvl_highnum:
            lvl_highnum[graph]=gsid
         else:
            if gsid>lvl_highnum[graph]:
               lvl_highnum[graph]=gsid
         hr=((len(max(lines,key=len))*8))/2+6
         vr=(((len(lines)+1)*6.6))/2+3
         self.DrawRoundedRectangle((-hr, -vr-18, 2*hr, 2*vr+36), 5)
         self.DrawLine((-hr, -vr), (hr, -vr))
         self.DrawText(gsid, (-1*len(gsid)*5+1, -vr-17))
         ll=len(lines)*-6.6
         for ln in lines:
            self.DrawText(ln,(-hr+4,ll+10))
            ll+=12
      if not(gsid.find('D')!=-1 and mo) and not mo:
         self.SetDrawnPen(clr('cyan',2))
         hr=((len(max(lines,key=len))*8)+4)/2+2
         vr=((len(lines)*12)+3)/2+6
         self.DrawRectangle((-hr, -vr, 2*hr, 2*vr))
         ll=len(lines)*-6.6
         for ln in lines:
            self.DrawText(ln,(-hr+3,ll))
            ll+=12
      self.CalculateSize()
class NodeEvtHandler(ogl.ShapeEvtHandler):
   def __init__(self):
      ogl.ShapeEvtHandler.__init__(self)
   def OnLeftClick(self, draw,x, y, keys = 0, attachment = 0):
      shape = self.GetShape()
      canvas = shape.GetCanvas()
      for n in node_grf:
         if node_grf[n]==shape:
            if 'label' in dfd[graph].get_node(n).attr:
               route_message('🐜',graph+' '+aid+' 🤛 '+dfd[graph].get_node(n).attr['label'])
            route_message('🐜',graph+' '+aid+' 👈 '+n[n.rfind('.')+1:])
   def OnRightClick(self, draw,x, y, keys = 0, attachment = 0):
      shape = self.GetShape()
      canvas = shape.GetCanvas()
      for n in node_grf:
         if node_grf[n]==shape:
            if 'label' in dfd[graph].get_node(n).attr:
               route_message('🐜',graph+' '+aid+' 🤜 '+dfd[graph].get_node(n).attr['label'])
            route_message('🐜',graph+' '+aid+' 👉 '+n[n.rfind('.')+1:])
   def OnDragLeft(self, draw,x, y, keys = 0, attachment = 0):
      shape = self.GetShape()
      canvas = shape.GetCanvas()
      dc=wx.ClientDC(canvas)
      canvas.DoPrepareDC(dc)
      shape.Move(dc,x,y)
      canvas.Refresh()
   def OnBeginDragLeft(self, x, y, keys = 0, attachment = 0):
      six=9
   def OnEndDragLeft(self, x, y, keys = 0, attachment = 0):
      shape = self.GetShape()
      canvas = shape.GetCanvas()
      canvas.Refresh(False)
      dc=wx.ClientDC(canvas)
      for n in node_lbl:
         if node_lbl[n]==shape:
            route_message('ℹī¸',graph+' '+n+' 📐 '+str(x)+','+str(y))
      for n in node_grf:
         if node_grf[n]==shape:
            route_message('ℹī¸',graph+' '+n+' 📐 '+str(x)+','+str(y))
class CButton(wx.Button):
   def __init__(self,parent,txt,):
      wx.Button.__init__(self,parent,-1,txt,size=wx.Size(35,35),style=wx.BU_EXACTFIT|wx.BORDER_NONE)
      self.Bind(wx.EVT_LEFT_DOWN, self.OnLeftDown)
      self.Bind(wx.EVT_LEFT_UP, self.OnLeftUp)
      self.Bind(wx.EVT_LEFT_DCLICK, self.OnDClick)
   def OnLeftDown(self, event):
      global last_icon
      global mouse_up
      if mouse_up:
         last_icon=self.GetLabel() 
         self.SetLabel('✨')
      mouse_up=False
      event.Skip()
   def OnLeftUp(self, event):
      global mouse_up
      self.SetLabel(last_icon)
      mouse_up=True
      event.Skip()
   def OnDClick(self, event):
      global accel
      self.SetLabel(last_icon)
      accel=True
      event.Skip()
class logwindow(wx.Frame):
   def __init__(self, parent,txt):
      wx.Frame.__init__( self,parent, size=(503,320), style=wx.DEFAULT_FRAME_STYLE )
      panel=wx.Panel(self, -1)
      log_txt = wx.TextCtrl(self,size=(500,310),style=wx.TE_MULTILINE)
      log_txt.write(txt)
      self.Centre()
      self.Show()
class grp_frm(wx.Frame):
   def __init__(self):
      wx.Frame.__init__( self,
         None, -1, "Console",
         size=(958,1053),
         style=wx.DEFAULT_FRAME_STYLE )
      wrn_btn = CButton(self,'🟩')
      min_btn = CButton(self,'➖')
      pls_btn = CButton(self,'➕')
      plf_btn = CButton(self,'◀ī¸')
      prt_btn = CButton(self,'â–ļī¸')
      pup_btn = CButton(self,'đŸ”ŧ')
      pdn_btn = CButton(self,'đŸ”Ŋ')
      pub_btn = CButton(self,'đŸ“Ŗ')
      exp_btn = CButton(self,'📤')
      imp_btn = CButton(self,'đŸ“Ĩ')
      lay_btn = CButton(self,'🎡')
      rfs_btn = CButton(self,'🔄')
      dir_btn = CButton(self,'↔ī¸')
      lvl_txt = wx.TextCtrl(self,size=(70,-1),style=wx.TE_PROCESS_ENTER)
      sid_txt = wx.TextCtrl(self,style=wx.TE_RIGHT)
      slb_txt = wx.TextCtrl(self,style=wx.TE_RIGHT)
      oid_txt = wx.TextCtrl(self)
      olb_txt = wx.TextCtrl(self)
      grp_vsz = wx.BoxSizer( wx.VERTICAL )
      btn_hsz = wx.BoxSizer( wx.HORIZONTAL)
      uip_hsz = wx.BoxSizer( wx.HORIZONTAL)
      btn_hsz.Add(lay_btn,0)
      btn_hsz.Add(min_btn,0)
      btn_hsz.Add(pls_btn,0)
      btn_hsz.Add(pup_btn,0)
      btn_hsz.Add(pdn_btn,0)
      btn_hsz.Add(plf_btn,0)
      btn_hsz.Add(prt_btn,0)
      btn_hsz.Add(lvl_txt,1,wx.EXPAND| wx.ALL, 1)
      btn_hsz.Add(slb_txt,9,wx.EXPAND| wx.ALL, 1)
      btn_hsz.Add(sid_txt,4,wx.EXPAND| wx.ALL, 1)
      btn_hsz.Add(dir_btn,0)
      btn_hsz.Add(oid_txt,4,wx.EXPAND| wx.ALL, 1)
      btn_hsz.Add(olb_txt,9,wx.EXPAND| wx.ALL, 1)
      btn_hsz.Add(pub_btn,0)
      btn_hsz.Add(rfs_btn,0)
      btn_hsz.Add(wrn_btn,0)
      btn_hsz.Add(exp_btn,0)
      btn_hsz.Add(imp_btn,0)
      grp_vsz.Add( btn_hsz, 0,wx.EXPAND)
      grp_vsz.Add( uip_hsz, 1,wx.EXPAND)
      nav_tre = wx.TreeCtrl(self, -1, wx.DefaultPosition, size=(90,-1))
      font = wx.SystemSettings.GetFont(wx.SYS_SYSTEM_FONT)
      font.SetPointSize(20)
      self.SetFont(font)
      canvas = ogl.ShapeCanvas( self )
      canvas.SetScrollbars(1,1, 2000, 4000)
      uip_hsz.Add( nav_tre, 0, wx.GROW )
      uip_hsz.Add( canvas, 9, wx.GROW )
      canvas.SetBackgroundColour( "White" ) 
      lvl_txt.SetValue(graph)
      diagram = ogl.Diagram()
      diagram.SetSnapToGrid(False)
      canvas.SetVirtualSize((4000,2000))
      canvas.SetScrollRate(20,20)
      canvas.SetDiagram( diagram )
      diagram.SetCanvas( canvas )
      font.SetPointSize(12)
      lvl_txt.SetFont(font)
      slb_txt.SetFont(font)
      sid_txt.SetFont(font)
      oid_txt.SetFont(font)
      olb_txt.SetFont(font)
      root = nav_tre.AddRoot('0') 
      self.Bind(wx.EVT_BUTTON,lambda event: publish_bar(),pub_btn)
      self.Bind(wx.EVT_BUTTON,lambda event: autoroute(),lay_btn)
      self.Bind(wx.EVT_BUTTON,lambda event: directions(),dir_btn)
      self.Bind(wx.EVT_BUTTON,lambda event: plus(),pls_btn)
      self.Bind(wx.EVT_BUTTON,lambda event: minus(),min_btn)
      self.Bind(wx.EVT_BUTTON,lambda event: subtractleft(),plf_btn)
      self.Bind(wx.EVT_BUTTON,lambda event: addleft(),prt_btn)
      self.Bind(wx.EVT_BUTTON,lambda event: subtracttop(),pup_btn)
      self.Bind(wx.EVT_BUTTON,lambda event: addtop(),pdn_btn)
      self.Bind(wx.EVT_BUTTON,lambda event: refresh_screen(),rfs_btn)
      self.Bind(wx.EVT_BUTTON,lambda event: showlog(),wrn_btn)
      self.Bind(wx.EVT_BUTTON,lambda event: export_dfd(),exp_btn)
      self.Bind(wx.EVT_BUTTON,lambda event: import_dfd(),imp_btn)
      self.Bind(wx.EVT_TEXT_ENTER,lambda event: change_level(),lvl_txt)
      self.Bind(wx.EVT_TREE_ITEM_ACTIVATED,lambda event: leaf(event),nav_tre)
      self.SetSizer(grp_vsz)
      self.SetAutoLayout(1)
      self.Show(1)
      def refresh_screen():
         global node_grf
         global accel 
         global dfd
         global lvl_highnum
         global lvl_lbl
         lvl_highnum={'0':''}
         if accel:
            accel=False
         diagram.DeleteAllShapes() 
         canvas.Refresh(True)
         for n in dfd[graph]:
            if dfd[graph].get_node(n).attr['label']==None or dfd[graph].get_node(n).attr['label']=='\\N':
               lbl=n
            else:
               lbl=dfd[graph].get_node(n).attr['label']
            try:
               node_grf[n]=Node(self,GaneSarson(n,lbl),dfd[graph].get_node(n).attr['pos'])
            except:
               node_grf[n]=Node(self,GaneSarson(n,lbl),'0,0')
            canvas.AddShape(node_grf[n])
         lvl_lbl={'0':'Level 0'}
         for g in dfd:
            for n in dfd[g]:
               if dfd[g].get_node(n).attr['label']==None:
                  lbl=n
               else:
                  lbl=dfd[g].get_node(n).attr['label']
               try:
                  if g=='0':
                     dfd[n].graph_attr['label']='Level 0'
                  else:
                     dfd[n].graph_attr['label']=lbl.replace('\\n',' ')
               except:
                  six=9
         for e in dfd[graph].edges_iter():
            if e.attr['dir']=='both':
               Edge(node_grf[e[0].get_name()],node_grf[e[1].get_name()]) 
               Edge(node_grf[e[1].get_name()],node_grf[e[0].get_name()]) 
            elif e.attr['dir']=='forward':
               Edge(node_grf[e[0].get_name()],node_grf[e[1].get_name()]) 
            elif e.attr['dir']=='back':
               Edge(node_grf[e[1].get_name()],node_grf[e[0].get_name()]) 
         wrn_btn.SetLabel('🟩')
         dc=wx.ClientDC(canvas)
         diagram.ShowAll( 1 )
         diagram.Redraw(dc)
         canvas.Draw()
         if graph not in lvl_highnum:
         #****************  fix for natsort
            lvl_highnum[graph]='0'  
         try:
            self.SetTitle(aid+' - '+gvizlay[graph]+' - '+dfd[graph].graph_attr['label']+' - '+lvl_highnum[graph])
         except:
            six=9 
      def update_tre():
         nav_tre.DeleteChildren(root)
         for i in dfd:
            if i !='0':
               nav_tre.AppendItem(root,i)
               nav_tre.SortChildren(root) 
      def clear_pub():
         sid_txt.SetValue('')
         slb_txt.SetValue('')
         oid_txt.SetValue('')
         olb_txt.SetValue('')
      def find_selection():   
         if nav_tre.GetSelection()!=graph:
            (child, cookie) = nav_tre.GetFirstChild(nav_tre.RootItem)
            while child.IsOk():
               if nav_tre.GetItemText(child)==graph:
                  nav_tre.SetFocusedItem(child)         
               (child, cookie) = nav_tre.GetNextChild(nav_tre.RootItem, cookie)
      def change_level():
         global dfd
         global graph
         global scale
         global left
         global top
         graph=lvl_txt.GetValue()
         if graph in gvizlay:
            if gvizlay[graph]=='neato':
               lay_btn.SetLabel('⚛ī¸')
            elif gvizlay[graph]=='dot':
               lay_btn.SetLabel('đŸ”ĩ')
            elif gvizlay[graph]=='twopi':
               lay_btn.SetLabel('đŸĨ§')
            elif gvizlay[graph]=='circo':
               lay_btn.SetLabel('⭕')
            elif gvizlay[graph]=='fdp':
               lay_btn.SetLabel('⚖ī¸')
            else:
               lay_btn.SetLabel('🎡')
         else:
            gvizlay[graph]='sfdp'
            lay_btn.SetLabel('🎡')
         node_grf.clear()
         if graph not in scale:
            scale[graph]=80
         if graph not in left:
            left[graph]=50
         if graph not in top:
            top[graph]=50
         if graph not in dfd:
            route_message('ℹī¸',graph+' '+aid+' 💡 '+graph)
            dfd[graph]= pgv.AGraph()
            update_tre()
         find_selection()
         clear_pub()
         route_message('🐜',graph+' '+aid+' 📝 '+graph)
      def leaf(evt):
         item=evt.GetItem()
         selected=nav_tre.GetItemText(item)
         if selected!=graph:
            lvl_txt.SetValue(selected) 
            change_level()
      lay_btn.GetLabel() 
      def import_dfd():
         with wx.FileDialog(self, "Open Triple Pub Console DFD", wildcard="*.dfd;*.dfd.trim",
            style=wx.FD_OPEN | wx.FD_FILE_MUST_EXIST) as fileDialog:

            if fileDialog.ShowModal() == wx.ID_CANCEL:
               return     
            pathname = fileDialog.GetPath()
            try:
               with open(pathname, 'rb') as f:
                  for l in f.read().splitlines():
                     q.put(l.decode("utf-8"))
            except IOError:
               wx.LogError("Cannot open file '%s'." % newfile)
         ts=datetime.utcnow().isoformat(sep='T', timespec='milliseconds').replace(':','').replace('-','').replace(' ','')+'Z'
         publish.single("allorg/dfd/full",'🐜 '+ts+' '+
            aid+' '+graph+' '+aid+' 🛎ī¸ '+aid,hostname=mqttbroker)
      def export_dfd():
         logfin=[]
         with wx.FileDialog(self, "Save Triple Pub Console DFD", wildcard="*.dfd.*",
                       style=wx.FD_SAVE | wx.FD_OVERWRITE_PROMPT) as fileDialog:
            if fileDialog.ShowModal() == wx.ID_CANCEL:
               return
            pathname = fileDialog.GetPath()
            try:
               with open(pathname, 'w+') as f:
                  if pathname[-9:]=='.dfd.trim':
                     for l in log:
                        b=l.split(' ')
                        mess=b[3]+' '+b[4]+' '+b[5]+l[l.rfind(b[5]+' ')+1:]  
                        if b[0]!='🐜' and b[5]!='📐' and mess not in logfin: 
                           f.write(l+'\n')
                           logfin.append(mess)
                  else:
                     for l in log:
                        f.write(l+'\n')
            except IOError:
               wx.LogError("Cannot save current data in file '%s'." % pathname)
      def Edge(node1,node2):
         line = ogl.LineShape()
         line.SetCanvas(canvas)
         line.SetPen(clr('black',1))
         line.SetBrush(brsh('black'))
         line.AddArrow(ogl.ARROW_ARROW)
         line.MakeLineControlPoints(2)
         node1.AddLine(line,node2)
         diagram.AddShape(line)
      def Node(self,shape,pos):
         [x,y]=pos.split(',')
         shape.SetDraggable(True, True)
         shape.SetCanvas(canvas)
         shape.SetX(float(x))
         shape.SetY(float(y))
         shape.Show(True)
         evthandler = NodeEvtHandler()
         evthandler.SetShape(shape)
         evthandler.SetPreviousHandler(shape.GetEventHandler())
         shape.SetEventHandler(evthandler)  
         return shape
      def process_line(gs,mess,refresh):
            b=mess.split(' ')
            if b[5] in ('👉','👈','🤜','🤛','💡','🔍','📝'):
               if b[5]=='📝':
                  refresh_screen() 
               elif b[5]=='👉':
                  oid_txt.SetValue(mess[mess.rfind('👉')+2:])
               elif b[5]=='👈': 
                  sid_txt.SetValue(mess[mess.rfind('👈')+2:])
               elif b[5]=='🤜':
                  olb_txt.SetValue(mess[mess.rfind('🤜')+2:])
               elif b[5]=='🤛': 
                  slb_txt.SetValue(mess[mess.rfind('🤛')+2:])
               elif b[5]=='🔍':
                  if graph=='0':
                     lvl_txt.SetValue(mess[mess.rfind('🔍')+2:])
                  else:
                     lvl_txt.SetValue(graph+'.'+mess[mess.rfind('🔍')+2:])
                  change_level()
               elif b[5]=='💡':
                  if b[6] not in gs:
                     gs[b[6]]= pgv.AGraph()
                     update_tre()
                     find_selection()
               return(refresh)
            else:
               if b[3] not in gs:
                  gs[b[3]]=pgv.AGraph()
                  update_tre()
               if b[5] in ('↔ī¸','➡ī¸','âŦ…ī¸','📐'):
                  if b[5]!='📐':
                     gs[b[3]].add_node(b[4])
                     if b[5]=='↔ī¸':
                        gs[b[3]].add_edge(b[4],b[6],dir='both')
                     if b[5]=='➡ī¸':
                        gs[b[3]].add_edge(b[4],b[6],dir='forward')
                     if b[5]=='âŦ…ī¸':
                        gs[b[3]].add_edge(b[4],b[6],dir='back')
                  else:
                     gs[b[3]].add_node(b[4],pos=b[6])
               elif b[5]=='🏷ī¸' and b[4].find('DataFlow')==-1:
                  gs[b[3]].add_node(b[4],label=mess[mess.rfind('🏷ī¸')+3:])
               elif b[5]=='đŸšĢ':
                  if b[4]==b[6]:
                     gs[b[3]].remove_node(b[4])
                  else:
                     gs[b[3]].remove_edge(b[4],b[6])
            return('đŸŸĨ')
      async def process_queue():
         wrn_btn.SetLabel('đŸŸĨ')
         global dfd
         while not q.empty():
            mess=q.get()
            b=mess.split(' ')
            if b[5]!='🛎ī¸':
               wrn_btn.SetLabel(process_line(dfd,mess, wrn_btn.GetLabel()))
               if b[5]!='📝' and mess not in log:
                  publish.single("allorg/dfd/full",mess,hostname=mqttbroker)
                  log[mess]=datetime.utcnow().isoformat(sep='T', timespec='milliseconds').replace(':','').replace('-','').replace(' ','')+'Z'
            await asyncio.sleep(0.1)
      def on_message(client, userdata, mess):
         q.put(mess.payload.decode("utf-8"))
         StartCoroutine(process_queue,self)
      client = mqttc.Client()         
      client.connect(mqttbroker, 1883, 60)
      client.on_message = on_message
      client.subscribe("allorg/dfd/full")
      client.loop_start() 
      def showlog():
         flog=''
         for l in log:
            flog+=l+'\n'
         log_win = logwindow(self,flog)
         log_win.Show()
      def publish_bar():
         global accel
         if accel:
            accel=False
            route_message('ℹī¸',graph+' '+aid+' 🎨 '+graph)
         if dir_btn.GetLabel()!='đŸšĢ':
            if sid_txt.GetValue()!='' and oid_txt.GetValue()!='':
               route_message('ℹī¸',graph+' '+sid_txt.GetValue()+' '+dir_btn.GetLabel()+' '+oid_txt.GetValue())
            if sid_txt.GetValue()!='' and slb_txt.GetValue()!='' and dir_btn.GetLabel()!='🏷ī¸':
               route_message('ℹī¸',graph+' '+sid_txt.GetValue()+' 🏷ī¸ '+slb_txt.GetValue())
            if oid_txt.GetValue()!='' and olb_txt.GetValue()!='' and dir_btn.GetLabel()!='🏷ī¸':
               route_message('ℹī¸',graph+' '+oid_txt.GetValue()+' 🏷ī¸ '+olb_txt.GetValue())
         else:
            if sid_txt.GetValue()!='' and oid_txt.GetValue()!='':
               route_message('ℹī¸',graph+' '+sid_txt.GetValue()+' '+dir_btn.GetLabel()+' '+oid_txt.GetValue())
      def autoroute():
         global accel
         global gvizlay
         global dfd
         if accel:
            accel=False
            if lay_btn.GetLabel() == '🎡':
               lay_btn.SetLabel('đŸ”ĩ')
               gvizlay[graph]='dot'
            elif lay_btn.GetLabel() == 'đŸ”ĩ':
               lay_btn.SetLabel('đŸĨ§')
               gvizlay[graph]='twopi'
            elif lay_btn.GetLabel() == 'đŸĨ§':
               lay_btn.SetLabel('⭕')
               gvizlay[graph]='circo'
            elif lay_btn.GetLabel() == '⭕':
               lay_btn.SetLabel('⚛ī¸')
               gvizlay[graph]='neato'
            else:
               lay_btn.SetLabel('🎡')
               gvizlay[graph]='sfdp'
         else:
            dfd[graph].graph_attr.update(sep="+10",root="1", overlap="false",splines="true",concentrate="false")
            dfd[graph].layout(gvizlay[graph])
            for n in dfd[graph]:
               [ax,ay]=dfd[graph].get_node(n).attr['pos'].split(',')
               route_message('ℹī¸',graph+' '+n+' 📐 '+str(round(float(ax)*scale[graph]/100+left[graph]))+','+str(round(float(ay)*scale[graph]/100+top[graph])))
            route_message('🐜',graph+' '+aid+' 📝 '+graph)
      def addleft():
         global left
         global accel
         accel=False
         left[graph]+=10
         autoroute()
      def subtractleft():
         global left
         global accel
         accel=False
         left[graph]+=-10
         autoroute()
      def addtop():
         global top 
         global accel
         accel=False
         top[graph]+=10
         autoroute()
      def subtracttop():
         global top 
         global accel
         accel=False
         top[graph]+=-10
         autoroute()
      def plus():
         global scale
         global accel
         accel=False
         scale[graph]+=10
         autoroute()
      def minus():
         global scale
         global accel
         accel=False
         scale[graph]+=-10
         autoroute()
      def directions():
         if dir_btn.GetLabel() == '↔ī¸':
            dir_btn.SetLabel('➡ī¸')
         elif dir_btn.GetLabel() =='➡ī¸':
            dir_btn.SetLabel('âŦ…ī¸')
         elif dir_btn.GetLabel() =='âŦ…ī¸':
            dir_btn.SetLabel('đŸšĢ')
         else:
            dir_btn.SetLabel('↔ī¸')
if __name__ == '__main__':
   app = WxAsyncApp()
   ogl.OGLInitialize()
   frame = grp_frm()
   frame.Show()
   app.SetTopWindow(frame)
   loop = get_event_loop()
   loop.run_until_complete(app.MainLoop())
triple_pub