'''
Input and Output functions for the CST modeling package.
'''
import os
import re
from typing import Tuple, List
import numpy as np
#* ===========================================
#* I/O and format transfer
#* ===========================================
[docs]
def output_curve(x: np.ndarray, y: np.ndarray, fname='curve.dat', ID=0) -> None:
'''
Output airfoil data to tecplot ASCII format file.
Parameters
-----------
x, y : ndarray
coordinates of the curve.
ID : int
if `ID`=0, create new file and write header.
If `ID`>0, append to existed file.
'''
nn = x.shape[0]
if ID == 0:
with open(fname, 'w') as f:
f.write('Variables= X Y \n ')
with open(fname, 'a') as f:
f.write('zone T="%d" i= %d \n'%(ID, nn))
for i in range(nn):
f.write(' %20.9f %20.9f \n'%(x[i], y[i]))
f.write('\n')
[docs]
def output_foil(x: np.ndarray, yu: np.ndarray, yl: np.ndarray, fname='airfoil.dat', ID=0) -> None:
'''
Output airfoil data to tecplot ASCII format file
Parameters
-----------
x, yu, yl : ndarray
coordinates of the baseline airfoil.
ID : int
if `ID`=0, create new file and write header.
If `ID`>0, append to existed file.
'''
nn = x.shape[0]
if ID == 0:
# Write header
with open(fname, 'w') as f:
f.write('Variables= X Y \n ')
with open(fname, 'a') as f:
f.write('zone T="Upp-%d" i= %d \n'%(ID, nn))
for i in range(nn):
f.write(' %20.9f %20.9f \n'%(x[i], yu[i]))
f.write('zone T="Low-%d" i= %d \n'%(ID, nn))
for i in range(nn):
f.write(' %20.9f %20.9f \n'%(x[i], yl[i]))
[docs]
def output_surface(surf: List[np.ndarray], fname: str, ID=0, zone_name=None) -> None:
'''
Output the surface to `*.dat` in Tecplot format.
Parameters
------------
surf : List[np.ndarray]
surface coordinates, i.e., [surf_x, surf_y, surf_z].
fname : str
name of the output file.
ID : int
if `ID`=0, create new file and write header.
If `ID`>0, append to existed file.
zone_name : str
name of the zone.
'''
# surf_x[ns,nt], ns => spanwise
ns = surf[0].shape[0]
nt = surf[0].shape[1]
if ID == 0:
with open(fname, 'w') as f:
f.write('Variables= X Y Z \n ')
with open(fname, 'a') as f:
if zone_name is None:
zone_name = '%d'%(ID)
f.write('zone T=" %s " i= %d j= %d \n'%(zone_name, nt, ns))
for i in range(ns):
for j in range(nt):
f.write(' %.9f %.9f %.9f\n'%(surf[0][i,j], surf[1][i,j], surf[2][i,j]))
[docs]
def read_tecplot(fname='tecplot.dat'):
'''
Read a tecplot format data file.
Parameters
------------
fname : str
file name.
Returns
-----------
data : list of ndarray
data of all zones, shape [ni,nj,nk,nv].
name_var : list of str
name of variables.
titles : list of str
title of zones
Examples
-------------
>>> data, name_var, titles = read_tecplot(fname='tecplot.dat')
'''
name_var = []
data = []
titles = []
n_var = 0
with open(fname, 'r') as f:
lines = f.readlines()
nLine = len(lines)
iLine = 0
while iLine < nLine:
line = lines[iLine].split()
if len(line) == 0:
iLine += 1
continue
if line[0] in 'Variables=' or line[0] in 'VARIABLES=' :
line = re.split(r'[=",\s]', lines[iLine])
while '' in line:
line.remove('')
name_var = line[1:]
n_var = len(name_var)
iLine += 1
continue
if line[0] in 'zone' or line[0] in 'ZONE' or line[0] in 'Zone':
line = re.split(r'[=\s]', lines[iLine])
while '' in line:
line.remove('')
if 'i' in line:
ni = int(line[line.index('i')+1])
elif 'I' in line:
ni = int(line[line.index('I')+1])
else:
ni = 1
if 'j' in line:
nj = int(line[line.index('j')+1])
elif 'J' in line:
nj = int(line[line.index('J')+1])
else:
nj = 1
if 'k' in line:
nk = int(line[line.index('k')+1])
elif 'K' in line:
nk = int(line[line.index('K')+1])
else:
nk = 1
if 'T' in line:
# 非贪婪模式:寻找最短的可能匹配 https://www.cnblogs.com/baxianhua/p/8571967.html
str_pat = re.compile(r'\"(.*?)\"')
name = str_pat.findall(lines[iLine])
titles.append(name[0])
else:
titles.append('')
data_ = np.zeros((ni,nj,nk,n_var))
iLine += 1
for k in range(nk):
for j in range(nj):
for i in range(ni):
line = ['#']
while line[0] == '#':
line = lines[iLine].split()
iLine += 1
for v in range(n_var):
data_[i,j,k,v] = float(line[v])
data.append(data_.copy())
continue
return data, name_var, titles
[docs]
def read_block_plot3d(lines: list, iLine0: int, ni: int, nj: int, nk: int) -> Tuple[np.ndarray, int]:
'''
Read block data from lines.
Parameters
-----------
lines : list of str
f.readlines() of the entire plot3d formate file
iLine0 : int
the first line of this block is lines[iLine0]
ni, nj, nk: int
size of this block
Returns
---------
xyz : ndarray
coordinates, shape `[ni,nj,nk,3]`.
iLine0_new : int
index of line after read.
Examples
----------
>>> xyz, iLine0_new = read_block_plot3d(lines, iLine0, ni, nj, nk)
'''
xyz = np.zeros([ni,nj,nk,3])
ll = iLine0
ii = 0
line = []
for m in range(3):
for k in range(nk):
for j in range(nj):
for i in range(ni):
if ii >= len(line)-1:
# Need to read the next line
line = lines[ll].split()
ii = 0
ll += 1
else:
# Read next value
ii += 1
xyz[i,j,k,m] = float(line[ii])
iLine0_new = ll
return xyz, iLine0_new
[docs]
def output_plot3d(Xs: List[np.ndarray], Ys: List[np.ndarray], Zs: List[np.ndarray], fname='plot3d.xyz', scale=1.0) -> None:
'''
Output surface to fname in plot3d format.
Parameters
-------------
Xs, Ys, Zs: List[np.ndarray] [n0][ns,nn]
coordinates of multiple surfaces
fname: str
the name of the file (`*.xyz`)
scale: float
scaling factor.
'''
# ns: number of span-wise points
# nn: number of curve points
n0 = len(Xs)
with open(fname, 'w') as f:
f.write('%d \n '%(n0)) # Number of surfaces
for i_sec in range(n0):
ns = Xs[i_sec].shape[0]
nn = Xs[i_sec].shape[1]
f.write('%d %d 1\n '%(nn, ns))
for i_sec in range(n0):
ii = 0
ns = Xs[i_sec].shape[0]
nn = Xs[i_sec].shape[1]
for i in range(ns):
for j in range(nn):
f.write(' %.9f '%(Xs[i_sec][i,j]*scale))
ii += 1
if ii%3==0 or (i==ns-1 and j==nn-1):
f.write(' \n ')
ii = 0
ns = Ys[i_sec].shape[0]
nn = Ys[i_sec].shape[1]
for i in range(ns):
for j in range(nn):
f.write(' %.9f '%(Ys[i_sec][i,j]*scale))
ii += 1
if ii%3==0 or (i==ns-1 and j==nn-1):
f.write(' \n ')
ii = 0
ns = Zs[i_sec].shape[0]
nn = Zs[i_sec].shape[1]
for i in range(ns):
for j in range(nn):
f.write(' %.9f '%(Zs[i_sec][i,j]*scale))
ii += 1
if ii%3==0 or (i==ns-1 and j==nn-1):
f.write(' \n ')
[docs]
def output_curves_igs(x: np.ndarray, y: np.ndarray, z: np.ndarray, fname='curve.igs', n_degree=3, is_planar=True):
'''
Output curves in the Initial Graphics Exchange Specification (IGES) format.
Parameters
------------
x, y, z : ndarray
coordinates of the curve(s), [:] or [n_curve,:]
fname : str
file name.
n_degree : int
degree of basis functions.
0=Determined by data; 1=Line; 2=Circular arc;
3=Elliptical arc; 4=Parabolic arc; 5=Hyperbolic arc.
is_planar : bool
whether is planar curve in X-Y plane.
References
------------
https://wiki.eclipse.org/IGES_file_Specification
https://filemonger.com/specs/igs/devdept.com/version6.pdf
'''
#* Curve dimension
if len(x.shape) == 1:
n_curve = 1
n_point = x.shape[0]
x = x[None,:]
y = y[None,:]
z = z[None,:]
else:
n_curve = x.shape[0]
n_point = x.shape[1]
#* Output IGES format file
f = open(fname, 'w')
#* Start section
f.write('This is igs file generated by LI Runze. All rights reserved. S 1\n')
#* Global section
f.write('1H,,1H;,3Higs,7Higs.igs,44HDASSAULT SYSTEMES CATIA V5 R20 - www.3ds.com,G 1\n')
f.write('27HCATIA Version 5 Release 20 ,32,75,6,75,15,3Higs,1.0,2,2HMM,1000,1.0, G 2\n')
f.write('15H20180311.223810,0.001,10000.0,5Hyancy,15HDESKTOP-BEPNROH,11,0,15H2018G 3\n')
f.write('0311.223810,; G 4\n')
#* Data entry section
iType = 126 # Rational B-Spline Curve
iLineStart = 1
nLineCount = 3 + 3*n_point + n_degree
for ic in range(n_curve):
# iLineStart: is the line number inside the PD section that has the first line of this entity data.
# nLineCount: specifies the number of lines this entity takes up in the PD section.
f.write(' %7d %7d %7d %7d %7d %7d %7d %7d'%(iType, iLineStart, n_degree, 0, 0, 0, 0, 0))
f.write(' %1d %1d %1d %1dD %6d\n'%(0, 0, 0, 0, ic*2+1))
f.write(' %7d %7d %7d %7d %7d'%(iType, 0, 0, nLineCount, 0))
f.write(' BSp Curv{:<3d}'.format(ic*2+1) + ' 0D %6d\n'%(ic*2+2))
iLineStart += nLineCount
#* Parameter data section
iLine = 0
for ic in range(n_curve):
is_closed = False # is the starting and ending point the same
is_polynomial = True # if all weights are equal, otherwise, the curve is rational
is_periodic = False # actually has no difference
# Starting
iLine += 1
f.write(' %4d, %4d, %4d, %4d, %4d, %4d, %4d,'%(iType, n_point-1, n_degree, is_planar, is_closed, is_polynomial, is_periodic))
f.write('%30dP %6d\n'%(ic*2+1, iLine))
# Knot sequence (n_point + n_degree + 1)
xKnot = knotx(n_point, n_degree+1)
for ix in range(xKnot.shape[0]):
iLine += 1
f.write('%19.10e, %51dP %6d\n'%(xKnot[ix], ic*2+1, iLine))
ximin = xKnot[0]
ximax = xKnot[-1]
# Weight sequence (n_point)
for _ in range(n_point):
iLine += 1
f.write('%19.10e, %51dP %6d\n'%(1.0, ic*2+1, iLine))
# Node coordinates (3*n_point)
for i in range(n_point):
iLine += 1
f.write('%19.10e,%19.10e,%19.10e,%12dP %6d\n'%(
x[ic,i], y[ic,i], z[ic,i], ic*2+1, iLine))
# Ending
# Start parameter value, End parameter value, Unit normal x, y, z (if planar)
# (note: '%**dP' must have at least '%9d')
iLine += 1
f.write('%10.3f,%10.3f,%12.5f,%12.5f,%12.5f;%11dP %6d\n'%(
ximin, ximax, 0, 0, 1, ic*2+1, iLine))
#* Ending section
f.write('S %6dG %6dD %6dP %6d %40s %6d\n'%(1, 4, 2*n_curve, iLine, 'T', 1))
f.close()
[docs]
def plot3d_to_igs(fname='igs', plot3d_ext='.xyz', igs_ext='.igs'):
'''
Converts Plot3d surface grid file [fname.xyz] to IGES file [fname.igs].
Original Fortran version by Prof. Zhang Yufei: zhangyufei@tsinghua.edu.cn.
Ref: https://wiki.eclipse.org/IGES_file_Specification
'''
#* Read plot3d format file
if not os.path.exists(fname+plot3d_ext):
raise Exception(fname+' does not exist for format transformation')
with open(fname+plot3d_ext, 'r') as f:
lines = f.readlines()
line = lines[0].split()
num_block = int(line[0])
nIJK = np.zeros([num_block, 5], dtype=int)
for i in range(num_block):
line = lines[i+1].split()
nIJK[i,0] = int(line[0])
nIJK[i,1] = int(line[1])
nIJK[i,2] = int(line[2])
nIJK[i,3] = idataline(nIJK[i,0], nIJK[i,1])
if nIJK[i,2]!=1:
raise Exception('Wrong input file: dimension K is not 1')
if nIJK[i,0]<4 or nIJK[i,1]<4:
raise Exception('Wrong input file: dimension I or J less than 4')
nIJK[0,4] = 1
for i in range(1, num_block):
nIJK[i,4] = nIJK[i-1,3] + nIJK[i-1,4]
kLine = num_block+1
#* Output IGES format file
f = open(fname+igs_ext, 'w')
#* Start section
f.write('This is igs file generated by ZHANG Yufei. All rights reserved. S 1\n')
#* Global section
f.write('1H,,1H;,3Higs,7Higs.igs,44HDASSAULT SYSTEMES CATIA V5 R20 - www.3ds.com,G 1\n')
f.write('27HCATIA Version 5 Release 20 ,32,75,6,75,15,3Higs,1.0,2,2HMM,1000,1.0, G 2\n')
f.write('15H20180311.223810,0.001,10000.0,5Hyancy,15HDESKTOP-BEPNROH,11,0,15H2018G 3\n')
f.write('0311.223810,; G 4\n')
#* Data entry section
iType = 128 # Rational B-Spline Surface
for ib in range(num_block):
iLineStart = nIJK[ib, 4]
iLineEnd = nIJK[ib, 3]
f.write(' %7d %7d %7d %7d %7d %7d %7d %7d'%(iType, iLineStart, 0, 0, 0, 0, 0, 0))
f.write(' %1d %1d %1d %1dD %6d\n'%(0, 0, 0, 0, ib*2+1))
f.write(' %7d %7d %7d %7d %7d'%(iType, 0, 0, iLineEnd, 0))
f.write(' BSp Surf{:<3d}'.format(ib+1) + ' 0D %6d\n'%(ib*2+2))
#* Parameter data section
iLine = 0
for ib in range(num_block):
ni = nIJK[ib, 0]
nj = nIJK[ib, 1]
nk = nIJK[ib, 2]
# Starting
iLine += 1
f.write(' %4d, %4d, %4d, %4d, %4d,'%(iType, ni-1, nj-1, 3, 3))
f.write(' %4d, %4d, %4d, %4d, %4d, %11dP %6d\n'%(0, 0, 1, 0, 0, ib*2+1, iLine))
# Node vector
xKnot = knotx(ni)
for ix in range(ni+4):
iLine += 1
f.write('%19.10e, %51dP %6d\n'%(xKnot[ix], ib*2+1, iLine))
ximin = xKnot[0]
ximax = xKnot[-1]
xKnot = knotx(nj)
for ix in range(nj+4):
iLine += 1
f.write('%19.10e, %51dP %6d\n'%(xKnot[ix], ib*2+1, iLine))
xjmin = xKnot[0]
xjmax = xKnot[-1]
# Node weight
for j in range(nj):
for i in range(ni):
iLine += 1
f.write('%19.10e, %51dP %6d\n'%(1.0, ib*2+1, iLine))
# Node coordinates
xyz, kLine = read_block_plot3d(lines, kLine, ni, nj, nk)
for k in range(nk):
for j in range(nj):
for i in range(ni):
iLine += 1
f.write('%19.10e,%19.10e,%19.10e,%12dP %6d\n'%(
xyz[i,j,k,0], xyz[i,j,k,1], xyz[i,j,k,2],
ib*2+1, iLine))
# Ending
iLine += 1
f.write('%14.6e,%14.6e,%14.6e,%14.6e;%12dP %6d\n'%(
ximin, ximax, xjmin, xjmax, ib*2+1, iLine))
#* Ending section
f.write('S %6dG %6dD %6dP %6d %40s %6d\n'%(1, 3, 2*num_block, iLine, 'T', 1))
f.close()
[docs]
def idataline(ni: int, nj: int):
'''
Function for `plot3d_to_igs`
'''
i1 = ni+4
i2 = nj+4
i3 = ni*nj
i4 = ni*nj
i5 = 1+1
return i1+i2+i3+i4+i5
[docs]
def knotx(ni: int, n_offset=4) -> np.ndarray:
'''
Function for `plot3d_to_igs`.
Returns [0, 0, 0, 0, ...(ni-n_offset)..., 1.0, 1.0, 1.0, 1.0].
'''
xKnot = np.zeros(ni+n_offset)
for i in range(ni-n_offset+1):
xKnot[i+n_offset] = (i+1.0)/(ni-n_offset+1)
for i in range(n_offset):
xKnot[ni+i] = 1.0
return xKnot
[docs]
def output_plot3d_for_parts(fname: str, *args, scale=1.0) -> None:
'''
Output surfaces of multiple parts in plot3d format.
Parameters
------------
fname : str
file name.
*args : List[List[np.ndarray]]
surfaces of different parts, i.e., part1.surfaces, part2.surfaces, ...
part.surfaces = [[surf_x, surf_y, surf_z], [surf_x, surf_y, surf_z], ...]
scale : float
scaling factor.
Examples
------------
>>> output_plot3d_for_surfaces(fname='plot3d', surf1, surf2, surf3)
'''
n_part = len(args)
Xs = []
Ys = []
Zs = []
for i_part in range(n_part):
part = args[i_part]
for i_surf in range(len(part)):
Xs.append(part[i_surf][0])
Ys.append(part[i_surf][1])
Zs.append(part[i_surf][2])
output_plot3d(Xs, Ys, Zs, fname, scale=scale)