From 3eb07c0b57ddefd0b82c81d34854e79f8dc79fdd Mon Sep 17 00:00:00 2001 From: bettleChen <2207153529@qq.com> Date: Thu, 13 Jul 2023 17:03:36 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E7=A8=8B=E5=BA=8FUI=EF=BC=8C?= =?UTF-8?q?=E5=B0=86=E6=95=B4=E4=BD=93UI=E5=A4=A7=E5=B0=8F=E6=94=BE?= =?UTF-8?q?=E5=A4=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .idea/dataSources.xml | 12 + NetworkAnalog/NetworkAnalog.py | 227 +++--- NetworkAnalog/NetworkAnalog_X1.py | 757 ------------------ NetworkAnalog/SimObjs.py | 42 +- .../__pycache__/NetworkAnalog.cpython-37.pyc | Bin 29916 -> 33600 bytes .../__pycache__/SimObjs.cpython-37.pyc | Bin 24584 -> 24496 bytes NetworkAnalog/network.db | Bin 40960 -> 40960 bytes NetworkAnalog/x1_test.py | 18 + 8 files changed, 181 insertions(+), 875 deletions(-) create mode 100644 .idea/dataSources.xml delete mode 100644 NetworkAnalog/NetworkAnalog_X1.py create mode 100644 NetworkAnalog/x1_test.py diff --git a/.idea/dataSources.xml b/.idea/dataSources.xml new file mode 100644 index 0000000..e33e3e5 --- /dev/null +++ b/.idea/dataSources.xml @@ -0,0 +1,12 @@ + + + + + sqlite.xerial + true + org.sqlite.JDBC + jdbc:sqlite:$PROJECT_DIR$/../网络仿真/NetworkAnalog/network.db + $ProjectFileDir$ + + + \ No newline at end of file diff --git a/NetworkAnalog/NetworkAnalog.py b/NetworkAnalog/NetworkAnalog.py index f4bfe9d..9391e16 100644 --- a/NetworkAnalog/NetworkAnalog.py +++ b/NetworkAnalog/NetworkAnalog.py @@ -7,12 +7,35 @@ from ttkbootstrap import ttk from tkinter import messagebox import re from PIL import ImageTk, Image -import platform from SimObjs import SimPacket, SimHost, AllSimConnect, SimRouter, SimSwitch, SimHub, SimBase from dbUtil import search, execute_sql, delete_obj, truncate_db +def round_rectangle(cv, x1, y1, x2, y2, radius=25, **kwargs): + points = [x1 + radius, y1, + x1 + radius, y1, + x2 - radius, y1, + x2 - radius, y1, + x2, y1, + x2, y1 + radius, + x2, y1 + radius, + x2, y2 - radius, + x2, y2 - radius, + x2, y2, + x2 - radius, y2, + x2 - radius, y2, + x1 + radius, y2, + x1 + radius, y2, + x1, y2, + x1, y2 - radius, + x1, y2 - radius, + x1, y1 + radius, + x1, y1 + radius, + x1, y1] + + return cv.create_polygon(points, **kwargs, smooth=True) + def validate_ip_address(ip_address): """ 匹配ip地址格式是否规范 @@ -62,16 +85,16 @@ class RouterConfigWindow(tk.Toplevel): def create_interface_inputs(self): label_text = ["接口1", "接口2", "接口3", "接口4"] for i in range(4): - label = tk.Label(self, text=label_text[i]) + label = tk.Label(self, text=label_text[i], font=("黑体", 16)) label.grid(row=i, column=0, padx=10, pady=5, sticky="w") - entry = tk.Entry(self, width=20) + entry = tk.Entry(self, width=20, font=("黑体", 16),) entry.grid(row=i, column=1, padx=10, pady=5, sticky="w") self.interface_entries.append(entry) - button = tk.Button(self, text="添加", command=lambda index=i: self.add_router_entry(index)) + button = tk.Button(self, text="添加", font=("黑体", 16), command=lambda index=i: self.add_router_entry(index)) button.grid(row=i, column=2, padx=10, pady=5) lab = LabelFrame(self, text="示例") lab.grid(row=4, column=0, columnspan=3, sticky=W, padx=20) - Label(lab, text="10.1.2.0/24 或者 10.1.2.12" if self.router_obj.ObjType == 2 else "MAC11").pack() + Label(lab, text="10.1.2.0/24 或者 10.1.2.12" if self.router_obj.ObjType == 2 else "MAC11", font=("黑体", 16)).pack() def create_router_table(self): def on_right_click(event): @@ -88,7 +111,10 @@ class RouterConfigWindow(tk.Toplevel): self.router_treeview.delete(item) self.router_table_frame = tk.Frame(self) self.router_table_frame.grid(row=5, column=0, columnspan=3, padx=10, pady=5) - self.router_treeview = ttk.Treeview(self.router_table_frame, columns=("Interface", "Route"), show="headings") + style = ttk.Style() + style.configure("Custom.Treeview.Heading", font=("宋体", 15)) + style.configure("Custom.Treeview", rowheight=30, font=("宋体", 15)) + self.router_treeview = ttk.Treeview(self.router_table_frame, style="Custom.Treeview", columns=("Interface", "Route"), show="headings") self.router_treeview.heading("Interface", text="接口") self.router_treeview.heading("Route", text="网段") self.router_treeview.pack(side="left", fill="both") @@ -203,6 +229,8 @@ class NetWorkAnalog(Canvas): Image.open(sys.path[0] + "/../datas/images/集线器.png").resize((60, 60))) self.host_img = ImageTk.PhotoImage( Image.open(sys.path[0] + "/../datas/images/主机.png").resize((60, 60))) + self.width = int(self.cget("width")) + self.height = int(self.cget("height")) self.chose = self.host_img self.AllSimObjs = {} self.conns = [] @@ -282,6 +310,7 @@ class NetWorkAnalog(Canvas): return conn = AllSimConnect(self, self.line_start_obj, self.line_start_ifs, self.line_end_obj, self.line_end_ifs) + conn.draw_line() for conn_obj in self.line_start_obj.connections: # 判断两个连接对象是否已经连接过了 if conn_obj == conn: flag = True @@ -309,15 +338,15 @@ class NetWorkAnalog(Canvas): if self.drawLine: self.delete("Line") x, y = event.x, event.y - if not (150 < x < 650 and 40 < y < 490): - if x < 150: - x = 150 - elif x > 650: - x = 650 - if y < 40: - y = 40 - elif y > 490: - y = 490 + if not (0 < x < self.width * 0.8 and 0 < y < self.height * 0.85): + if x < 0: + x = 0 + elif x > self.width * 0.8: + x = self.width * 0.8 + if y < 0: + y = 0 + elif y > self.height * 0.85: + y = self.height * 0.85 self.create_line(self.line_start_obj.ObjX, self.line_start_obj.ObjY, x + 8, y + 8, fill="#5b9bd5", width=1, tags="Line") @@ -345,7 +374,7 @@ class NetWorkAnalog(Canvas): 鼠标左键松开事件 :param event: 事件对象 """ - if 170 < event.x < 630 and 60 < event.y < 470: # 在方框内,无变化 + if 20 < event.x < self.width * 0.8 + 20 and 20 < event.y < self.height * 0.85 - 20: # 在方框内,无变化 if name == "路由器": tag = SimRouter(self, event.x, event.y) elif name == "集线器": @@ -354,6 +383,7 @@ class NetWorkAnalog(Canvas): tag = SimSwitch(self, event.x, event.y) else: tag = SimHost(self, event.x, event.y) + tag.create_img() self.AllSimObjs[tag.ObjID] = tag tag.save() self.tag_bind_event() @@ -374,7 +404,7 @@ class NetWorkAnalog(Canvas): else: self.chose = self.host_img self.delete("L") - if 170 < event.x < 630 and 60 < event.y < 470: # 在方框内,无变化 + if 20 < event.x < self.width * 0.8 + 20 and 20 < event.y < self.height * 0.85 - 20: # 在方框内,无变化 pass else: # 在方框外,显示禁止放置标识 self.create_oval(event.x - 10, event.y - 45, event.x + 10, event.y - 25, outline="red", width=2, @@ -410,6 +440,7 @@ class NetWorkAnalog(Canvas): tag = SimSwitch(self, ObjX, ObjY, ObjID, ConfigCorrect, ObjLable) else: tag = SimHub(self, ObjX, ObjY, ObjID, ConfigCorrect, ObjLable) + tag.create_img() self.AllSimObjs[tag.ObjID] = tag sim_conn_sql = "select s.conn_id, ConfigCorrect, node_id, node_ifs from sim_conn s join conn_config c on s.conn_id=c.conn_id" @@ -423,6 +454,7 @@ class NetWorkAnalog(Canvas): for key, value in conn_datas.items(): conn_obj = AllSimConnect(self, self.AllSimObjs[value[0][0]], value[0][1], self.AllSimObjs[value[1][0]], value[1][1], key[1]) + conn_obj.draw_line() self.AllSimObjs[value[0][0]].connections[value[0][1] - 1] = conn_obj # 将连接对象传入组件对象 self.AllSimObjs[value[1][0]].connections[value[1][1] - 1] = conn_obj self.conns.append(conn_obj) @@ -447,7 +479,9 @@ class NetWorkAnalog(Canvas): if isinstance(conn, AllSimConnect): conn.delete_line() delete_sql = f"delete from sim_conn where conn_id in (select conn_id from conn_config where node_id='{self.chose_obj.ObjID}')" + delete_config_sql = f"delete from conn_config where node_id='{self.chose_obj.ObjID}'" execute_sql(delete_sql) + execute_sql(delete_config_sql) delete_obj(self.chose_obj.ObjID) self.delete("rectangle") self.reload_data() @@ -532,12 +566,12 @@ class NetWorkAnalog(Canvas): return child1 = tk.Toplevel() child1.title(self.chose_obj.ObjLabel + "的标签信息") - child1.geometry('240x100+450+250') + child1.geometry('360x150+200+150') child1.grab_set() # 设置组件焦点抓取。使焦点在释放之前永远保持在这个组件上,只能在这个组件上操作 tk.Label(child1, text='原标签:' + self.chose_obj.ObjLabel, - font=('黑体', 12)).grid(row=0, column=0, columnspan=2, sticky='w') - tk.Label(child1, text='新标签:', font=('黑体', 12)).grid(row=1, column=0, sticky='w') # ,sticky='w'靠左显示 - new_name = tk.Entry(child1, font=('黑体', 12), textvariable=tk.StringVar()) + font=('黑体', 16)).grid(row=0, column=0, columnspan=2, sticky='w') + tk.Label(child1, text='新标签:', font=('黑体', 16)).grid(row=1, column=0, sticky='w') # ,sticky='w'靠左显示 + new_name = tk.Entry(child1, font=('黑体', 16), textvariable=tk.StringVar()) new_name.grid(row=1, column=1) def update_tag(): @@ -553,10 +587,10 @@ class NetWorkAnalog(Canvas): child1.destroy() messagebox.showinfo("提示", message="修改成功!") - tk.Button(child1, text='确定', font=('黑体', 10), height=1, command=update_tag).grid(row=2, column=0, - sticky='e') - tk.Button(child1, text='取消', font=('黑体', 10), height=1, command=child1.destroy).grid(row=2, column=1, - sticky='e') + tk.Button(child1, text='确定', font=('黑体', 16), height=1, command=update_tag).grid(row=2, column=0, + sticky='e', pady=10) + tk.Button(child1, text='取消', font=('黑体', 16), height=1, command=child1.destroy).grid(row=2, column=1, + sticky='e', pady=10) def network_config(self): # todo: 网络配置 @@ -572,7 +606,7 @@ class NetWorkAnalog(Canvas): return child_r = tk.Toplevel() child_r.title(self.chose_obj.ObjLabel + "的网络配置信息") - child_r.geometry('530x395+350+200') + child_r.geometry('713x522+350+200') child_r.grab_set() # 设置组件焦点抓取。使焦点在释放之前永远保持在这个组件上,只能在这个组件上操作 ifs_frame = tk.Frame(child_r) ifs_frame.grid(row=1, column=0, columnspan=4) # 装接口的框架 @@ -585,20 +619,20 @@ class NetWorkAnalog(Canvas): index = self.chose_obj.connections.index(conn) num_label = tk.LabelFrame(ifs_frame, text=f'接口{index + 1}') num_label.grid(row=num, column=0, padx=18, ipady=5) - tk.Label(num_label, text='MAC:', font=('黑体', 10)).grid(row=0, column=0) - mac_en = tk.Entry(num_label, font=('黑体', 12), textvariable=tk.StringVar(), state=DISABLED if self.chose_obj.ObjType not in [1, 2, 3] else NORMAL) + tk.Label(num_label, text='MAC:', font=('黑体', 16)).grid(row=0, column=0) + mac_en = tk.Entry(num_label, font=('黑体', 16), textvariable=tk.StringVar(), state=DISABLED if self.chose_obj.ObjType not in [1, 2, 3] else NORMAL) mac_en.insert('0', str(self.chose_obj.interface[index].get("mac", ""))) mac_en.grid(row=0, column=1, padx=10) - tk.Label(num_label, text='IP:', font=('黑体', 10)).grid(row=1, column=0) - ip_en = tk.Entry(num_label, font=('黑体', 12), textvariable=tk.StringVar(), state=DISABLED if self.chose_obj.ObjType not in [1, 2] else NORMAL) + tk.Label(num_label, text='IP:', font=('黑体', 16)).grid(row=1, column=0) + ip_en = tk.Entry(num_label, font=('黑体', 16), textvariable=tk.StringVar(), state=DISABLED if self.chose_obj.ObjType not in [1, 2] else NORMAL) ip_en.insert('0', str(self.chose_obj.interface[index].get("ip", ""))) ip_en.grid(row=1, column=1, padx=10) - tk.Label(num_label, text='端口:', font=('黑体', 10)).grid(row=0, column=2) - port_en = tk.Entry(num_label, font=('黑体', 12), textvariable=tk.StringVar(), state=DISABLED if self.chose_obj.ObjType != 1 else NORMAL) + tk.Label(num_label, text='端口:', font=('黑体', 16)).grid(row=0, column=2) + port_en = tk.Entry(num_label, font=('黑体', 16), textvariable=tk.StringVar(), state=DISABLED if self.chose_obj.ObjType != 1 else NORMAL) port_en.insert('0', str(self.chose_obj.interface[index].get("conn_port", ""))) port_en.grid(row=0, column=3, padx=10) - tk.Label(num_label, text='应用层地址:', font=('黑体', 10)).grid(row=1, column=2) - addr_en = tk.Entry(num_label, font=('黑体', 12), textvariable=tk.StringVar(), state=DISABLED if self.chose_obj.ObjType != 1 else NORMAL) + tk.Label(num_label, text='应用层地址:', font=('黑体', 16)).grid(row=1, column=2) + addr_en = tk.Entry(num_label, font=('黑体', 16), textvariable=tk.StringVar(), state=DISABLED if self.chose_obj.ObjType != 1 else NORMAL) addr_en.insert('0', str(self.chose_obj.interface[index].get("addr", ""))) addr_en.grid(row=1, column=3, padx=10) num += 1 @@ -606,23 +640,23 @@ class NetWorkAnalog(Canvas): num_label = tk.LabelFrame(ifs_frame, text=f'示例') num_label.grid(row=num, column=0, padx=18, ipady=5) - tk.Label(num_label, text='MAC:', font=('黑体', 10)).grid(row=0, column=0) - mac_en = tk.Entry(num_label, font=('黑体', 12), textvariable=tk.StringVar()) + tk.Label(num_label, text='MAC:', font=('黑体', 16)).grid(row=0, column=0) + mac_en = tk.Entry(num_label, font=('黑体', 16), textvariable=tk.StringVar()) mac_en.insert('0', "MAC11") mac_en.config(state=READONLY) mac_en.grid(row=0, column=1, padx=10) - tk.Label(num_label, text='IP:', font=('黑体', 10)).grid(row=1, column=0) - ip_en = tk.Entry(num_label, font=('黑体', 12), textvariable=tk.StringVar()) + tk.Label(num_label, text='IP:', font=('黑体', 16)).grid(row=1, column=0) + ip_en = tk.Entry(num_label, font=('黑体', 16), textvariable=tk.StringVar()) ip_en.insert(0, "10.1.1.10") ip_en.config(state=READONLY) ip_en.grid(row=1, column=1, padx=10) - tk.Label(num_label, text='端口:', font=('黑体', 10)).grid(row=0, column=2) - port_en = tk.Entry(num_label, font=('黑体', 12), textvariable=tk.StringVar()) + tk.Label(num_label, text='端口:', font=('黑体', 16)).grid(row=0, column=2) + port_en = tk.Entry(num_label, font=('黑体', 16), textvariable=tk.StringVar()) port_en.insert('0', "80") port_en.config(state=READONLY) port_en.grid(row=0, column=3, padx=10) - tk.Label(num_label, text='应用层地址:', font=('黑体', 10)).grid(row=1, column=2) - addr_en = tk.Entry(num_label, font=('黑体', 12), textvariable=tk.StringVar()) + tk.Label(num_label, text='应用层地址:', font=('黑体', 16)).grid(row=1, column=2) + addr_en = tk.Entry(num_label, font=('黑体', 16), textvariable=tk.StringVar()) addr_en.insert('0', "10.1.2.10:10810:Name3") addr_en.config(state=READONLY) addr_en.grid(row=1, column=3, padx=10) @@ -630,10 +664,10 @@ class NetWorkAnalog(Canvas): self.chose_obj.config(datas) child_r.destroy() - tk.Button(ifs_frame_YN, text='确定', font=('黑体', 10), height=1, command=commit).grid(row=0, column=0, - padx=20) # ,sticky="w" - tk.Button(ifs_frame_YN, text='取消', font=('黑体', 10), height=1, - command=child_r.destroy).grid(row=0, column=2, padx=20) + tk.Button(ifs_frame_YN, text='确定', font=('黑体', 16), height=1, command=commit).grid(row=0, column=0, + padx=20, pady=20) # ,sticky="w" + tk.Button(ifs_frame_YN, text='取消', font=('黑体', 16), height=1, + command=child_r.destroy).grid(row=0, column=2, padx=20, pady=20) def router_table_config(self): # todo: 路由表配置 @@ -700,10 +734,10 @@ class NetWorkAnalog(Canvas): messagebox.showerror("注意", message="请先选择要显示的对象!") return self.delete("netSet") - self.create_text(740, 120, text="(" + self.chose_obj.ObjLabel + ")", anchor="n", font=('微软雅黑', 10, 'bold'), + self.create_text(self.width * 0.82 + 120, 80, text="(" + self.chose_obj.ObjLabel + ")", anchor="n", font=('微软雅黑', 14, 'bold'), fill="#7030a0", tags="netSet") - self.create_text(675, 145, text=self.chose_obj, - anchor="nw", font=('宋体', 11), tags="netSet") + self.create_text(self.width * 0.82 + 20, 80 + 25, text=self.chose_obj, + anchor="nw", font=('宋体', 14), tags="netSet") def show_router_config(self): # todo: 显示路由表交换表信息 @@ -718,10 +752,10 @@ class NetWorkAnalog(Canvas): messagebox.showerror("注意", message="请选择路由器/交换机对象!") return self.delete("routerSet") - self.create_text(905, 120, text="(" + self.chose_obj.ObjLabel + ")", anchor="n", font=('微软雅黑', 10, 'bold'), + self.create_text(self.width * 0.82 + 120, self.height * 0.85 / 2 + 50, text="(" + self.chose_obj.ObjLabel + ")", anchor="n", font=('微软雅黑', 14, 'bold'), fill="#7030a0", tags="routerSet") - self.create_text(835, 145, text=self.chose_obj.get_table_config(), - anchor="nw", font=('宋体', 11), tags="routerSet") + self.create_text(self.width * 0.82 + 20, self.height * 0.85 / 2 + 50 + 25, text=self.chose_obj.get_table_config(), + anchor="nw", font=('宋体', 14), tags="routerSet") def send_packet(self): """ @@ -739,17 +773,17 @@ class NetWorkAnalog(Canvas): return child2 = tk.Toplevel() child2.title("数据包配置") - child2.geometry('240x100+450+250') + child2.geometry('330x195+200+110') child2.grab_set() # 设置组件焦点抓取。使焦点在释放之前永远保持在这个组件上,只能在这个组件上操作 - tk.Label(child2, text='目的IP:', font=('黑体', 12)).grid(row=0, column=0, columnspan=2, sticky='w') - packet_ip = tk.Entry(child2, font=('黑体', 12), textvariable=tk.StringVar()) - packet_ip.grid(row=0, column=1) - tk.Label(child2, text='目的MAC:', font=('黑体', 12)).grid(row=1, column=0, sticky='w') # ,sticky='w'靠左显示 - packet_mac = tk.Entry(child2, font=('黑体', 12), textvariable=tk.StringVar()) - packet_mac.grid(row=1, column=1) - tk.Label(child2, text='消息:', font=('黑体', 12)).grid(row=2, column=0, sticky='w') # ,sticky='w'靠左显示 - packet_message = tk.Entry(child2, font=('黑体', 12), textvariable=tk.StringVar()) - packet_message.grid(row=2, column=1) + tk.Label(child2, text='目的IP:', font=('黑体', 16)).grid(row=0, column=0, columnspan=2, sticky='w', pady=10) + packet_ip = tk.Entry(child2, font=('黑体', 16), textvariable=tk.StringVar()) + packet_ip.grid(row=0, column=1, pady=10) + tk.Label(child2, text='目的MAC:', font=('黑体', 16)).grid(row=1, column=0, sticky='w', pady=10) # ,sticky='w'靠左显示 + packet_mac = tk.Entry(child2, font=('黑体', 16), textvariable=tk.StringVar()) + packet_mac.grid(row=1, column=1, pady=10) + tk.Label(child2, text='消息:', font=('黑体', 16)).grid(row=2, column=0, sticky='w', pady=10) # ,sticky='w'靠左显示 + packet_message = tk.Entry(child2, font=('黑体', 16), textvariable=tk.StringVar()) + packet_message.grid(row=2, column=1, pady=10) def send(): """ @@ -773,9 +807,8 @@ class NetWorkAnalog(Canvas): packet_message.get()) child2.destroy() - tk.Button(child2, text='确定', font=('黑体', 10), height=1, command=send).grid(row=3, column=0, sticky='e') - tk.Button(child2, text='取消', font=('黑体', 10), height=1, command=child2.destroy).grid(row=3, column=1, - sticky='e') + tk.Button(child2, text='确定', font=('黑体', 16), height=1, command=send).grid(row=3, column=0, sticky='e', pady=10) + tk.Button(child2, text='取消', font=('黑体', 16), height=1, command=child2.destroy).grid(row=3, column=1, sticky='e', pady=10) def send_packet_list(self): """ @@ -788,26 +821,26 @@ class NetWorkAnalog(Canvas): hosts[tag.ObjLabel] = tag.ObjID child2 = tk.Toplevel() child2.title("批量数据包配置") - child2.geometry('400x420+450+200') - tk.Label(child2, text='目的IP:', font=('黑体', 12)).grid(row=0, column=0, columnspan=2, sticky='w', ipady=10) - packet_ip = tk.Entry(child2, font=('黑体', 12), textvariable=tk.StringVar()) + child2.geometry('462x452+200+110') + tk.Label(child2, text='目的IP:', font=('黑体', 16)).grid(row=0, column=0, columnspan=2, sticky='w', ipady=10) + packet_ip = tk.Entry(child2, font=('黑体', 16), textvariable=tk.StringVar()) packet_ip.grid(row=0, column=1, pady=5) - tk.Label(child2, text='目的MAC:', font=('黑体', 12)).grid(row=1, column=0, sticky='w', + tk.Label(child2, text='目的MAC:', font=('黑体', 16)).grid(row=1, column=0, sticky='w', pady=5) # ,sticky='w'靠左显示 - packet_mac = tk.Entry(child2, font=('黑体', 12), textvariable=tk.StringVar()) + packet_mac = tk.Entry(child2, font=('黑体', 16), textvariable=tk.StringVar()) packet_mac.grid(row=1, column=1, pady=5) - tk.Label(child2, text='消息:', font=('黑体', 12)).grid(row=2, column=0, sticky='w', pady=5) # ,sticky='w'靠左显示 - packet_message = tk.Entry(child2, font=('黑体', 12), textvariable=tk.StringVar()) + tk.Label(child2, text='消息:', font=('黑体', 16)).grid(row=2, column=0, sticky='w', pady=5) # ,sticky='w'靠左显示 + packet_message = tk.Entry(child2, font=('黑体', 16), textvariable=tk.StringVar()) packet_message.grid(row=2, column=1, pady=5) host = StringVar() - tk.Label(child2, text='发送主机:', font=('黑体', 12)).grid(row=3, column=0, sticky='w', + tk.Label(child2, text='发送主机:', font=('黑体', 16)).grid(row=3, column=0, sticky='w', pady=5) # ,sticky='w'靠左显示 combobox = ttk.Combobox( master=child2, # 父容器 height=5, # 高度,下拉显示的条目数量 width=12, # 宽度 state='readonly', # readonly(只可选) - font=('', 18), # 字体 + font=('', 16), # 字体 textvariable=host, # 通过StringVar设置可改变的值 values=list(hosts.keys()), # 设置下拉框的选项 ) @@ -856,10 +889,10 @@ class NetWorkAnalog(Canvas): threading.Thread(target=data[0].send, args=(data[1],)).start() child2.destroy() - tk.Button(child2, text="添加数据包", font=('黑体', 12), height=3, command=add).grid(row=0, column=2, rowspan=4) + tk.Button(child2, text="添加数据包", font=('黑体', 14), height=3, command=add).grid(row=0, column=2, rowspan=4, padx=10) button_frame = Frame(child2) - tk.Button(button_frame, text="发送", font=('黑体', 12), height=1, command=send).pack(side=RIGHT, padx=20) - tk.Button(button_frame, text="取消", font=('黑体', 12), height=1, command=child2.destroy).pack(side=RIGHT) + tk.Button(button_frame, text="发送", font=('黑体', 16), height=1, command=send).pack(side=RIGHT, padx=20) + tk.Button(button_frame, text="取消", font=('黑体', 16), height=1, command=child2.destroy).pack(side=RIGHT) button_frame.grid(row=6, column=0, columnspan=3) def clear_canvas(self): @@ -886,20 +919,21 @@ class NetWorkAnalog(Canvas): 创建整体页面布局 :return: """ - self.create_rectangle(150, 40, 650, 490, outline="#7f6000", width=3) # 矩形框,左上角坐标,右下角坐标 - self.create_rectangle(660, 100, 815, 400, outline="#ffff00", width=3, fill="#f4b88e") # 矩形框,左上角坐标,右下角坐标 - self.create_text(735, 105, text="网络配置信息", anchor="n", font=('微软雅黑', 11, 'bold')) - self.create_rectangle(825, 100, 1000, 400, outline="#ffff00", width=3, fill="#f4b88e") # 矩形框,左上角坐标,右下角坐标 - self.create_text(915, 105, text="路由/交换表信息", anchor="n", font=('微软雅黑', 11, 'bold')) + self.create_rectangle(0, 0, self.width * 0.8, self.height * 0.85, outline="#7f6000", width=3) # 矩形框,左上角坐标,右下角坐标 + round_rectangle(self, self.width * 0.82, 30, self.width * 0.98, self.height * 0.85 / 2 - 30, outline="#ffff00", width=3, fill="#f4b88e") # 矩形框,左上角坐标,右下角坐标 + self.create_text(self.width * 0.82 + 120, 30 + 15, text="网络配置信息", anchor="n", font=('微软雅黑', 18, 'bold')) + round_rectangle(self, self.width * 0.82, self.height * 0.85 / 2, self.width * 0.98, self.height * 0.85 - 30, outline="#ffff00", width=2, fill="#f4b88e") # 矩形框,左上角坐标,右下角坐标 + self.create_text(self.width * 0.82 + 120, self.height * 0.85 / 2 + 15, text="路由/交换表信息", anchor="n", font=('微软雅黑', 18, 'bold')) # 显示左边的固定图片 - self.create_text(80 - 55, 120 + 15, text="路由器", anchor="nw", font=('微软雅黑', 14, 'bold')) # 显示文字 - router = self.create_image(80, 120, image=self.router_img, anchor="nw") - self.create_text(80 - 55, 190 + 15, text="交换机", anchor="nw", font=('微软雅黑', 14, 'bold')) # 显示文字 - switch = self.create_image(80, 190, image=self.switch_img, anchor="nw") - self.create_text(80 - 55, 260 + 15, text="集线器", anchor="nw", font=('微软雅黑', 14, 'bold')) # 显示文字 - hub = self.create_image(80, 260, image=self.hub_img, anchor="nw") - self.create_text(80 - 55, 330 + 15, text="主机", anchor="nw", font=('微软雅黑', 14, 'bold')) # 显示文字 - host = self.create_image(80, 330, image=self.host_img, anchor="nw") + img_height, text_height, split_width = self.height - 120, self.height - 60, 120 + self.create_text(split_width, text_height, text="路由器", anchor="nw", font=('微软雅黑', 18, 'bold')) # 显示文字 + router = self.create_image(split_width, img_height, image=self.router_img, anchor="nw") + self.create_text(split_width * 2, text_height, text="交换机", anchor="nw", font=('微软雅黑', 18, 'bold')) # 显示文字 + switch = self.create_image(split_width * 2, img_height, image=self.switch_img, anchor="nw") + self.create_text(split_width * 3, text_height, text="集线器", anchor="nw", font=('微软雅黑', 18, 'bold')) # 显示文字 + hub = self.create_image(split_width * 3, img_height, image=self.hub_img, anchor="nw") + self.create_text(split_width * 4, text_height, text="主机", anchor="nw", font=('微软雅黑', 18, 'bold')) # 显示文字 + host = self.create_image(split_width * 4, img_height, image=self.host_img, anchor="nw") self.bind_event(router, "路由器") self.bind_event(switch, "交换机") self.bind_event(hub, "集线器") @@ -941,14 +975,15 @@ class NetWorkAnalog(Canvas): if __name__ == '__main__': root = Window() root.title('网络拓扑图') - width = 1030 - height = 530 # 窗口大小 screen_width = root.winfo_screenwidth() # winfo方法来获取当前电脑屏幕大小 screen_height = root.winfo_screenheight() - x = int((screen_width - width) / 2) - y = int((screen_height - height) / 2) - 40 - size = '{}x{}+{}+{}'.format(width, height, x, y) - canvas = NetWorkAnalog(root, width=1030, heigh=height, bg="white") + root_attr = { + "width": screen_width * 0.83, + "height": screen_height * 0.85, + } + size = '%dx%d+%d+%d' % (root_attr['width'], root_attr['height'], (screen_width - root_attr['width']) / 2, + (screen_height - root_attr['height']) / 2 - 30) + canvas = NetWorkAnalog(root, width=root_attr['width'], heigh=root_attr['height'], bg="white") canvas.place(x=0, y=0, anchor='nw') root.geometry(size) root.mainloop() diff --git a/NetworkAnalog/NetworkAnalog_X1.py b/NetworkAnalog/NetworkAnalog_X1.py deleted file mode 100644 index 378b8c9..0000000 --- a/NetworkAnalog/NetworkAnalog_X1.py +++ /dev/null @@ -1,757 +0,0 @@ -import math -import sys -import threading -from time import sleep - -from ttkbootstrap import * -from uuid import uuid4 -import ipaddress - -from PIL import ImageTk, Image - -from dbUtil import search, execute_sql - - -class SimBase(): - # todo: 组件父类 - """ - 图标类,所有组件的父类 - """ - def __init__(self, canvas: Canvas, x, y, id, config=None, label=None): - self.ConfigCorrect = 0 if config is None else config # 是否进行配置 - self.ObjType = None # 组件类型 1->主机,2->路由器,3->交换机,4->集线器 - self.ObjLabel = "" - self.ObjID = id - self.ObjX = x - self.ObjY = y - self.canvas = canvas - self.img = None - self.img_tm = None - self.interface = [{}, {}, {}, {}] - self.connections = ["1", "2", "3", "4"] - self.set_default_config() - - def bind_event(self): - self.canvas.tag_bind(self.ObjID, "", self.start_drag) - self.canvas.tag_bind(self.ObjID, "", self.drag) - self.canvas.tag_bind(self.ObjID, "", self.release) - - def set_default_name(self): - data_frame = search(f"select objid from sim_objs where objType={self.ObjType}") - num = data_frame.size + 1 - if isinstance(self, SimHost): - name = "SHO%d" % num - elif isinstance(self, SimRouter): - name = "SRO%d" % num - elif isinstance(self, SimSwitch): - name = "SWI%d" % num - else: - name = "SHUB%d" % num - return name - - def release(self, event): - if self.click_x == event.x and self.click_y == event.y: # 鼠标左键单击 - self.canvas.chose_obj = self - self.canvas.is_chose() - else: - self.update() - - def start_drag(self, event): - self.canvas.tag_raise(self.ObjID) # 将 SimBase 组件置于最上层 - self.start_x = event.x - self.start_y = event.y - self.click_x = event.x - self.click_y = event.y - - def drag(self, event): - """ - 移动图标 - :param event: - :return: - """ - self.canvas.delete("rectangle") - self.canvas.chose_obj = None - dx = event.x - self.start_x - dy = event.y - self.start_y - # 移动范围限制, 超出移动范围则直接返回 - if not (170 <= self.ObjX + dx <= 630 and 60 <= self.ObjY + dy <= 470): - return - self.ObjX += dx - self.ObjY += dy - self.canvas.move(self.ObjID, dx, dy) # 移动 SimBase 组件 - self.canvas.move(self.ObjID + "text", dx, dy) # 移动 SimBase 组件 - self.start_x = event.x - self.start_y = event.y - for conn in self.connections: - if isinstance(conn, AllSimConnect): - conn.update_line() - - def create_img(self): - """ - 创建图片 - :return: - """ - self.canvas.delete("L") - id = self.canvas.create_image(self.ObjX - 30, self.ObjY - 30, - image=self.img if self.ConfigCorrect == 1 else self.img_tm, anchor="nw", - tags=self.ObjID) - self.canvas.dtag("L", "L") - self.canvas.create_text(self.ObjX, self.ObjY - 40, text=self.ObjLabel, font=("", 12, "bold"), - fill="#7030a0", tags=self.ObjID + "text", anchor="nw") - self.canvas.tag_raise(id) - self.bind_event() - - def config(self, interface): - """ - 网络配置方法, - :param interface: 传入配置数据 - :return: - """ - for key, value in interface.items(): - self.interface[key - 1] = {key: value.get() if not value.get() == "" else "NULL" for key, value in value.items()} - self.ConfigCorrect = 1 - self.create_img() - for conn in self.connections: - if isinstance(conn, AllSimConnect): - index = self.connections.index(conn) + 1 - conn.update_node_config(self, index) - self.update() - - def set_default_config(self): - sql = f"select * from conn_config where node_id='{self.ObjID}'" - conn_data = search(sql) - for index, conn in conn_data.iterrows(): - self.interface[int(conn["node_ifs"]) - 1] = {"ip": conn["ip"], - "mac": conn["mac"], - "conn_port": conn["conn_port"], - "addr": conn["addr"]} - - def get_config(self): - sql = f"select * from conn_config where node_id='{self.ObjID}'" - conn_data = search(sql) - return conn_data - - def save(self): - """ - 将对象存储至mysql当中 - :return: - """ - sql = f"insert into sim_objs values ('{self.ObjID}', {self.ObjType}, '{self.ObjLabel}'," \ - f"{self.ObjX}, {self.ObjY}, {self.ConfigCorrect})" - execute_sql(sql) - - def update(self): - """ - 当坐标发生改变时修改数据库数据 - :return: - """ - self.canvas.delete(self.ObjID + "text") - self.canvas.create_text(self.ObjX, self.ObjY - 40, text=self.ObjLabel, font=("", 12, "bold"), - fill="#7030a0", tags=self.ObjID + "text", anchor="nw") - sql = f"update sim_objs set objLabel='{self.ObjLabel}', ObjX={self.ObjX}," \ - f"ObjY={self.ObjY}, ConfigCorrect={self.ConfigCorrect} where ObjID='{self.ObjID}'" - execute_sql(sql) - - def transfer_animate(self, status, packet, error_message=None): - if status: - text = f"目的IP: {str(packet.destination_ip)}\n" \ - f"目的MAC: {packet.destination_mac}\n" \ - f"消息内容: {packet.message}" - self.canvas.create_rectangle(self.ObjX + 30, self.ObjY - 30, self.ObjX + 160, self.ObjY + 20, outline="#92d050", - width=3, fill="#92d050", tags=self.ObjID + "packetData") - self.canvas.create_text(self.ObjX + 35, self.ObjY - 25, text=text, anchor="nw", - font=('', 10), tags=self.ObjID + "packetData") # 显示文字 - self.canvas.update() - sleep(2) - self.canvas.delete(self.ObjID + "packetData") # 删除展示的数据包内容 - else: - text = f"传输失败\n" if error_message is None else error_message - self.canvas.create_rectangle(self.ObjX + 30, self.ObjY - 30, self.ObjX + 160, self.ObjY, outline="red", - width=3, fill="red", tags=self.ObjID + "packetData") - self.canvas.create_text(self.ObjX + 35, self.ObjY - 25, text=text, anchor="nw", - font=('', 10), tags=self.ObjID + "packetData") # 显示文字 - self.canvas.update() - sleep(2) - self.canvas.delete(self.ObjID + "packetData") # 删除展示的数据包内容 - - def __str__(self): - str = "" - config = self.get_config() - for index, data in config.iterrows(): - str += f"【接口{data['node_ifs']}】\n" - str += f"IP: {data['ip']}\n" - str += f"MAC: {data['mac']}\n" - return str - - -class SimPacket(): - # todo: 数据包类 - """ - 数据包类 - """ - def __init__(self, source_ip, source_mac, destination_ip, destination_mac, message): - """ - :param source_mac: 源主机mac地址 - :param source_ip: 源主机ip地址 - :param destination_mac: 目的主机mac地址 - :param destination_ip: 目的主机ip地址 - :param message: 数据 - """ - self.source_mac = source_mac - self.source_ip = source_ip - self.destination_mac = destination_mac - self.destination_ip = destination_ip - self.message = message - self.up_jump = None # 上一跳连接对象 - self.img = ImageTk.PhotoImage( - Image.open(sys.path[0] + "/../datas/images/packet.png").resize((30, 30))) - self.id = str(uuid4()) - - def move(self, cv, target_x, target_y, duration): - start_x, start_y = cv.coords(self.id) - distance_x = target_x - start_x - distance_y = target_y - start_y - steps = duration // 10 # 以10毫秒为间隔进行移动 - step_x = distance_x / steps - step_y = distance_y / steps - self._move_step(cv, start_x, start_y, target_x, target_y, step_x, step_y, steps) - - def _move_step(self, cv, start_x, start_y, target_x, target_y, step_x, step_y, steps): - if steps > 0: - new_x = start_x + step_x - new_y = start_y + step_y - cv.coords(self.id, new_x, new_y) - cv.update() # 更新画布显示 - sleep(0.01) # 添加延迟以控制动画速度 - self._move_step(cv, new_x, new_y, target_x, target_y, step_x, step_y, steps - 1) - else: - cv.coords(self.id, target_x, target_y) - cv.delete(self.id) - - def transfer_packet(self, cv: Canvas, nodex_tag: SimBase, nodey_tag: SimBase): - cv.create_image(nodex_tag.ObjX - 15, nodex_tag.ObjY - 15, image=self.img, anchor="nw", tags=self.id) - self.move(cv, nodey_tag.ObjX - 15, nodey_tag.ObjY - 15, 500) - - -class AllSimConnect(): - # todo: 连接类 - def __init__(self, canvas: Canvas, nodex: SimBase, nodex_ifs, nodey: SimBase, nodey_ifs, config=None): - """ - 连接对象 - :param nodex: 节点 - :param nodex_ifs: 节点接口 - :param nodey: 节点 - :param nodey_ifs: 节点接口 - """ - self.canvas = canvas - self.ConfigCorrect = 0 if config is None else config - self.NobjS = nodex - self.NobjE = nodey - self.IfsS = nodex_ifs - self.IfsE = nodey_ifs - self.width = 1 if self.ConfigCorrect == 0 else 4 # 线的粗细 - self.draw_line() - - def is_connected_to(self, other): - """ - 判断两个连接对象是否已经连接 - :param other: 另一个连接对象 - :return: 如果已连接,则返回True;否则返回False - """ - return ( - self.NobjS.ObjID == other.NobjS.ObjID - and self.NobjE.ObjID == other.NobjE.ObjID - ) - - def update_node_config(self, node, ifs): - """ - 当节点进行网络配置后将节点的接口配置保存 - :return: - """ - try: - nodex_update_sql = f""" - update conn_config set - ip='{node.interface[ifs - 1]["ip"]}', - mac='{node.interface[ifs - 1]["mac"]}', - conn_port='{node.interface[ifs - 1]["conn_port"]}', - addr='{node.interface[ifs - 1]["addr"]}' - where node_id='{node.ObjID}' and node_ifs={ifs} - """ - execute_sql(nodex_update_sql) - self.ConfigCorrect = 1 - self.check_config() - update_sql = f""" - update sim_conn set ConfigCorrect={self.ConfigCorrect} - where conn_id in ( - select distinct conn_id from conn_config where node_id='{node.ObjID}' - ) - """ - execute_sql(update_sql) - except Exception as E: - pass - - def check_config(self): - """ - 检查两边节点的配置是否正确 - :return: - """ - if self.NobjS.interface[self.IfsS - 1]["mac"] != "" and self.NobjS.interface[self.IfsS - 1][ - "ip"] != "" \ - and self.NobjE.interface[self.IfsE - 1]["mac"] != "" and self.NobjE.interface[self.IfsE - 1][ - "ip"] != "": - self.width = 4 - self.draw_line() - else: - self.width = 1 - self.draw_line() - - def transfer(self, source_node, packet: SimPacket): - """ - 传输数据 - :param packet: 数据包 - :return: - """ - if source_node == self.NobjS: - packet.transfer_packet(self.canvas, self.NobjS, self.NobjE) - self.NobjE.receive(packet) - else: - packet.transfer_packet(self.canvas, self.NobjE, self.NobjS) - self.NobjS.receive(packet) - - def draw_line(self): - line = self.canvas.create_line(self.NobjS.ObjX, self.NobjS.ObjY, self.NobjE.ObjX, self.NobjE.ObjY, - width=self.width, fill="#5b9bd5", tags=self.NobjS.ObjID + self.NobjE.ObjID + "line") - self.canvas.tag_lower(line) - self.analyseIFS(self.NobjS.ObjX, self.NobjS.ObjY, self.NobjS.ObjLabel, self.IfsS, - self.NobjE.ObjX, self.NobjE.ObjY, self.NobjE.ObjLabel, self.IfsE) - - def update_line(self): - """ - 当组件移动时重新绘制线 - :return: - """ - self.canvas.delete(self.NobjS.ObjID + self.NobjE.ObjID + "line") - self.canvas.delete(self.NobjS.ObjID + self.NobjE.ObjID) - self.draw_line() - - def update_info(self, obj_id): - """ - 修改数据库中的连接信息 - :return: - """ - update_sql = f""" - update sim_conn set conn_id='{self.NobjS.ObjLabel}-{self.NobjE.ObjLabel}', ConfigCorrect={self.ConfigCorrect} - where conn_id in ( - select distinct conn_id from conn_config where node_id='{obj_id}' - ) - """ - execute_sql(update_sql) - - def analyseIFS(self, SX, SY, SLabel, IfsS, EX, EY, ELabel, IfsE): # NobjS的x,y;NobjE的x,y; #分析接口在哪个位置,再显示 - ''' - :param SX: S对象的x坐标 - :param SY: S对象的y坐标 - :param SLabel: S对象的标签 - :param IfsS: S对象要显示的接口号 - ''' - if (EX - SX) == 0: # 即垂直的时候,NobjS在NobjE对象的正上方和正下方,x坐标无变化,只需要y变化 - R = 18 - x = 0 # x无偏移量 - y = R - else: - k = (EY - SY) / (EX - SX) # ey-sy/sx-ex - reat = math.atan(k) # 根据斜率计算弧度 - # 连线与图标交接点坐标 - R = 18 - x = abs(math.cos(reat) * R) + 6 # python math中三角函数中的数值是弧度,而计算器中的数值是角度 - y = abs(math.sin(reat) * R) + 6 - if SX <= EX and SY <= EY: # NobjS在NobjE的左上角,NobjS的右下角连接NobjE的左上角 - x_S = SX + x - y_S = SY + y # NobjS的连接点坐标 - x_E = EX - x - y_E = EY - y # NobjE的连接点坐标 - # 显示S接口号 - self.canvas.create_text((x_S - 5, y_S - 5), text=IfsS, anchor="nw", font=("幼圆", 12, "bold"), fill="red", - tag=self.NobjS.ObjID + self.NobjE.ObjID) # 显示文字 - # 显示E接口号 - self.canvas.create_text(x_E - 10, y_E - 10, text=IfsE, anchor="nw", font=("幼圆", 12, "bold"), fill="red", - tag=self.NobjS.ObjID + self.NobjE.ObjID) # 显示文字 - elif SX < EX and SY > EY: # NobjS在NobjE的左下角,NobjS的右上角连接NobjE的左下角 - x_S = SX + x - y_S = SY - y # NobjS的连接点坐标 - x_E = EX - x - y_E = EY + y # NobjE的连接点坐标 - # 显示S接口号 - self.canvas.create_text(x_S + 5, y_S - 10, text=IfsS, anchor="nw", font=("幼圆", 12, "bold"), fill="red", - tag=self.NobjS.ObjID + self.NobjE.ObjID) # 显示文字 - # 显示E接口号 - self.canvas.create_text(x_E - 5, y_E, text=IfsE, anchor="nw", font=("幼圆", 12, "bold"), fill="red", - tag=self.NobjS.ObjID + self.NobjE.ObjID) # 显示文字 - elif SX > EX and SY < EY: # NobjS在NobjE的右上角,NobjS的左下角连接NobjE的右上角 - x_S = SX - x - y_S = SY + y # NobjS的连接点坐标 - x_E = EX + x - y_E = EY - y # NobjE的连接点坐标 - # 显示S接口号 - self.canvas.create_text(x_S - 5, y_S, text=IfsS, anchor="nw", font=("幼圆", 12, "bold"), fill="red", - tag=self.NobjS.ObjID + self.NobjE.ObjID) # 显示文字 - # 显示E接口号 - self.canvas.create_text(x_E + 5, y_E, text=IfsE, anchor="nw", font=("幼圆", 12, "bold"), fill="red", - tag=self.NobjS.ObjID + self.NobjE.ObjID) # 显示文字 - elif SX >= EX and SY >= EY: # NobjS在NobjE的右下角,NobjS的左上角连接NobjE的右下角 - x_S = SX - x - y_S = SY - y # NobjS的连接点坐标 - x_E = EX + x - y_E = EY + y # NobjE的连接点坐标 - # 显示S接口号 - self.canvas.create_text(x_S - 5, y_S - 15, text=IfsS, anchor="nw", font=("幼圆", 12, "bold"), fill="red", - tag=self.NobjS.ObjID + self.NobjE.ObjID) # 显示文字 - # 显示E接口号 - self.canvas.create_text(x_E - 5, y_E - 5, text=IfsE, anchor="nw", font=("幼圆", 12, "bold"), fill="red", - tag=self.NobjS.ObjID + self.NobjE.ObjID) # 显示文字 - - def save(self): - """ - 将连接对象保存至数据库 - :return: - """ - conn_id = self.NobjS.ObjLabel + "-" + self.NobjE.ObjLabel - sql = f"insert into sim_conn values ('{conn_id}', {self.ConfigCorrect})" - execute_sql(sql) - execute_sql( - f"insert into conn_config values ('{conn_id}', '{self.NobjS.ObjID}', {self.IfsS}, '', '', '', '')") - execute_sql( - f"insert into conn_config values ('{conn_id}', '{self.NobjE.ObjID}', {self.IfsE}, '', '', '', '')") - - def delete_line(self): - self.canvas.delete(self.NobjS.ObjID + self.NobjE.ObjID) - self.canvas.delete(self.NobjS.ObjID + self.NobjE.ObjID + "line") - - def __eq__(self, other): - """ - 重写equals方法,判断两个连接对象是否相同 - :param other: 连接对象 - :return: Boolean - """ - if isinstance(other, AllSimConnect): - other_ids = other.NobjS.ObjID + other.NobjE.ObjID - return (self.NobjS.ObjID in other_ids - and self.NobjE.ObjID in other_ids) - return False - - -class SimHost(SimBase): - # todo: 主机类 - """ - 主机类 - """ - def __init__(self, canvas: Canvas, x, y, id=None, config=None, label=None): - self.ObjID = str(uuid4()) if id is None else id - super().__init__(canvas, x, y, self.ObjID, config, label) - self.ObjType = 1 - self.ObjLabel = label if label is not None else self.set_default_name() - self.interface = [{}] - self.connections = [None] - self.img = ImageTk.PhotoImage( - Image.open(sys.path[0] + "/../datas/images/主机.png").resize((60, 60))) - self.img_tm = ImageTk.PhotoImage( - Image.open(sys.path[0] + "/../datas/images/主机_tm.png").resize((60, 60))) - self.set_default_config() - self.create_img() - - def create_packet(self, ip, mac, message): - """ - 创建数据包 - :param ip: 目的主机ip - :param mac: 目的主机mac - :param message: 消息 - :return: - """ - packet = SimPacket(self.interface[0]["ip"], self.interface[0]["mac"], ip, mac, message) - print(f"创建数据包成功,数据包由{packet.source_ip} 发往 {packet.destination_ip}") - self.send(packet) - - def send(self, packet): - """ - 发送数据包 - :param packet: - :return: - """ - connection: AllSimConnect = self.connections[0] - print(f"数据包从 {self.ObjLabel} 发出") - packet.up_jump = connection - connection.transfer(self, packet) - - def receive(self, packet: SimPacket): - """ - 接收数据 - :param packet: 数据包 - :return: - """ - print(f"主机{self.ObjLabel}接受到数据{packet.message}") - if packet.destination_ip == self.interface[0]["ip"]: - self.transfer_animate(True, packet) - else: - self.transfer_animate(False, packet) - - def __str__(self): - str = "" - config = self.get_config() - for index, data in config.iterrows(): - str += f"【接口{data['node_ifs']}】\n" - str += f"AppAddr: /Root\n" - str += f"PORT: {data['conn_port']}\n" - str += f"IP: {data['ip']}\n" - str += f"MAC: {data['mac']}\n" - return str - - -class SimRouter(SimBase): - # todo: 路由类 - """ - 路由类 - """ - def __init__(self, canvas: Canvas, x, y, id=None, config=None, label=None, *args): - self.ObjID = str(uuid4()) if id is None else id - super().__init__(canvas, x, y, self.ObjID, config, label) - self.ObjType = 2 - self.ObjLabel = label if label is not None else self.set_default_name() - self.img = ImageTk.PhotoImage( - Image.open(sys.path[0] + "/../datas/images/路由器.png").resize((60, 60))) - self.img_tm = ImageTk.PhotoImage( - Image.open(sys.path[0] + "/../datas/images/路由器_tm.png").resize((60, 60))) - self.create_img() - self.router_table = {} - self.set_default_router_table() - - def set_default_router_table(self): - """ - 将数据库中的路由表信息提取 - :return: - """ - sql = f"select * from router_table where obj_id='{self.ObjID}'" - router_tables = search(sql) - for index, router_table in router_tables.iterrows(): - if router_table["node_ifs"] in self.router_table: - self.router_table[router_table["node_ifs"]].append(router_table["segment"]) - else: - self.router_table[router_table["node_ifs"]] = [router_table["segment"]] - - def check_destination_ip(self, destination_ip, network): - """ - 检查目标ip是否属于网段范围内 - :param destination_ip: 目标ip - :param network: 网段 - :return:10.2.3.0/24 - """ - ip = ipaddress.ip_address(destination_ip) - network = ipaddress.ip_network(network) - if ip in network: - return True - if network == "0.0.0.0/24": # 如果网段为 0.0.0.0/24 则为默认路由 - return True - - def transmit(self, packet: SimPacket): - """ - 转发数据包 - :return: - """ - flag = False - next_hop_ifs = None - for conn in self.connections: - if isinstance(conn, AllSimConnect): - if conn.ConfigCorrect == 0: - continue - if conn == packet.up_jump: - continue - ifs = self.connections.index(conn) + 1 - for network in self.router_table[ifs]: - if self.check_destination_ip(packet.destination_ip, network): - flag = True - next_hop_ifs = ifs - if flag: - conn = self.connections[next_hop_ifs - 1] - packet.up_jump = conn - conn.transfer(self, packet) - else: - for conn in self.connections: - if isinstance(conn, AllSimConnect): - if conn == packet.up_jump: - continue - if conn.NobjS != self: - if conn.NobjE.ObjType == 1: - conn.transfer(self, packet) - break - error_message = "路由寻址失败" - self.transfer_animate(False, packet, error_message) - - def receive(self, packet): - """ - 接收数据 - :param packet: 数据包 - :return: - """ - print(f"{self.ObjLabel}-路由器接受到数据{packet.message}") - self.transmit(packet) - - def add_config(self, router, router_ifs): - sql = f"insert into router_table values ('{self.ObjID}', {router_ifs}, '{router}')" - execute_sql(sql) - - def delete_config(self, ifs, network): - sql = f"delete from router_table where obj_id='{self.ObjID}' and node_ifs={ifs} and segment='{network}'" - execute_sql(sql) - - def get_table_config(self): - """ - 返回对象的路由表配置信息,用于展示 - :return: - """ - str = "" - sql = f"select * from router_table where obj_id='{self.ObjID}'" - router_tables = search(sql) - for index, router in router_tables.iterrows(): - str += f"网段号: {router['segment']}\n" - str += f"接口号: {router['node_ifs']}\n" - return str - - -class SimSwitch(SimBase): - # todo: 交换机类 - """ - 交换机类 - """ - def __init__(self, canvas: Canvas, x, y, id=None, config=None, label=None, *args): - self.ObjID = str(uuid4()) if id is None else id - super().__init__(canvas, x, y, self.ObjID, config, label) - self.ObjType = 3 - self.ObjLabel = label if label is not None else self.set_default_name() - self.img = ImageTk.PhotoImage( - Image.open(sys.path[0] + "/../datas/images/交换机.png").resize((60, 60))) - self.img_tm = ImageTk.PhotoImage( - Image.open(sys.path[0] + "/../datas/images/交换机_tm.png").resize((60, 60))) - self.create_img() - self.mac_table = {} - self.set_default_mac_table() - - def set_default_mac_table(self): - """ - 将数据库中的路由表信息提取 - :return: - """ - sql = f"select * from mac_table where obj_id='{self.ObjID}'" - router_tables = search(sql) - for index, router_table in router_tables.iterrows(): - if router_table["node_ifs"] in self.mac_table: - self.mac_table[router_table["node_ifs"]].append(router_table["mac"]) - else: - self.mac_table[router_table["node_ifs"]] = [router_table["mac"]] - - def add_config(self, router, router_ifs): - sql = f"insert into mac_table values ('{self.ObjID}', {router_ifs}, '{router}')" - execute_sql(sql) - - def delete_config(self, ifs, mac): - sql = f"delete from mac_table where obj_id='{self.ObjID}' and node_ifs={ifs} and mac='{mac}'" - execute_sql(sql) - - def get_table_config(self): - """ - 返回对象的交换表配置信息,用于展示 - :return: - """ - str = "" - sql = f"select * from mac_table where obj_id='{self.ObjID}'" - router_tables = search(sql) - for index, router in router_tables.iterrows(): - str += f"网段号: {router['mac']}\n" - str += f"接口号: {router['node_ifs']}\n" - return str - - def transmit(self, packet: SimPacket): - """ - 转发数据包 - :return: - """ - flag = False - next_hub_ifs = None - for conn in self.connections: - if isinstance(conn, AllSimConnect): - if conn.ConfigCorrect == 0: - continue - ifs = self.connections.index(conn) + 1 - if packet.destination_mac in self.mac_table.get(ifs, []): - flag = True - next_hub_ifs = ifs - if flag: - conn = self.connections[next_hub_ifs - 1] - packet.up_jump = conn - conn.transfer(self, packet) - return - for conn in self.connections: # 将数据包往所有接口进行转发 - if isinstance(conn, AllSimConnect): - if conn == packet.up_jump: - continue - if conn.ConfigCorrect == 0: - continue - new_packet = SimPacket(packet.source_ip, - packet.source_mac, - packet.destination_ip, - packet.destination_mac, - packet.message) - new_packet.up_jump = conn - threading.Thread(target=conn.transfer, args=(self, new_packet)).start() - - def receive(self, packet: SimPacket): - """ - 接收数据 - :param packet: 数据包 - :return: - """ - print(f"交换机{self.ObjLabel}接受到数据{packet.message}") - self.transmit(packet) - - -class SimHub(SimBase): - # todo: 集线器类 - """ - 集线器类 - """ - def __init__(self, canvas: Canvas, x, y, id=None, config=None, label=None, *args): - self.ObjID = str(uuid4()) if id is None else id - super().__init__(canvas, x, y, self.ObjID, config, label) - self.ObjType = 4 - self.ObjLabel = label if label is not None else self.set_default_name() - self.img = ImageTk.PhotoImage( - Image.open(sys.path[0] + "/../datas/images/集线器.png").resize((60, 60))) - self.img_tm = ImageTk.PhotoImage( - Image.open(sys.path[0] + "/../datas/images/集线器_tm.png").resize((60, 60))) - self.create_img() - - def transmit(self, packet: SimPacket): - """ - 集线器转发数据包 - :return: - """ - for conn in self.connections: # 将数据包往所有接口进行转发 - if isinstance(conn, AllSimConnect): - if conn == packet.up_jump: - continue - if conn.ConfigCorrect == 0: - continue - new_packet = SimPacket(packet.source_ip, - packet.source_mac, - packet.destination_ip, - packet.destination_mac, - packet.message) - new_packet.up_jump = conn - threading.Thread(target=conn.transfer, args=(self, new_packet)).start() - - def receive(self, packet: SimPacket): - """ - 接收数据 - :param packet: 数据包 - :return: - """ - print(f"集线器-{self.ObjLabel}接受到数据,将进行转发!") - self.transmit(packet) diff --git a/NetworkAnalog/SimObjs.py b/NetworkAnalog/SimObjs.py index 378b8c9..be8ff8f 100644 --- a/NetworkAnalog/SimObjs.py +++ b/NetworkAnalog/SimObjs.py @@ -17,7 +17,7 @@ class SimBase(): """ 图标类,所有组件的父类 """ - def __init__(self, canvas: Canvas, x, y, id, config=None, label=None): + def __init__(self, canvas, x, y, id, config=None, label=None): self.ConfigCorrect = 0 if config is None else config # 是否进行配置 self.ObjType = None # 组件类型 1->主机,2->路由器,3->交换机,4->集线器 self.ObjLabel = "" @@ -73,8 +73,10 @@ class SimBase(): self.canvas.chose_obj = None dx = event.x - self.start_x dy = event.y - self.start_y + width = int(self.canvas.cget("width")) + height = int(self.canvas.cget("height")) # 移动范围限制, 超出移动范围则直接返回 - if not (170 <= self.ObjX + dx <= 630 and 60 <= self.ObjY + dy <= 470): + if not (0 + 20 <= self.ObjX + dx <= width * 0.8 - 20 and 20 <= self.ObjY + dy <= height * 0.85 - 20): return self.ObjX += dx self.ObjY += dy @@ -96,7 +98,7 @@ class SimBase(): image=self.img if self.ConfigCorrect == 1 else self.img_tm, anchor="nw", tags=self.ObjID) self.canvas.dtag("L", "L") - self.canvas.create_text(self.ObjX, self.ObjY - 40, text=self.ObjLabel, font=("", 12, "bold"), + self.canvas.create_text(self.ObjX, self.ObjY - 40, text=self.ObjLabel, font=("", 16, "bold"), fill="#7030a0", tags=self.ObjID + "text", anchor="nw") self.canvas.tag_raise(id) self.bind_event() @@ -146,7 +148,7 @@ class SimBase(): :return: """ self.canvas.delete(self.ObjID + "text") - self.canvas.create_text(self.ObjX, self.ObjY - 40, text=self.ObjLabel, font=("", 12, "bold"), + self.canvas.create_text(self.ObjX, self.ObjY - 40, text=self.ObjLabel, font=("", 16, "bold"), fill="#7030a0", tags=self.ObjID + "text", anchor="nw") sql = f"update sim_objs set objLabel='{self.ObjLabel}', ObjX={self.ObjX}," \ f"ObjY={self.ObjY}, ConfigCorrect={self.ConfigCorrect} where ObjID='{self.ObjID}'" @@ -235,7 +237,7 @@ class SimPacket(): class AllSimConnect(): # todo: 连接类 - def __init__(self, canvas: Canvas, nodex: SimBase, nodex_ifs, nodey: SimBase, nodey_ifs, config=None): + def __init__(self, canvas, nodex: SimBase, nodex_ifs, nodey: SimBase, nodey_ifs, config=None): """ 连接对象 :param nodex: 节点 @@ -250,7 +252,6 @@ class AllSimConnect(): self.IfsS = nodex_ifs self.IfsE = nodey_ifs self.width = 1 if self.ConfigCorrect == 0 else 4 # 线的粗细 - self.draw_line() def is_connected_to(self, other): """ @@ -371,10 +372,10 @@ class AllSimConnect(): x_E = EX - x y_E = EY - y # NobjE的连接点坐标 # 显示S接口号 - self.canvas.create_text((x_S - 5, y_S - 5), text=IfsS, anchor="nw", font=("幼圆", 12, "bold"), fill="red", + self.canvas.create_text((x_S - 5, y_S - 5), text=IfsS, anchor="nw", font=("幼圆", 16, "bold"), fill="red", tag=self.NobjS.ObjID + self.NobjE.ObjID) # 显示文字 # 显示E接口号 - self.canvas.create_text(x_E - 10, y_E - 10, text=IfsE, anchor="nw", font=("幼圆", 12, "bold"), fill="red", + self.canvas.create_text(x_E - 10, y_E - 10, text=IfsE, anchor="nw", font=("幼圆", 16, "bold"), fill="red", tag=self.NobjS.ObjID + self.NobjE.ObjID) # 显示文字 elif SX < EX and SY > EY: # NobjS在NobjE的左下角,NobjS的右上角连接NobjE的左下角 x_S = SX + x @@ -382,10 +383,10 @@ class AllSimConnect(): x_E = EX - x y_E = EY + y # NobjE的连接点坐标 # 显示S接口号 - self.canvas.create_text(x_S + 5, y_S - 10, text=IfsS, anchor="nw", font=("幼圆", 12, "bold"), fill="red", + self.canvas.create_text(x_S + 5, y_S - 10, text=IfsS, anchor="nw", font=("幼圆", 16, "bold"), fill="red", tag=self.NobjS.ObjID + self.NobjE.ObjID) # 显示文字 # 显示E接口号 - self.canvas.create_text(x_E - 5, y_E, text=IfsE, anchor="nw", font=("幼圆", 12, "bold"), fill="red", + self.canvas.create_text(x_E - 5, y_E, text=IfsE, anchor="nw", font=("幼圆", 16, "bold"), fill="red", tag=self.NobjS.ObjID + self.NobjE.ObjID) # 显示文字 elif SX > EX and SY < EY: # NobjS在NobjE的右上角,NobjS的左下角连接NobjE的右上角 x_S = SX - x @@ -393,10 +394,10 @@ class AllSimConnect(): x_E = EX + x y_E = EY - y # NobjE的连接点坐标 # 显示S接口号 - self.canvas.create_text(x_S - 5, y_S, text=IfsS, anchor="nw", font=("幼圆", 12, "bold"), fill="red", + self.canvas.create_text(x_S - 5, y_S, text=IfsS, anchor="nw", font=("幼圆", 16, "bold"), fill="red", tag=self.NobjS.ObjID + self.NobjE.ObjID) # 显示文字 # 显示E接口号 - self.canvas.create_text(x_E + 5, y_E, text=IfsE, anchor="nw", font=("幼圆", 12, "bold"), fill="red", + self.canvas.create_text(x_E + 5, y_E, text=IfsE, anchor="nw", font=("幼圆", 16, "bold"), fill="red", tag=self.NobjS.ObjID + self.NobjE.ObjID) # 显示文字 elif SX >= EX and SY >= EY: # NobjS在NobjE的右下角,NobjS的左上角连接NobjE的右下角 x_S = SX - x @@ -404,10 +405,10 @@ class AllSimConnect(): x_E = EX + x y_E = EY + y # NobjE的连接点坐标 # 显示S接口号 - self.canvas.create_text(x_S - 5, y_S - 15, text=IfsS, anchor="nw", font=("幼圆", 12, "bold"), fill="red", + self.canvas.create_text(x_S - 5, y_S - 15, text=IfsS, anchor="nw", font=("幼圆", 16, "bold"), fill="red", tag=self.NobjS.ObjID + self.NobjE.ObjID) # 显示文字 # 显示E接口号 - self.canvas.create_text(x_E - 5, y_E - 5, text=IfsE, anchor="nw", font=("幼圆", 12, "bold"), fill="red", + self.canvas.create_text(x_E - 5, y_E - 5, text=IfsE, anchor="nw", font=("幼圆", 16, "bold"), fill="red", tag=self.NobjS.ObjID + self.NobjE.ObjID) # 显示文字 def save(self): @@ -445,7 +446,7 @@ class SimHost(SimBase): """ 主机类 """ - def __init__(self, canvas: Canvas, x, y, id=None, config=None, label=None): + def __init__(self, canvas, x, y, id=None, config=None, label=None): self.ObjID = str(uuid4()) if id is None else id super().__init__(canvas, x, y, self.ObjID, config, label) self.ObjType = 1 @@ -457,7 +458,6 @@ class SimHost(SimBase): self.img_tm = ImageTk.PhotoImage( Image.open(sys.path[0] + "/../datas/images/主机_tm.png").resize((60, 60))) self.set_default_config() - self.create_img() def create_packet(self, ip, mac, message): """ @@ -511,7 +511,7 @@ class SimRouter(SimBase): """ 路由类 """ - def __init__(self, canvas: Canvas, x, y, id=None, config=None, label=None, *args): + def __init__(self, canvas, x, y, id=None, config=None, label=None, *args): self.ObjID = str(uuid4()) if id is None else id super().__init__(canvas, x, y, self.ObjID, config, label) self.ObjType = 2 @@ -520,7 +520,6 @@ class SimRouter(SimBase): Image.open(sys.path[0] + "/../datas/images/路由器.png").resize((60, 60))) self.img_tm = ImageTk.PhotoImage( Image.open(sys.path[0] + "/../datas/images/路由器_tm.png").resize((60, 60))) - self.create_img() self.router_table = {} self.set_default_router_table() @@ -621,7 +620,7 @@ class SimSwitch(SimBase): """ 交换机类 """ - def __init__(self, canvas: Canvas, x, y, id=None, config=None, label=None, *args): + def __init__(self, canvas, x, y, id=None, config=None, label=None, *args): self.ObjID = str(uuid4()) if id is None else id super().__init__(canvas, x, y, self.ObjID, config, label) self.ObjType = 3 @@ -630,7 +629,6 @@ class SimSwitch(SimBase): Image.open(sys.path[0] + "/../datas/images/交换机.png").resize((60, 60))) self.img_tm = ImageTk.PhotoImage( Image.open(sys.path[0] + "/../datas/images/交换机_tm.png").resize((60, 60))) - self.create_img() self.mac_table = {} self.set_default_mac_table() @@ -717,7 +715,7 @@ class SimHub(SimBase): """ 集线器类 """ - def __init__(self, canvas: Canvas, x, y, id=None, config=None, label=None, *args): + def __init__(self, canvas, x, y, id=None, config=None, label=None, *args): self.ObjID = str(uuid4()) if id is None else id super().__init__(canvas, x, y, self.ObjID, config, label) self.ObjType = 4 @@ -726,7 +724,6 @@ class SimHub(SimBase): Image.open(sys.path[0] + "/../datas/images/集线器.png").resize((60, 60))) self.img_tm = ImageTk.PhotoImage( Image.open(sys.path[0] + "/../datas/images/集线器_tm.png").resize((60, 60))) - self.create_img() def transmit(self, packet: SimPacket): """ @@ -755,3 +752,4 @@ class SimHub(SimBase): """ print(f"集线器-{self.ObjLabel}接受到数据,将进行转发!") self.transmit(packet) + diff --git a/NetworkAnalog/__pycache__/NetworkAnalog.cpython-37.pyc b/NetworkAnalog/__pycache__/NetworkAnalog.cpython-37.pyc index a5cad17e2a594b870b0cbcfe9f7579102bb97587..1f15bb13bef4bd99fc810a6cff9605ab70e131e4 100644 GIT binary patch literal 33600 zcmd6QeS93{edo^X+w5w!TFJ63zhH#1v1J=uvW+3GAvOjADJT?NNcP-rWX)P$S=yCn zcd@Ll5`qnu1I8v0!fTL#CL|#?B!m~+z%_TDYn$t}z1Lo+z5Ao}%2wOY(M#KFn(JNe z`~5w$v$HG77@B-~viF&1p0}BK=J)>lJIc?_|Ay&_5JR1~ujbgQ1~tGAwy*&`2663l2tx zqRHq`L$X22LxZuQ#$@AAJQ*KqN;VB8l8K?_Wb;r`N~Cl+SE5qFz4;lPiWQy=_?NTlFQP|CayGFCpFE7-113Zas`f8;kYur!kpjg zORqHBT7Bk%=k(+%vpv1aT)5DObP>`O>a5*oE?$gws#KksOWJ*V0&TuzXZrH=GH);Q zGIQy%Kyr0@^}rgFGj_Szam+7QUz_f9?&Io7AD&jb+t;~lns2A4Q?s?zNc@GuJ{+P$ z>3qJoKfOCQ(y80w+lG+exz7$trZa3u?#>L|+1s}-U9iK*-kQr7?8fbbgUIj5WwYtN zf;71+S1hD0$=-b+Q|Q}khuFHfTYBBtn@@KJ>`*@4YoV$kJ(BK2`&54apdB^SgK6Y* zy9exsf>q4+_2SIjJ-rZj+ZWcUL)mXUX8}OCFd$dx>)J#32eNBIq6YuXy zhVnzXTw(9dPAwVlv(kLR;oRWC{#>@xNa}s}+4@Mgtslg1WCM;H?2y%KW{P<`wC_N# z)sN$FE|V?fzvZ(Q;o<*oGm@LO>?*(fM)~pI+|`pV9LQPwwr6_>bNv@PTQ_{rZnAR4 zteLX#=6bXJgXt#D=xQWB&2at&E}AoRYYAHS0kh?gxE{c72Y&eI;20`F?HLSkMd>@H!4@0iWNuPqXeaL;JM+ z=By3~4&UwDzxiGt>Q%qMLf;{6EMx{tA*c2Irh(QERa%>oyM3jA^}~^Mr2wxq>lNOR zD?C-{v;V1keeB1pnSQ*wXfcHcigw_y{Kz}g4<5@5m!EsB{QUh>6KBh3k54^ys(j)b z7ruG;!Xt;I(U##}t9Pg)Go0!*O$(T{rQ^0cRS#8fr3*zXyQSmCTy8Mko0Y=R&A+~C zm-!prYd4N{?ppUD+4ZV^!&v9?(X}6;-}0Vmz9frpZTe?OZkg^tx)W%rTWLEu)GJ8a z59k=q+kOCBXE+(HK1g!jaBl%9o=pM0_onj2-Pv>@+2$<-38<7e%WySKTdPIYhIUXc zm~2qvrG|R*`vk&OvKV)0&%3X8Fk=#Bs&}&z75U{zeBpo=&|_Lm3;5f#MVg@}v^EW8 zIFIujM{5Pz^|5i~&+laNTaiio$dZ!!5#Jt-czf7L`U#d`3IQ`{hHwm;Vbj1dBuvAX z3?Bi+Ck+5dgWaM?ABf?eO#i)^teHE|R~es2&N`3z9wY_dAwdcf=aO$iH#H{*3N;XT z7L4$DT}pr|sbD|aq(T{{pe+*yNjZ)YA?ZG$ETc`EHeNHbY2(H<8?WhFv!Sc2NPNEV z)~WMPynbQw6+rCycTaXk?O?t*4Ek)OQkiU~kV@G`e>yjmE?5WcU?Ec&Oxwn1b`RX% zyE_ev3>d(h7K+>qKuBAAdi&C;bhcn+(s?`PR2O=8quT<9oK@Rpvcttf-k#@Fcw2Y+ z?La;~xCi8{*W!3;!HjeK~+QLRM_$v;CIT|9Hz*Hs>_ zhBQUcS?2$*km38~yX_B*X(LY+AW$6AKo}kYvC;GDz<7g8o1*A=rx6Qb3AUF{!1^6b>K8*GWGgQ*|XaZvtA|mFzV}Y?? zDJZ>LrT6?}0nN9+4MfgBk2Z6`F?}pl3Jv%Hg@)N)h~TOV2-k=XG?=aXbjvmu zWp#70y6cIU3mr>(ilog`kN#%)_)FcM{02{cqbI+~x(w|*^&+|M^qI%bz5DnyCm(`A zppYIZ*nvGcyfh&pQ8o!8v9$x8LECTT4%nf-++cAiYX^pV%@Iitg69`9efthhx2kc1 z2Qp@1uSNbf&2i*XQ*WIqe|f@vYb@s*n}=VX*BSRs0PZKrOEtnxx%2NbW{I+*M09n9Z=j2foXZ(YMH z@Cod2Uv6lqH)~GoIO_~pTUhf;Oh{O)>ygLsr5IQ^(Z!lNauPu)`_p-e3ps*uJB?yR*>_THxeqTQ5Lv zQz#thw=$+3yqS|@U5$b4Q19??I&0dY8;gZPE^9}n;Vs}+X+;X@h^d2 zOj`;<9Ybz(^`*ZL7e)wq1a86XC4iVCz9ZTm&{NXyF%5Z5Xt<%oka5u2aV@2f1Iq=U zfZ@QYpcxT-3QPnieS+^H2h$;P0Q*B!34r-c6srpXU^4onEI~!!SO6m$C@}_r&1S1P z{}_5X=fGaidDLt-7n+M$k5L+Al*Q6AW-f84NR;BdY9lqE9|Mi%<;Orna2_9M!qxB_ zFeVaaM@gIHSiXVgLJL%xWykcRz>LT1QEhyk_M5%++vadBfgPc zC5ngph_>b@}u!YBu{QBUD?N~@5X>ir1_pPb%c zj~#-{Q%vU_Y3*k}`9~g1J;MERnn_su|wr01AZ``@n{y+Gfo7=$4N<+s?gVIO{rI zgoNI@mC3b8yd=@OjioLT9bK@am@njp*6nnFZQZSD=ttRphgX$PK1^Ok(FadK(mdK! z*JFAqEA?aqSKpf^5J@{gT;#Tjh+_xkiELx98a)qrC73T9gqQk{VbPHYfgKo3?*ZWNhU!Ning@n}hh_!`ZR5VQ1;*Gri1JVlauqP( z7W4)!mwVgAyVdOY!F->U8ys}_%xHA0BHveUyx|s9hYL`sb9?s4t(3Qgmqga$~!D zEjxHn@ZS$47a+9Ou|Z%rII4&uZt^%2a2%jX*Zr`XG zC^QVjND3g>H&zICNwd^<_xiH1gz=wqcOlcyyCV@2LKN;%F#i2lrVbx3F2_SFN$vbIhZTUIdw+83OQ)1B zeD3XInW#tb zspPvibhap2#@`Qi63IZy6dhaCRELP!(M&#*&BL_OmsY??0a7$Ti&P!76Zo69tej@GnvUya&EOk5UVH8086SyN7*in&zL^4tmr%O7m8&Q7< zzY0idI;dt$K(dueDgMQ^HYlsDP-Qe2#D=t!!CVH8`DQtm2|ML4hTDJA5<_|(w_A_50(T0DnLq+UwTH(Hpj?dKv`&4 zZKS3Fsz(f}z7qW#2jY(CAA;z=s7CZ(4E3QR`dr7}YwLXsoQb;fJTU5Tvud8pEX!0t4brSq6Z*~?#(*O4oyhZY;*k77C@<2y? zxAw5$N+uM-6_)H}jtmJza=@+H$J`)G>+KpM9(@lrGlcSwI)NWsD91Y<=KbwwV(}(* ztH6Ab39D)u25leG*!B~94_QS5S7`kXYVsXOL}(3DXr*<8|LVonOM9&~;wm-R9r)!>BO$vl zx#DU8_T>S8ApjBe&H}Vih^TiKNX%x6s0KvT$UxNLDh(bH^-WhqjX^{m!H6+eL~X1S zQJor(h*~o)#FTiQh}u-SQfKI6ilNpqJa<(D?Mx_M^e9VDFyYI!qD)pWA;Yj9W8zAN z)>nA?I1?&0A`@CqFemTZBMe$!Whq~^O5`0=3j7g%`6Wom#paR#N3Wda{npEVpdZg- z80$GE&od#H^vEw?=c#)O6{ae8@&c+R@XLpgh`@5W#WI4b`{#FZ`B#y-6k(;_E#mmn z4e~^|OCrjDo{TELd9s0=5als*zS)LjBl#YVaT>>PY=RMc362Sx!foSZaf?w9;;b6ZBr!30i{(eB=iF@^MYcNf47^A1uK;3$+U(b4o8j zOfJA?UkDEv%9gVL_U4I*>7RrYg%;5nEdq5d8fLVB`c^YWY(|SvU5my-9BN>b8J^S* z@u~^u+;GmDfn$v*ZxNr+Jn;{~cYu=lCNMnJCg}o6+a+Bn=^|;fSkfi9!euUQMSBE6 zinNZbU$<^Obn@Q(`V6B0^6Qlwsr>XS>xOA+ncj*<){97HZ>}W6spn3!`Rq-mpMB{3 z>Gz!xi-gP>7d`j(8D|J<8|v+lvexYETv`=0?GPM=Fg{u&amnbNdvk@H1T0v5*LENW zQ@8EUAB4FE4obu#z!jSrO}n10%uv4_%}bm@iuv%~;%>&&0b zOT#AWsVQZ8-!s_TZ_ks0s+&y;8y(pQUKQMFMXr7s_iD8xsgMAn2|@=*G5#BV`7R_r z@R<-a(WNy%avww7Y|7bIO-I{mkZh*N4g8Ww4dFUB;RLV-pPSM{L^-APT}PY-%*_J< zH-|DYbx7F)T2VD9q9K?ffopR19}+*!7bOdgsrK;`ezUP!Jl7}b_(^%GcW=4$n%z!Cg-%pXQ_Bv5fm zABBo%`k#Yq17*aa6i|5`xJ$35%xwpSv{_ctMXAYk` z^XB<8kCjh8bMExR$h*yE%zhsRasPDW#Jo<{Xu@2cQYl>U$U}KW3NXOc^50~#hiyJLhTH>^3h#7as zaeE+rzmAJp-(&K9CT}p|o2_}(#3h*B;YtUPIx0+1A5v^ zyoPx30o+i{9F~uNzkKA30)g_`cgjaj0|%!b{+9AMPkAtrlk3FB$TV!J8ON!?Ow^Uv z<1dF1eubAo!T%PIh9N4Ji%6tIL?W3@_9Pfkwo zmlt*et}*T(07ckmShwKC`BMHIY4;|4dZ^c;+?P^e!)XQx`|bg#mExY z4m1KC6AT0?`7zF`jC|YwD$oj%q4`8fMi^-17-p>92L*^FAt?z~N+#mGTa-7!QV)@n zj=@7xGSN&C6i>4H3+Pw5f8?BU)y}#1M$^4_pex^d4Vrn8v;6GWFTAE=);gTOO6*$2 z8+Hk}*t2ug*m_5fF5VkzP|v0A25y3=cSr?qR46Io5X3L0ytzX%LLIiP1q>)``}C#3 z)J!CviFUZ%oAZ6W!|5CB0Hw8CibR$vad5yFA&3Y^#)U)gDsFP&$hXUHy$(zeRdPGp zN|W+i-#dTixYOkQBj-*(1lNSH3*jEGp`9g0g2$N?79t!)q>3QKhud!QXq&tRJ3;|6 zMIprcZJr16y>JnpV*fjlBtyk4xd=jSBJP7K{QO8q#XcgP{LEB16x(^%I02Kp-FjP-4W-4=+007$)3V687n{u0zX!>2LMz^I4zG zB7!Qr1hu+Tdk<>C4_TZ4Lxy?6LZPGO9-yg~Z3khf^bS~GsN@j_fh(ZZBSdDjqNiVt z60lybHVxh-t*r?jMOXBXD6m6 zj?`5Ayx8i$!xe?M_CND9|E53Lzfu1F@{`lV;?eMx*X;hpZu1&t%k(RI-8S|*i=?MB zY6;v6jB6vxG%VFGp&GnOg*^*|3zodcuJ0p(+S1GXe`d0a1v9Lj1mRyMxGqP=7bbYd zwJiWdI?kf*ajB*wPlaZ5!=-_wmjJoIhznJP2g-iqT1AZk|Itza8d7KRk@|Zq&fG2F zoP2Vb1Kd9!2t~ieqB_+3k0?v?S;+1fP5}hEM46H3>@|S8+>`WnhPYFz6?YtyJh?w^ z@ZaL#p$9kNbRTX?QQUQxHhC3ch6+<7$2-Y{o;%y9h`rYDvU?15u`k zgT}s4EZW-A(ivM64xk1$nYLVSCZ9qA@H)f%HYVid)1;5nt9kmLm?IHJ zBXQ7eV<{V1IV858J-{G2F&HDpI18(n<;Vk#*03gYDK^87i^>&8~9ib!_(q+^=H0byC1=s99mT zqYYbc?7n(7)$5Y)XKIf7nW)?kLhY?TLL$91%)wA^;Z)^k6l`(ZDf;V+6-Aj!~G)Xal&9k$a_3DO`${j8f!K zXsjXoY1Bnf_Z7HF>+9B`E{eL71(?g~`n4ePudb}r;IKOqA780qAhOlBe;I774P!Ap zOAOD_nDLFpM+&7z=Xv(Z^E8#3%-BL7Qr!I>y%gUU#;VgzIB%5FI7<-;7_V)I(SKZD zhUmd)Z9kIY{Xf3fhgpo1^}?FLSxk&I7eMsnP7%+7&}RH_HI+Az<1;jy$hkGlvnIh5 zv)58;Q8A6BmiloJv*?aQvc1*kwsA+4HqMw%8=5?$r4~^L>^>d0q?sAuP`ow$tUYBF}?4vD+o0U`*yoQi@HhQ6KcNNgqE zd34YjF27Dx_KdlOqW~GHfgZDfF&r^IhB!;&g!(m zw7M*n7&VAI8i#CTS2^RDNvx^CGmAS@8Ats4(pc*zarM@J;LTA^s*+;6!AcM2dQIu_ zBFWXQsP~0v3*mZL%2D2bjN+7^&YFbIHUYG1)0XS2aa^h`N6B*hi@}02y0Dhqd9LNs zJ6b>~#0$OqJ!FV8VgX&k#c68cnroZXD$7{l8h|@n=+|cYOMnNAKZe0Vu*5wxBXl++ zDs-9DhJfd7SR8}+3CJWz<1{D&|W zjsZCeMT2z|WxT!_cdo4#qtW))RJ;a{gIQs(R z3z2%*8KO;2)@zO4qe7(W5D=>fthBTDDrWYv0TH(<%^eykOttfB-fK}O)WF3>E&wm}k0b%tLE&mW;A*nu zk~|-_`mkk@?;{Tm?{HA8K4&dB=Z)MDfO&AtP;EQH_Wm2OOfyV=&V^SKjO&CFf;)(+ z1-vM0FdNtsIl+QLBdkVPv0e%(Yk#5XkngBoNDMS%v1d$0k&f$y7I+utO`@flI2Inm zLSr#M_$7kI7&8!{(u(%7+^l3Ef;0#zCNeNzB1jjKI+VgVYv+Qq3a#i|RNTP%>~Q`Z z&=+?pqA}d^iz6NpV^+jdW^fEN3kHt9>W=PUhkMSOahcU6us^q>%Ycb4;Dc_(B{M{~ zM?FSHSGrZ!G@jS`3nqVwM9ltpK=)zQ%IeXkja^+MSa!W;lUjZ~E&MTy9<~9+-ppkO z4}z*ZiGpcrU5Md&83)mJl*09Ic)A&h9pe(q`+BVmeSQ)^+J|EKqe2$KPYgWEi4Xb& z;-f_oUs}fK%;Wt_g%a%?!Kc1~5ejrXciBgJdn3qRqe*;DunyBfx;DU`m4r zQ&0;`*(I2Qyn`v8c7iFpAY0xEub14h?DVj0jIq;UY?A@0b|IY1$U%}d8{0XCv%Gz- z7P#Wsdx=$1N_zzjcosLC+~-O>FAM9$Hrw1I6R(`a)kQ3TV#PbApS$17KHPU(itLchENIB+q2`{KM`7 zYih+cheMHhR(*<a~R3HMTX6d~3zj!0tkw8hN@FQ5RF#i^uPX9H_Xl?|75Mv1K zAf0V>3|*&Z=ZDU7nXm<`9|6rYB21|1TkdX7%udpgOJcunH;L;Io{vT0f{U0X(mk9+_5fqaC zzWciQ*3>YT2EvpK?=ECh2l1Zx3Sc^*wchh$_)aW>z%O&y@7es;pbws zF8HyU13Sg2c5wK??0C$N7y|yIv9XccIXJE2Zfz54W8m$6scXOp2CfPT!E8R}KZ-@_ z7@-OOTFAX!?$5YcENjPTkRlL0YnV-t`N93sx&b3{{m4S+dTg*GSAO&b)o4pCN^L?Nu%(Jkc%hD7{Rviiw_v!9RW~4!5%F}+h!y`OVNY$=z(yFA zOxXGdm3J#$7EC}Qi4zvg1q*Zvaz%JavEgMi*)W1n5bu}@FePh+^0kWj0V-ch@H)dK zO_Z-2N{BUX!i*gQ`1j2I7{zHx)q7~DeL|>5=0U9NVN zfvdE+TH&S6vyx$3ORMGQUf3Z-RJZRaAAfS{&GF8rddf^HYypwIYu-qJEg-(nx`VkM zCZAzK+9Sl(;zVJNgtRuXlv4pp*~mO&)K!(tO;hzr)FX0&mZ5z`(V}I7bdoDZmVjo) z^+aPkN)~COmtAzSTnZV(V0@+I3Hi4H;sC@sb*%#AIIJ)Qm4t>u2jDgb>?;5YUB3{V z2su<#QAfiJyHpgxhYl50@>_5MG*In40YHxc)IrgNI#y~B%z+vrfm3551}oz`s-4hB zw(xEkfptRvwY9KI#1RJB1Z&0{svOZ&0SS)`4m5*aHbRPp6@+1t*t|z=t`jS9dmZfa z2l`8T9UN$ZG^*C@aTOlIC)GwCsF{bFd3d_kNtq+)S#4s8-CStP>V*Z^uqOZ;b9e&U zS7D*t!J-l@wiAoFzF`uQ_N3C!IBJAz9n^Em6)&Ss2mYbvK(-Q@^@>a7im!OCXq1du zu6ViUiiV6@e?{zG=v)zTXcu|L$UgrG-CE?iA~eofu6Tv#iV>N)Wp&SZrF+H5C0y|l z&lRJk=qy)U?zv)Ap3%J`-fHApZs`)HCBGCkS3HO9TGac82Jr^8cLfty{Zk>qT!~ej z&RaKEId6DL;WD$6>o=i|f2_AHqpCL>G*`>0#z1w{kNHMrROjuPYh=`jXVj1R)?`%Y z4Vi0Y)Tn3Fx;Nv>G*_GJAk#G9JuF4I)q222q|{(`L8ehy*KKy28yuWui;ZjnndWl0 zO$;rZHX_q(Dy|0&bCoCTEO7qJb4pQn3qP_nqx#Qc+YoiBcsZ`9m4(wMl}j zO6;&*LH6lxn{$L%I<9zQHMYV7=XPL+?pw8c^JhI3XyE>cWjnDXf-5dBS>t>rZAcVjR~u`2WH;;6yd6#Mb2K8`NOZ63?n>&{b&bxi?48xMrMv68 z?yfC8P$)OL5*|7V6BG%L%V&huK_qt=7PeJzBrt4%SQ}`M=IR#3pqoZqUd39E8$wPkzEXP&3Ms@vAnMP@qUV(=2Fhii} zs1s;x|6n?sjLJR^>ai{^;e3&Uii9JIy(=6l4e>74iV#$lf9Nht8aLf`_x2lazxgIR z)bp9U?%002^$d!!UaMeP|DK&5WwHS8IBDE<^Y)uQ({uahJd#sP#G_;c3#Qa2Hp!Tq zO?|G%`d92t`+(d=GJ+U?wT>!@RbzeF#xM!I;2})llMJB@8EX-Ni*E|Am!U4!LMHP5 zL>i0NzL&fC3J;@sMHr*Cn-Z%M$)XU-_~T|DEQxFFI4;sw!N!o7^Jr*`utN>oF&LMY z^NZ&a0g8J7p}O$5+FNy{Jv>m=%S2qEXJ;-ilX!g~AMFEr&m!m1TaMm`sF={l6Rx#0 z*X=_2iy*;56BptsT$`R7qoru^74U9|E<;RH)+xfEY}8Q`K~lxfdEPCF91NV`o2Ct> z@}{|!T|gVXq84pu>G{tw@pW5t{30fv&e3Ac#Jd4-7oYeS&~c85Pq@qMomoFQ$E+`{ zo%Q-|?v$guY~!{A$NW7=2(2-sSWSYQwN7@(sjP^L(T~cw3wU>yU~i=5;wi1vwxX84 zR1JaP2%{7=eG5FLI^f=4r18O>=BcONgW+^N))AveD{d(X*U0s&rg&7y|WxLO8?YR&74}ToFa;=4$oG4JhH# zGc`}b47q$Pd)3Htr)H7N5jMmd2w=xR?vCPE&-QP?Op!w9A2L%S*~~pt*mux+i_d?G z$saJ`{3L@ta30<*)1>k@S7uB2K;3M`^^yW^Ug1wnCr`uo#9DbOe-5GJFT`w5|v$;E6=)XStM=uRu{}-Q}rhRPs zP9#6oR{d1#w6!ddgiu+2@8pGdPfb7jAhwsCCPC%yc!MSu7hK!5v8%TWK7L2Wasl(Asa^ z;&A4$V>z+f345*S zJ8zc0tx_f)JMQR!ehXY^Y%kJ=<^r379mSko_H<6Rk8|7n`~b45yA)U8E@lb`>+84` zET!BTlG|Q;;n=zoU{zfPLod3u0!7wUOjZ)+W`ecAqScAYONMF{ko%FKI$96aBGw3Y ztl`aVGfwPSk#OCj1wIRSTqRZv>5X6glxQ!|s5k z1+*z*V{bpU(r#2S7-9)TNH^+ZsE2)mF^s?n<(*->All4xY!qzoXQ{k)aA#&sc}tMvK@4&$|xT#o|rO{wdOmBy>H zyCbMA+TR?LCLqaA{r2J8{hJ;`j4H)h<`m3Xh zoR4y-mAlyW;rzEwgT66}ucyccm2Yscfqbl^0y9t@l&Q(vm6h|+3qBvrcwG&|2{BIu z&_L;vod;Q^yaMsAoMF z*lH(}I(tzk%P!p+wh4~$CL1Kj;bcM@c>B&X{e=99teF}QwQ}#g7?=;b z$2wq5NgycKP4(6`?8j0Umg^bSWm*OXj^c;e_vLvlxSqI`iUBv6CV_>^sW>7)m#d-@ z$obtIJij;(!r_3uy$hjz{RqwO2yon8bcEnI z84GsIa_^xLL5-U8G*nO2)6AG_s8Lu^V7j*F2<~c#zOQ5oCkN-W?}7wm1{94*U6j4Y$4b$%$B9F`3bxQevyhT7r-sG#r!3?Lsh$#wGaT7 zy7kvPZmS0K2ASzk61L23px@hz*AW*;hWR&j^quJ=$LFHpGpM(?0u)`9jx#v&rp+5h zHeIvf7tE1cKuEkCS-DRB$!xjUh^bL?p~R0Z;?+o~{5x!pfhxTYij;5+24Jabrxw-c%4+ zxXpqb5yMKtn%@SARvm@p;#?8jyByI3%eO@|K^!cEo-mf3Y9*`!L!2q(ZbX@4<<~5w zj0wlmR>Z>wv9(fYBIIpT!Y*2(I|T6wkRYSJWq<9h;|%24#LxK1x-utDd<<=5xAzD> z-V=ofxPe|^{2H-KSCr_q|IK?zy_ANQ z;R@By)^!N?Z-)sc3QvpL7lPq~_0gOjE|0*_e4#27VO$qHfd$oXfZT%9uy65X4Qi24}x(@In=&5+(Y3FFda zyWwfhca1uB8wz?lFZUSWMWnsJF=&7nB$-vh6(?s8n^ZlWm@1zI<1Lo?Gl?(}f9V6P zIK+gMJsH|@bI;D3@0xoD;$J~4e3<06Pu;pxy(UAU1F_3$z_7`fgE#7n9*?km3opkP zoQ$atG&!I9;QPexVIo{_W}9o7)QQ>Gu}ofn)$^ICf2~&&7WQH|Uyxe_QLa|9wvx2@ z^$l0nh983GbwRxFOGKp&ztU0rqaPKLyuN5&+*pR5OSz**)B~fnWa1 zNbs2?3b{JJZb4C9wzOEpttAUO_i@1&B4KG_OdAlm1FM9vh8Px9Y>TXH^~{;FRVyer zzKk%^hW4cVG4A?Bp&sSfE0KNFMinxiLY-}sVukeSQatiQ$#^e9&U4zhUrqPmjdTFp#^Gd#_lFa>d~FhCnyn_|Jc z5%G^nDYk+Anj=H3#!FnA-rtv;;(j$gPOXiR!#MGc3&nn zFaG3Z-ijeHp`fj@cZuw2f^)oB7_^n{geXfcc0nn$ACwxBgYnyK7lq*)3yx{{LJ(Tx zF`y^v=#Ei6s%CHm0g8s<8P7Y4^ze?XMrC;StfPASnlPP1`=JX*t|4`5HwxWs!`;RG z{9Y6mw-Xd`cl-_^gS@NlgDB%JFCi8B4N#PJW)#n=LmR4VE9Gn-cG}Y|fN{;Bv|l`< zJLeDe!NgOP0)2L}-d7FVS_RC8VXmIQufwA%mTqnV=o;l}N;=I{&z8vQ*>U@^5 zR@97j723}8^j(SbR?qn=oX>a0Y8+_8eMw;7EaR@O8Fvl(E%1!97U%7r^Q&>b&~r{A zw8**6%;#TUGxmD4UF_-Gh4UrOxqv%r6zWV*})ljkWH=^7ymmGpDD% z`UXFwHTi?-=U)U(D!+dJ++;}v(V08t(CM*fb5~cFiXGFddyBa4LD@Q_f7kIJ{@tow zAAC}rHR!bme_dV7Y}$R@b!qVCGeCT$n1~bbO7f-ujxx0v&lBCJ97%IJWPZYN=#asf zqVtHJBf>?N!?ht%t|`Yags+=8L%zuRh+B`HtF1r785E{J$3YY(sdoKIa^`LgB%ypF zn7B&U7dRqK(rOvixwjux!T1>RUJm&?tQS}5IKStm%-EI6TWUMIF^1B?3F?={6>5YR zPM@E=Us=2r6Y%XN!SF6Vd~lb4t*VX}sRRc7v6 zOuo(JJ4{|>a-PZUOvafUMUqsXGV8@wl4e?MwVGfZ*{0RZ(Uvl~oJj{02D4dLFj>Y# z_O@Kd+$JVBGP#M#2N=Lvb%n254fUtg=I6-$fjow3lk*;rxRfe$>z zjYj;Mj24vts?lPx55F}v)SJnsQUaUGFE#b><5Nc;D?j^QzmT}&e^K2^v0`Z<@QpUC zFh^FHYvjKNCK=SGc2^1>*o%EQK()4^C>a!Bu=U;jiU#?ss|JM-0aIJhu5+DroFjaL z$@PSUSfQ|QH$JurQ&I1*qrspOpAXz8Q7}p(>|l}q&f9L6UG>#Y`r*4XL!aS3Ct^b| zI3kH*{eB=ce&OTTyMMKi8MNYTKmw=Un#h=fW-D0c#|=sJoqT@6f^FL;6DgpO45<%j ztM96;=BUlGI5dS%0H)Jf0eS5EAtwp}k}ci_4!SftEu2$$_H%_4K3{I}8;qmrWJrmq zR+Lxo#AR%QFL5xJ8?M4fRr$q7gwePRPVboX5)c+Hqvbk0x!?$LXGSPxoxo>7MO5U4bIgoTl6E zZrk*9x8L{Q!QeqsQPSr0RMgzLb6<1k-v9pZdvQl=YuJN->Cb+{eB~P+&tLMP`Y(XU zZMX`9n#W^$OfBmfPLz^7~Ys}7pwMbbv?HRZZ_v>+ApW2YRF14n| zV|K0Z9M=YV@w`5@w)$)?>G2F~G`pv6NUce&P4%WWPWuKnrLIZE&5qPsbE(;L+Ba}x z>c-4wlcTcCTz=ZCdC>Nj)F!?avA$^!-mX39>0L3$k?-%-Y;7X~Z?QOnTX-~8C?xl% z2J>URx*fcC6!Co{wqGK>K|Azddi4I}&`7Fi2NAs|UnttGw`a46@6G3OsiC42c_3dZ zrYwm*cqCmMI$#G_x-@8qRN;>eW_o>gppZ&h$ZScCrG`)=Q8<*f!)7X*LOeg1v0I8( zDL0hFlQ}p9xR8HAgg#t*aTWeK0#lpNOnpKxdZx6pR`yKm6JCky68Dw8){|p9%ibw{ z8gOI!9@m$7?(}?e&4l0d7rkZwan0xPdPX$s0W&b76?W!yj8X7G&!L?Ud62K_`Iccu zCIY6v9B@h>GYyn}qFUMvJ?JU>tRIhUF8lbV1>f+dyx~jLI)}dWkcaj7Hq(n)36~Q1 zpl}@jDo?*X_xS1b!ODxTRbDzaGxbj8oij6EdbM))TeIIjG5hp!DYWZg(n^lT(+3kt z)3h*myW;oWuWG1#D^)C6xn1!)^7(8knUlovouAt9i20eVo3>B%KC<~&M7OB?Z4~+T7nGxO*;+a37{!w^Fu$G$~-Q-^!N`7Hn@iR|I&4Yae2F9!wSi zM7ab2=s==S8q5Lcy6Y1GWYzR`X|ADYS1qZopkH2Sx2X0KqshX^cRUt*_BS^nxO3Md zm6zYFJp0W@F2DQC<%=&~y7>O(7r*vMf2w#SZ;jlZOJ?)?8=p2GJZg6yPG-|4!L-)@ zC|WD5L*NPeG@l;PBAU<+aYu|S8@`yWc@%>0p| zYWuu%o_H_xBPe=~%Q7OuD0`-KQ*&b8vYzq100jJ!E-6@C3IBeSNd(digD7nfz2F`a zG~>bBz5U~DJGS36wqyJD8@Auncf+>6zET@rGyB6=FF*JC?DQ*u@yqX?>kZrfLg^rI zhLK35bLnCtVH^8X`O#F-I%@li>0&lz8^9X(B?nW$5r7)NhTw>HfKJL9P7b9Ksa(-Y zrwVq&$u1@bQElmvm6Ew}%cXM%OT~iS>15QG?)BQfLMl6K2e7i(UN1Te#288>hO)^* zA(0SZu~sA5x(>lMT!k0{4`w*5b!Y+kGqmv)^M`$N_2XP*MzUw%lJS2T5za5?cE4{z z8~c0_gzUHmRPqGSo0ichJwR|kUuNJmkX+fD!K`rRIqPQ2X}{({Dc__JCY11%eNrM? zUt-de@lOTHKF%Ua2T{7MtPg7fT`{x$M9Azo?Va$K{ZhA6>UNz5sXf#Ubj4X+(qAGL zp84E2D`&p3wJyG`F221kzQgK3d5oj4m>Mf8q(EL^&X2&>j`aF%ua!Sy2Zr+5(rC{1 z9ZZ^I5*`JWD5i%-j?Q(d7W_xjX7Rur@k)uKH}k`bl_#g%0cAP|vlCZ=ubA{q0VJnM z^E8sX7%*kRnQ~6>QKb2?C{km+nzb1fY7=Z5yR*sBK{L7MPNe@=T%PgW3(V>w0JeE| zHb0ci7WN>bn(5W8t!Ts!4&_HjlR0xv$5XG*BI>brGPs$+Elf3Rzm?4GPuY4A1X#hn zZ@-l`ZU0^DwzUzZ>_GD1!Boz)19y~)#eA+eAfI4|vs@<$4z{hOZNEI)e)(QIFeqi^ zJFDL;@Vp5v65kX!S%l{(;JFK77vd|m@r_si?e2W=3kfTGaTQ)eAe0&N0h9q0lF>8X zD#+vR7ZCPk{LC=}1ntvaq8K~}GC>E-L7$yx@LvG?>a#)q%Z%~4=*xtpjdm#+7LW#a zkTJ|ovrCncoED@-%q6D<-DO%cQM?V;RGW*!y8Er|=(hq|f|kWK-|MfUm$zs)B?sikUwJmE?QL(Ez1fsLs+pMs3wCV&7Avz z?aQWyi?(ktUp#;|e1)``vVFtpY}Pgorz|X$WESaxJTM`^mr-QXiqc89Yt1al^anP%P1UhunZW1p2u9Zsi?*lpEv)r6q)U0kz6 zBjEhxC4))J_8*lRp&=nZCD49v0a%@Q)w+SLc9sqb?W{IQq>Y8brD%s#XVH#Ym2uV* zSiKRk592CyBOt7H14?7^*A0k`0T+*7FJo@i6TZz0;QS%NDF5*zU0D4OcmRX|;VuI~ zoc2uUN8c*yQ$VycuxtRL43j}(neNWroLk*aRk^c7n&k!gX_sn#l7 zm38aKWkCE=z9l*muukxYkg{H7bqPiRc+@@?R?Wio?MTH zJ6441UBL9RcSPfY%7gt1=H7T^=ERxOI()Q3sF%NTLIK>R_ortbeO2+Fm(HKAoSj4* zfE08@1z8ss(MIVu1gerMKKI<0XJ38w7w^>!%4=G_l=FO&uy5WZGeLvVm(!w@f|bA@6uHBnX29KkFXW2$Lu%2({8k==Ca6utzclZ3;4N zhQeEszYka8dITQ8oesE*fTttQ1ZRi8sMZaRuM3zo0?r_+jjvq*Dw}DNi$3{1E=WF% z2Y`YnLx2EpHtqQwCU)#LJb9;lB64hdM4!+m^sxmuCP%4|P@^x|K0`vBH#ij5hEXu_`U-t>-bUk8wnrc?^qFU(Z|!zaZR!^&ql9F^Pz*-(zqE zR>JmQb<5Rc_D-n=_E=E1a*Df%>&2+OjXicCQ>kk-w zmVuy`8sb03w0JeBVk7Cvqe$gf6#@tZ)3;hoTNulKA30Zp<(u^~W{Vkte#U^{62(14 zfd%)l*=}~=-a_#NcZeirH}0)e>*5}zIv4jgbA`DQ_n3K&xeE7o^I9{GdxyE&T!VY3 zxz=2VdzX2gc|Gn+gvxY-%B;6rp~ia1Rbw@9y<7(^xC%c(Kmr36LmP&w;d7LWL{=+; z#VA5?K!wm$&;)lXGMwS2dmGY2ZaNeVb>+iuy1ya41#1IWA|lG9R#7ZPO~`$THWN!f z5tFc8!VU>LCG3(iOC;v04NmR2-y=EVyvydNBa z+u{2UHsjaPbfza1`!PCdbR@IYx$BE|ve;w9!ELKHG# zF}c5B`@z)a3btWd$s_lrb16HTMVJ8dY!yX)7LjMLeezVFn;wQH&CTUgNLrSHZDg?% zO3N^uP42flC84H^lf+gB|3YO0ma)nVJi*Tkj2uZ?5Q80FD~_}pKky=+gpcw-kPCo` z?rOLUQL!mSTP+;#Zh-IkY=VYX$!7|}zY`IF3-oOivjBYLsi}<6d-w!hryE0>2x*eG zlqLiX{4>o>eypoNiA{-OJ&u2scb}d8`uFCJow{`X4`;sk)-T?9`trpuRVLqZQl_79 zOYU-X^KOD2vS2`g&L^9iTZT5F5=)uX6F}L?d9cnPu^);DVktXTrES$Z*7HpEK}q_s zgrC6J*7R9%WXb^BM!Epz9U$^1k_6d#{9$d4);xaIVy-OWMfnge-U}xXob*-o(Wihk zOz(@@NeyX`E=>Q4$SMCx&q@8H7b!lfa!wehn5$8rh`1NIEK8%BaY`TCHtEY~j^SjT zl42%(6t^)G5z$ZjQI}XVG)>v%VaP3Q82?!5c67xZ#7pNd%)EN$()sUJ{_yPOizhB! zeCzVXXDa8ua_PcTh`Yt+4SoZbCqvoEuOv}2wMkDxh0wku4t^UUpa5^zOR4&|3j|EQ zRQdK(m8YMddH(I0H@*M}sJ!;X?E7D(w)EAvXJ4P5{lQa}WAC7&m1;>mx;228m4+R5^Nu}=s!kGiB^8KPR2Jjt`Xpf#2}QPiPT|8X*Go< zD?`TjBHyJG#7Y!JWni8}r0AK%P?$Ot%}`lSc*h=qYMILFdnTZQ=44XIJV6_qk9<_2 z2b1YHeP!6)mU=`Kl^9gdR6dKo56Wjl^b?^8@1dTDJY{d;u+##Wm=WQ+bSSSq8JHU~ z201RX71IzcJ%$c7jA7->8zTTRC?l>6X)+AE8rpG8 zY#52A^ycNuW`vcZa-;~$W`211?mA&wbR_O#A~NDalF=lC1=YT1Qai5WYqWl6Qf+Up zGztvRQNyb+PGG^OOzUOTkzxvMeh800Wzua3#&5s6X%?J4n#z@GGhe5wzK_juaqTO0 zAe{NiV>4fzs+@u0<`p%FAS>wt)pfv97E8m3Q-rCdzc*z2K8j}T7Lp(IH=WNFEGknJ zS4k{lwx&fpH%^)e1X-uGv z3>4T10~upPw;rR=QB#Q%9~E0By)4C^upBP`aX*&kN~UHaSe!9hh00#kf0;(}LoZ{I z!AYTDl%-}S!n$Up2bKUy2}p{+nxYh#C=-Q>5-cBj4t7!cA2a1SxA70qQnlZRIq9mL z(`QF*FE}(>GkRkQ$-nxgT6y8Cv#%+0cQw0e_O5EzuVQ8P?;AIEe>6`>+|#fKU0hGr zfQ#@r@w&SNt=Eu@6&(>BI&@5Ip=sUCqKH~|AGtbMn!lXqFF9XB{*BKm;Bdc{6@2aE3wD3V=lg1qFLbbFgNHP6vTB!Vj{q zS6#NZr8J7qYoLFZzYY89D|7Em%}kwa$Z!_n|1rsY3vZkw@0xxRYjJJ^V?P5no#TQW z53apw@B@S9O+q~(L5QO2gUE5XVZ$N_D`?fmL|Fx@qb&ZyGz*wdvCK3Aw7>;z{fL31 z*=mA3LL&_6RqqW#T2$LC@I{S2;f(iHn(o}kw_P2C)ngDBw(z1FA3*7GQmYCjARJl( ztR~T`*lN7}76bLKK0=9|?DLbgYjokNUf`@s(HV zb%XAnEHj6zKnCfg*OizwiF@J2W1J%OCnE5J5!M|>*r1f-q#Kl^kRGBG#dI!gELLh& z-8H$eoTKS5mT&D z4r1#TWcHdMyys0?pQ@;^Pmr0)Df3&wf%F&Y(E?=3a>u1rp*2mtunW5x6x>UI!)y6e*$QHX%hv$~vTA zx{$IQDP&7`A_bF=6az6#HgVt-{H7IP^;t*7)L#-$NFgs#p!^*m_oZw_Qv4^Y-WK@Y5-AZUU~ zF*wbMqMX_{>m>$vAOIA7m2sEm&Jm-{ZDiU%U~B~f6lx^QbE}YQ>$xLvsPl{F6|PD- zsGTf_Zns!ZqJ;H122V41iot0HLfd}C*e4OV)I1MMwDn`AL>&4q?SdEzMgOPBnGfFq z#jxK&2&Xc~;|DwwrUkBf-=XRH20Zm?;~TC9%E|waw>xz40Z8M?Lh8n?jWWeN^4Kic zS84V?Lys)>yoo$MjQ%R}*hH-tT6tB{INq}Rj;%K?B;#ClfBtll{(Pd^A9&DN{}6?$ zR2FVM4rQr*2AF$kr}lAzbXKF99j<-Add>+E7^L#9NOzAm-m}>FyWA3q4)MxfT!rPp zGGZ<8h_wJ1W)Qy-H*G0 zdjR(k?m^tc&{9#zK3s&SPdQKymcwPE96BDDXvuv9c_HL|9(KXzyv@i9Bkx=hTB@dc z9q<%tN-NXu;VHLdjNP6?YoO9>nTX&UBKU^Zv}YnZRxGzV-*G^`qpjR#MwWRHqSwQE zIXV)=4ueg2Zk5z1Q{gcbZ7he@f7YA^FQZUnJ!05HKYPf7(Th%n-O-Cpv=`A&>1n&! z2KQ53c$%>O$Ges8Exs{6Y~@Geh4d&z*@7K}ki73GoR_EYK9Mr) z*1h8GwioCQ+ArlJArzW&xu8|C@{eboCJUQIGESSD;te%7#dCQRZskZNt^<*X7f z#54JHE-uv(z{5K}6wf2Q+TyK4)fgQPA%4SyH0ZQy8`V}taoOM7rkIJd>?OHg7z=2j z${#5xRzuhi;XH%~kx#mZg~^CpT$!RAuzt!I8Ij&j;W*%MuSQF@ctolXG|VZr;7urN z8%!@YjHI+w_59AJgu_?bKxsrih`UBwSyi6cCLJ>IrDFF}ng{Pl3V!6`%VfR7&c)q8 z@KTGF%I1@%RQY`(raO`E2~g2Mk&o<9{U4pXY?u#XIBPu~vntj!=x>U}7@fYuW4cx+d zl#t3`Px9hpJN|LyCD^pIjvPo?DYdfFW*m!bgR@$lWu%JB0|Kr*Rl`|ax2_-8*Y{d4 z;B~#NVw|h#(V+Y;j2wbrooEw<;gPlUJ@!o;FSwTM7H2P%C^FiZ7h^Dkz;Us&y=X{* zcy;3xmO~SCORbZH>NsjrnMSo4m1Yb(aM5WohG!_(=8@srek>PYi3WI)uPE$B!;{+L z0Bic0oACmq0stuu1XV~h$eVX;0Kn%LLemfO=Hspdl3(bjgO)`Sj5vs`L)5TYi@6|X zca)tth*+Sp90Bvt9oV`Rq~r(L8e~p-5JnHeYWE7#TFe%fL`*=q*a}4pHh7f-ii8x~ zj(bk&#aO0Y?WUk>`J`U#z;>|CX_SNud2j-|Y(!%BiuZ;wVHAUzE|jN>`I53h)*lQ- zD6@oK9Og1HUj^~hQ+*Qs_wv#-c|?U z(RkbTzP>T+Il5to+Iuudd@;wBWE;>Cn)zJzC{V&-rgk8J|Iic63>-+&WnKwA-(iYx zIG-!p5$>osoV3!kdRb%07{Vq0C=~^8kwq=6W> z^4$+>kMvkJN8UC8yA%}50WzD*C_V?sV4AHxY{S{#wO9$f@tudbvcN)ej}g|ItR9XB zzBl%gR}8n@Nz~pigRjh_H6L_9vO@lIFCMGM|L*+ONWl1)U7&ClklYr6=lXmHhgbQk zdIoF3_5`f!aQ%XX8Yl+ve|HUF>)$=T?pM>BTi5?mTi*obHAg))UT4n2rC!PYRcs-f z>l6l=nLJy0@@pKZh?NCU)~JuLc9zhSKWBz;aAz13`ZI@^9W+6lto%_hW$^S#=Z5p} zQ&#pH@>|@_p%^UdKeD)DxTt7y?7VL<)kh8t8i1fUiziH_eX8(HaRwyyFd}285-HqI zNTJpsNo_sy1xS&2k+;GIBz+C;ar|L(-3^UgRUMtIS<~`(ZM|SVq?=!8s>2y znAn1KFSGmyv)J+28&)Iww@9-78-xGOU=IVC0PoP@t(-O0jP*Z}VFw3`xx`V-Qyfo_ z(YOEZ5rTh zyczHj^xedsO3hq)+KE}&-C31b0&wWi5+BJVUVWn(E z5ldDQCesmicOV9im0zXyIo{9M2M|b$_&TS>s+q4=l7-N!H27as+|6`}FM)^>R41+i z$ug~I*s}#$MF{Yk`2-kl7I61V_!yF?LO$BSc!u)PN=zlJsv;llDMQ~NIbk7|dxW%! zmp59Ch%DwKFgW|E<`J|d=nrOL_Pu8-kAD-NT9GNNQLuVs&PllR{<)dw-manV)@Jlg zW2xvAV04$ga|&Z0Hh_)4a`fKL>4FU|M=o^V1fSCh=nSmp6sUxzLMqz?D zixYb6u+tRGVt&Q!#A*DWE-g?s30t1V%?Nme^%WgjmhdVO)y(cJiJWfqs|yHvDYmT#%fTrKSjBF6+tM<$q*D;R%;nQyqsexb zgVI8Xj`ZrAr1fju*7?1QwQiJ+1zKNK*Sdk$L-=cK{aUwmI8rUzIz+_Xjrw__O4wt|8YURu~b~&8?!Ms%z6?Q3x zl9db3!6~%z`eW+onO(LIzMU36MBdO&^x46n6M;*!QLpAl(t`)VVqmZwT%?O;kwPNQ zZSX4ZcAO7EMVp{&2Srzf3+yyk`d&fQakQrh7^;kpri(j)L<=Mn6fVX$HKO61+F0!D z0IpFgRkfZ?~nX+;xNvb|aC%SP=qrK(#eba zr_w4Vojslu;?u@gH?FD$YW}|%_@-^lzm0+a7>1&8;9U=%D-Zmm zsJO_$$J{Nl&ZvKMkx^gOIO@&S+#yGGj~~}wT!oJ#Ahbph!ciSDYqK0AP~CzPA@@hi zGnDBoaf-w`u^v@(yO2x!lE$M26d!qL7pzA*u(h{DkKuE!7sdDyx_h^xW)u zbX=JJemLuW5v?e5Bn3KXn4DvrM)xZZk@HBcvg|LjY}^e5a>KPPYQN8+BTvnpP6410 zaOCss9JS&L57|CcZyK_VV|$RoEiY=ggk*8sO78lxbxzK587eP^ipSb$@ZEPb7KgTB zz`&Y=PG5PzgiTm{!1|%I{t`d^ECM^koqnQDlTot${jgC#C_ zHV_XSjg22Vo1jw$8f9q_LIN1}4%v^#(d;aJ5HE9wO99<{md!0N4*-9aG16&qz6Qk( zfXY0WDyA=C*yaedztlGTQtP#~92Nz^ukzlx*>_)^d*N{?Acg1RIX+nvPR=*?ZSPC= z0X>Vz`zI)?G#;N|>egzyg`)%B^8 z)3_)jFM@>BuFgi{Nnb{FSc4<(hCz{OKy1eU0vL^T7lw}K(19yM2OvQaUhJ%qcmPEB zZifg5YhtdLrbU=VIS7`78>vD4Uj!HS4??gsXuT6S^3!t15>r74khI$w5YM=`ld#lX zqAJw|6*cpUrn*!`NdRt{|9jw6a}z0DhhEI5>06qJ3^YpELMkWY5eiR1d0^d$WoXgA z)7rwI55Z!vXl+I2Re|DOw!Dq)%>zYiJKwM{C>qV6D7**Z(b~br2#0ozOtLzDO7Z%O zKxT(Pm&0PHl8+NGyU|FME#bxxA3&4f349-KB$V?TaG?_o)pXLIN8hkFby3{KJO?@# zU<)M|E@pt&hv&m96c>T2TED2_SGkqB}jdUI!% ztD|NqVnwASVf{|oK*{>RqF4h^rHBHhxwZ~5jAa^YFL7%}uUMOV$Y||zYAgM?>;d*e z=e<;PL37BoSALNbbDO2ST}3j zNkcEca1MG0NaG92^jlHl>J{!F|9`~_&AN0^#H$yHo}peFK>kFX=J?hm4g=dYglG3( z0I^P7g((C|@4leX8=K|f)UQ!5kJHk2H%U;)tuvSpG%Xe8dR=q+n3Ie1QAEGVTwlgF zSNa(=59f=rC^{I%ncM4SyHDyn0fv+LMRf-Iu>}0k~@f zS^Xszs2#~YP1=_0nQ`^@fITEm zanu2Owu|u(GN9X&bt{9KmZOCq9yqc9)&a&~^+M=} z7B>b*sQ{TDag;K6u8TNDh16zVoOndy0Ro02XNlUe9hrtyu?YAU6j9?MP}aB@Kltzg2a-Jjor539Ka^_yEDqpx}a>%|pY@K2Jca35r~ z53O+L(wwj9aNFr@XeXM1Q5H4eyk&MzYQ-2ZNxN88mcrSKHV+=pQ)@FYh5anBW$++g z65Jqi!?^dzDW*}_CcLyfm@6`X$hXLEq0r6j*;Zz2%oy#)#loU z+;uq8r&f1eQ{C&Kb+6@K-%xj{xdHn1jlv8>Gd<|l4S3%&a}(?(JR*3hd84^mY{xd&gG}6Q zelvQ!$eC^=u2vfmNj+s&mVkO1%#UoQ(e)N}X+{bKQBskOT_tXnRAZr-k6j9P{+#dp zPYCeN!;JreapnA`9AGF!Ti;>eatq?`1{~;G7@E(d^TEwKU$Sr#HxDDSMDPC?n;;RK ze@fkL_+}**3-6{n2$CuM%;Ttn&Pg}^gw@%at>1gU5^311?FFNv+ zC+WhlLwsquwZ-v|oj@5mpf&^(YnV%krchk1IG-oX<#gz+hm;dT%Y3gq@*<80!}JsN z?1;nTh!w4F%E>HSM-U)?jJIer%R(?!`aQ8#+{4F1430AR41)~_>?l3a(zzsErOC3i z)jxx85eVot>fTLogT9bw90?@yFn3{w#3(^sq21!}I^v*a*ggWR?PpkMBJXyaQ?%%w zb*vH!%?y$k#*n`w)sN#19#D7n7Z>W^17Co!995v4rkXw2>L@(4cZIsMc+jc~oLB>* z!xiBCI392Y51grmt}ozZI<|3=%E>hobaR4z!o*p|JX>4#4q>D1X`H1a8s2r{(*iC4 zIF%N+V86_#uvhBHiPCDk4U)|4$u}ymJ)=yc+)4A&w2LvpvQ~kCv(HngvAqNGJj{VS z>dUZj6mcEXYKMvk=!6TGH#@LTq852AIYkEMMY67F@P9~Kiu3v%HV{XGu%uEt2L4Cs zb{!5&&a!GU@GU;N%u#}voFngkOjU;=*w6JonP<|qgWMd0bH5!~hQJAzpuF(l5ktB4VLknHiRJuU%EHh-E! z9sC5@ba|s}T3^|nOp+(pe(_zv8wL~zRBgth0D%oySOAjh2t;tcftS46LXLDzxiC47 zOAwZ%>=Fnr-PEQvm0b#Vr=Etgni3vFXy~Fyf%>|+%DfhLFH+;D@f#uXo@G4!b6&Z% z$n!bnbf+AJ2nr$f?Ol&N>ekpEzz_Xi24P~uymGzB3pnLKOs073%nEU-Vq7=ZxN)y} zog4R=8yGJdQ=uXpDCKuWTFi}TD`MV2jtIXq(qeAHbE|shLG=(IAd-kX7_^1v7UZ@$ zx#3IdoHGo#Pqh7o77C!I5b4T6tO?d9Ss&;oaw0kq^b=9?V3!azM zJ%0et-F45q@VwM{&-}0ZU_;vMO4wb7iJ^3*UMV&~LquKn~@R^LS_)7~a{}*C!$b z=-XFWh75Z8IV_JkqT}=ytiAMEAVicN#=pKkMs^I|a!U&2&aEM*1*HEytEEv*ZQrSL z^HN5YMa>m7GKjeXZEj^R{)E9_u>&7q2WA;Oi=fnvG-a@HtR*7IgVnw69pyRJ>w${wTCCo zJ@vvFD8VO9!l%Tcx;>M4!+m_i?QER-;-=kU!lr>q`X=*fVsxB`#fcl~uUFG6U%PO5 z`j}EOR~vOR>$TSMmL_KvNpme;su6jcV-Y*|9)80~##$vbTl*Kz61%o&$Q_gFJOC)I zMEC2hZq1XZhNMyTUnk5rX#rH1h4NKiKT~=83;n$-t*_w~%Kr4P86(+NXHh)Om>fv) zXN=v+;JXNHAI~VXzR%|vgE=Ou(+rb1vCT|bQB0-P#b7Oibqua!u%5vU3~pj@3xf|a zkOLnE7@I+Bki<#(aQd%U^Y9Z$>R0rDnaZb;ZciC6w&%J>i1y< z6!szF33NmB=;Eb!;a>koqKH2)O=K~h+)xA|@BFKeGgs2(T$u3{O+-|=XE;awE?w{( z?ysXO>U}}R|5ckALFYToIpVSLw=(JzF|E^$jBHwj7`Q; z<1XVm<7Oj@zgAprMu)M}_+6tz>hQ-UMw97WA|X(w*2v6L&(55Brt-pjX&gf1Np}E) zDLXB3<#@|yCdNK9afAG~{YMU9^E@yVQ6ywb?kKeN!Tk<`v3`VP*-<3uqj#${%7l+` zb|d&T#z6oXlqkuA3i2?OnDyd__+}_ZiXyq~z5m|(`u{x@h~@B#n; diff --git a/NetworkAnalog/__pycache__/SimObjs.cpython-37.pyc b/NetworkAnalog/__pycache__/SimObjs.cpython-37.pyc index 3d00c6924693db80428aec0c7c459997f6ee5594..772375f13d0a416036444d9a5ca70aa262bf1086 100644 GIT binary patch delta 5537 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 Y@u7SwRO!CTbG4`6(CDr9uJ(rh4f2z<0{{R3 delta 5613 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 delta 609 zcma)3O=uHA6rR~+lih7HFIqOCp&@CB8kL3ocNa`G5p=bM9F|ZoL0i%Y9vZ5iymXaV zf+x%PQ}9p`Z;DyXL8+H|u!UZPVxd>tlSdUV;=#AE(yMbA-uw8z?|pB^UK8v!;eJ%0 z=az;mc)z}~5~rm%315PB34DWH_zWLm6}r7ci8hR4_m+TUnbo{znYBuW5Hc)Vvf>$% zt*Hwct;|=elguIT9=^jm?7=oTD7_0KSiK`~pp;#yHHAw0PkH&g3Mt}a9WT^@!zTN{ zI_w9e&|scX#yfM_5ZxockdzkvH5mj!=1wfh%EBq~HRnf2b)o5nBu}(N{U^PKLkcb^ z>5XD@W;$zV1*4JG@DzHC4A7E97dFLQfW