上次偶然发现OpenSees的recorder命令可以向TCP端口发送数据,并采用Python进行了监听,这次干脆直接获取模型的位移并将其图形实时的绘制出来。由于recorder的TCP传输不会影响OpenSees的计算效率,因此可以得到下图所示的效果:
由于TCP端口的监听是一个循环,因此必须要调用Python的子进程或者子线程模块进行监听。子进程和子线程最大的区别就是变量共享问题,子进程为独立的进程,因此存在自己的变量空间,需要考虑进程之间的通信,而子线程则是和主线程共享同一个变量空间,因此只需要设定一个全局变量就可以使得子线程的数据传输过来。另外要说明一下的是Python由于一些设计原因,使用子线程并不能实现并发,如果想要实现多核心运算,最好还是使用子进程技术。
下面绘制一下实时模型显示的流程:
在正式开始绘制之前,需要在OpenSees模型中增加下面的语句:
1 |
recorder Node -tcp 127.0.0.1 8099 -time -node 504 -dof 1 2 3 disp |
上面语句将recorder记录得到的504号节点三个自由度的信息发送至127.0.0.1:8099端口,更多详细信息见:使用TCP接收OpenSees的Recorder数据
首先定义全局变量和端口监听函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 |
# -*- coding: utf-8 -*- """ Created on Thu Nov 24 18:49:08 2016 @author: orycho """ ################################### import socket from struct import Struct ################################### HOST = '127.0.0.1' PORT = 8099 ################################### TotalData = [] Data=[] ################################### def OTCP(): global Data,TotalData,HOST,PORT tcpSerSock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) tcpSerSock.bind((HOST, PORT)) tcpSerSock.listen(5) unpacker = Struct('d') while True: print('waiting for connection...') (tcpCliSock, address) = tcpSerSock.accept() print('Connected from:',address) datarecv = tcpCliSock.recv(unpacker.size) DataSize = unpacker.unpack(datarecv)[0] print('Data Size == {0}'.format(DataSize)) SizeReceived = 0 while True: try: datarecv = tcpCliSock.recv(unpacker.size) if SizeReceived > 0: Data.append(unpacker.unpack(datarecv)[0]) if SizeReceived == DataSize: SizeReceived = 0 TotalData=Data Data = [] else: SizeReceived += 1 except: print('disconnect from:', address) tcpCliSock.close() # 退出 break tcpSerSock.close() ################################### |
注意上面定义了两个全局变量,Data全局变量在每次接受数据之后会刷新并清空,因此不适合作为绘图时再次调用的变量,绘图时候会从TotalData里面读取数据。
下面定义OpenSees的子程序部分,用来在开启TCP监听之后直接调用OpenSees执行FILENAME:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
# -*- coding: utf-8 -*- """ Created on Thu Nov 24 18:49:08 2016 @author: orycho """ import subprocess FILENAME = 'main.tcl' def COPS(): try: outPuts = subprocess.check_output(['opensees',FILENAME]) except subprocess.CalledProcessError as e: outPuts = e.output code = e.returncode print(outPuts) print(code) |
这里直接使用了简单的check_output调用了opensees命令执行FILENAME。需要注意的是,我先前已经将OpenSees程序所在的文件夹添加到系统环境变量的PATH中去了,因此直接使用opensees就可以开启opensees分析了。后面调用Matplotlib进行绘图,这里采用的基本和之前的监控CPU差不多的形式,但是为了更加美观,我稍微调整了一下图像的刷新方式,图像的坐标轴会随着数据集的变化而变化:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
from matplotlib.lines import Line2D import matplotlib.pyplot as plt import matplotlib.animation as animation import matplotlib matplotlib.style.use('ggplot') # Axis setting xRange = 1 ymin = -1 ymax = 1 class Scope(object): def __init__(self, ax, maxt=xRange): self.ax = ax self.maxt = maxt self.xdata = [0] self.ydata = [0] self.line = Line2D(self.xdata, self.ydata, ls='-',lw = 2, alpha=0.8, color='gray') self.ax.add_line(self.line) self.ax.set_ylim(ymin, ymax) self.ax.set_xlim(0, self.maxt) def update(self, y): lastx = self.xdata[0] + y[0] if lastx > self.xdata[0] + 3/4*self.maxt: self.ax.set_xlim(self.xdata[0], max(self.maxt,5/4*lastx)) self.ax.figure.canvas.draw() lasty = y[1] if lasty > max(self.ydata): temp = self.ax.get_ylim()[0] self.ax.set_ylim(temp, max(ymax,5/4*y[1])) self.ax.figure.canvas.draw() elif lasty < min(self.ydata): temp = self.ax.get_ylim()[1] self.ax.set_ylim(min(ymin,5/4*y[1]), temp) self.ax.figure.canvas.draw() x = self.xdata[0] + y[0] self.xdata.append(x) self.ydata.append(y[1]) self.line.set_data(self.xdata, self.ydata) return self.line def emitter(): while True: yield (TotalData[0],TotalData[1]) |
首先说emitter这个生成器,这个生成器用作图像的frames,Matplotlib的frames可以使用迭代器和生成器作为参数,Matplotlib会依次从中获取frame传给图像更新函数。由于这里采用了生成器,因此图像是不会循环的,另外就是生成器会返回一个元组,用来表示新产生的数据,数据的来源就是TotalData的数据了。而updata这个图像更新函数,在每个interval都会调用这个函数,这个函数有一个参数就是当前的frame,当前的frame就是来自于frames的迭代器或者生成器的返回值。
在update函数中,实时的检测数据集的边缘是不是达到了坐标轴的边缘,如果到了,则从新更新坐标轴,并刷新图像。下面启动子线程和子程序并绘图:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
import time import sys import threading OTread = threading.Thread(target=OTCP, name='OTCP') OTread.start() OCOPS = threading.Thread(target=COPS, name='COPS') OCOPS.start() time.sleep(3) fig, ax = plt.subplots(figsize=(10, 8), dpi=80, facecolor='w', edgecolor='k') plt.xlabel('Time',fontname="Times New Roman",fontsize = 15) plt.ylabel('Disp',fontname="Times New Roman",fontsize = 15) for tick in ax.get_xticklabels(): # 设置标签字体 tick.set_fontname("Times New Roman") tick.set_fontsize(15) for tick in ax.get_yticklabels(): tick.set_fontname("Times New Roman") tick.set_fontsize(15) scope = Scope(ax) ani = animation.FuncAnimation(fig, scope.update, frames = emitter, interval=100, blit=False) plt.show() sys.exit() |
这样就可以直接绘制出上面的动态图了,但是需要注意的就是我在启动OpenSees子程序的时候,使用time.sleep(3)让程序暂停了三秒钟,这是因为OpenSees自身还需要对tcl语言进行解析和执行,因此不暂停程序,则刚开始绘图函数无法从TotalData中获取数据,主程序会直接报错退出。除此之外,程序在运行过程中尽量在本机实现,联机测试可能会出现丢包现象,这个问题在上次已经讨论过了,就不在讨论了。
点击下载本文源代码:点击下载
请教下,怎么这么强?
小天才