🌊

CAEのためのPython(Nastranファイルの読み書き)

2023/06/22に公開

動機

どうも,CAE専任者として派遣先に行ったらなぜか、C#とPythonをやってるものです。
解析で使うメッシュモデルの部分的な自動作成やポスト処理なんかをやってます。
CAEに携わっている方で同じような作業を何回もやっていてめんどくさいなぁ~と
思っている方へ何か参考になればいいなと考えています。

今回やること

何らかのPreソフト(HyperMeshとかANSA)で作ったNastranファイルを
後で加工しやすいように整理して読み取る、加工し終わったら書き出す
ことをしたいです。
今回は、節点、要素(三角形、四角形のシェル要素のみ)と
PSHELLだけを対象にして作成しています。
材料や境界条件などは、必要に応じて追加していこうと思います。

Nastranファイルの形式

Nastraファイルは下に示すように8文字で1つのデータになっているので
(CSVファイルのように,で区切った形式や節点の座標は16文字表記もあります)
ファイルを一行ずつ読み込み、8文字ごとに分ける必要があります。

BEGIN BULK
GRID           1          559.31 -167.48 1173.52
GRID           2          493.11   52.35  608.07
GRID           3          494.32   49.19  486.43
					・
					・
CTRIA3         4       1   40849   24437   40850
CTRIA3         5       1   24443   40848   15493
CTRIA3         6       1   40850   40848   24443
					・
					・
CQUAD4         1       5       2       1      61      62
CQUAD4         2       5       3       2      62      63
CQUAD4         3       5       4       3      63      64
					・
					・
PSHELL         1       2     1.0       2                       0
PSHELL         2       3     1.0       3                       0
PSHELL         3       4     1.0       4                       0
					・
					・
ENDDATA
  • 節点は、先頭から"GRID"(節点行を表すキーワード)、ID、X座標、Y座標、Z座標
  • 要素は、先頭から"CTRIA3"(三角形シェル)or"CQUAD4"(四角形)、ID、PID(PSHELL ID)、
    それ以降は構成する節点ID
  • PSHELLは、先頭から"PSHELL"(キーワード)、ID、MID(材料ID)、板厚、
    この後4つデータがありますがよくわかっておらず、とりあえずMID、空白、空白、0にしたら
    HyperMeshやLs-PrePostでは開くことができました。
    (詳しい方がいらしたら教えてくださると幸いです。)

プログラムの説明

  • 以下をインポート
from distutils.command.build_scripts import first_line_re
from tempfile import template
import numpy as np
from abc import ABCMeta,abstractmethod
import chardet
  • ファイル処理で汎用的に使うabstractクラスを作成します。
class File_Base(metaclass=ABCMeta) :
    def First_Read_File(self,File):
        with open(File,'rb') as tf:
            en=chardet.detect(tf.readline())['encoding']
        with open(File,'r',encoding=en) as f:
            Liens=f.readlines()
        return [i for i in Liens if i.find('$')==-1 ]
    def Separate_Str_Count_Liens(self,Liens,num=8):
        return [[Line[x:x+num] for x in range(0, len(Line), num)] for Line in Liens]
    def Separate_Liens(self,Liens,Keyword,num):
        return [i for i in Liens if i[:num].find(Keyword)!=-1]  
    @abstractmethod
    def Read_File(self,filename):
        pass
    @abstractmethod
    def Write_File(self,filename,DATA):
        pass
  1. First_Read_File:Nastranファイルを一行ずつList[string]として保存する。
  2. Separate_Liens:keyword(NastranファイルのGRIDとかPSHELL)ごとにListを分離する。
  3. Separate_Str_Count_Liens:8文字ごとに分離してList[List[string]]に変換する。
  • NODE(節点)、ELEMENT(要素)、PRO(PSHELL)クラスを作成して
    DATA_BASEクラスでID(int)とそれぞれの型のdictionaryを作成し読み取ったそれぞれの
    データを加工しやすいように保存できるようにします。
    DATA_BASEクラスには、コピーや中身を空にするメッソドも作っておきました。
class NODE:
    @property
    def CRD(self):
        return self.__CRD
    @CRD.setter
    def CRD(self,value):
        self.__CRD = value if value[0] is float else np.array([float(i) for i in value])   
    @property
    def T(self):
        return self.__T
    @T.setter
    def T(self,value):
        self.__CRD = value if value is float else float(value)
    def __init__(self,CRD_value,T_value):
        self.__CRD = CRD_value if CRD_value[0] is float else np.array([float(i) for i in CRD_value])
        self.__T = T_value if T_value is float else float(T_value)
        
class ELEMENT:
    @property
    def PID(self):
        return self.__PID
    @PID.setter
    def PID(self,value):
        self.__PID = value if value is int else float(value)
    @property
    def NID(self):
        return self.__NID
    @NID.setter
    def NID(self,value):
        self.__NID = value if value[0] is int else [int(i) for i in value]
    @property
    def T(self):
        return self.__T
    @T.setter
    def T(self,value):
        self.__T = value if value is int else float(value)
    def __init__(self,PID_value,NID_value,T_value):
        self.__PID = PID_value if PID_value is int else float(PID_value)
        self.__NID = NID_value if NID_value[0] is int else [int(i) for i in NID_value]
        self.__T = T_value if T_value is int else float(T_value)
                
class PRO:
    @property
    def MID(self):
        return self.__MID
    @MID.setter
    def MID(self,value):
        self.__MID = value if value is int else float(value)
    @property
    def T(self):
        return self.__T
    @T.setter
    def T(self,value):
        self.__T = value if value is float else float(value)  
    def __init__(self,MID_value,T_value):
        self.__MID = MID_value if MID_value is int else int(MID_value)
        self.__T = T_value if T_value is float else float(T_value)
        
class DATA_BASE:
    @property
    def N(self):
        return self.__N
    @N.setter
    def N(self,value):
        if type(value) is dict:
            self.__N= value
    @property
    def EL(self):
        return self.__EL
    @EL.setter
    def EL(self,value):
        if type(value) is dict:
            self.__EL=value
    @property
    def PR(self):
        return self.__PR
    @PR.setter
    def PR(self,value):
        if type(value) is dict:
            self.__PR=value
    def DATA_BASE_Copy(self,DATA):
        self._N=DATA._N.copy()
        self._EL=DATA._EL.copy()
        self._PR=DATA._PR.copy()
    def DATA_BASE_Clear(self):
        self.__N.clear()
        self.__EL.clear()
        self.__PR.clear()
    def __init__(self):
        self.__N={}
        self.__EL={}
        self.__PR={}    
  1. DATA=DATA_BASE()より前でファイルの中身を種類ごとにList[List[string]]を作成します。
  2. DATA=DATA_BASE()より後で1で作成したものをDATA_BASEに整理して保存しています。
class File_NAS(File_Base):
    def Read_File(self,filename): 
        temp=super().First_Read_File(filename)
        N1=super().Separate_Str_Count_Liens(super().Separate_Liens(temp,'GRID',4))
        N2=super().Separate_Str_Count_Liens(super().Separate_Liens(temp,'*',1))
        EL_Tri=super().Separate_Str_Count_Liens(super().Separate_Liens(temp,'CTRIA3',6))
        EL_Quad=super().Separate_Str_Count_Liens(super().Separate_Liens(temp,'CQUAD4',6))
        PR=super().Separate_Str_Count_Liens(super().Separate_Liens(temp,'PSHELL',6))
        
        DATA=DATA_BASE()
        self.NODE_SET_R(N1,N2,DATA)
        self.ELEMENT_SET_R(EL_Tri,EL_Quad,DATA)
        self.PRO_SET_R(PR,DATA)
        return DATA
    def NODE_SET_R(self,temp1,temp2,DATA):
        for i,value in enumerate(temp1):
            if len(temp2)==0:
                DATA.N.setdefault(int(temp1[i][1]),NODE(np.array([temp1[i][3],temp1[i][4],temp1[i][5]]),1))
            else:
                DATA.N.setdefault(int(temp1[i][1]+temp1[i][2]),NODE(np.array([temp1[i][5]+temp1[i][6],temp1[i][7]+temp1[i][8],temp2[i][1]+temp2[i][2]]),1))
    def ELEMENT_SET_R(self,Tri,Quad,DATA):
        for i,value in enumerate(Tri):
            DATA.EL.setdefault(int(Tri[i][1]),ELEMENT(Tri[i][2],[Tri[i][3],Tri[i][4],Tri[i][5]],1))
        for i,value in enumerate(Quad):
            DATA.EL.setdefault(int(Quad[i][1]),ELEMENT(Quad[i][2],[Quad[i][3],Quad[i][4],Quad[i][5],Quad[i][6]],1))
    def PRO_SET_R(self,temp,DATA):
        for i,value in enumerate(temp):
            DATA.PR.setdefault(int(temp[i][1]),PRO(temp[i][2],temp[i][3]))
  • 今回は、節点の座標が16文字で書かれていても対応できるようにしました。
    その場合、節点のフォーマットは以下のようになります。
GRID*             100982                   2.3275686e+03  -5.4023541e+02
*          9.3776306e+02
GRID*             100983                   2.3235437e+03  -5.4043951e+02
*          9.3800928e+02
GRID*             100984                   2.3318833e+03  -5.3508295e+02
*          9.3749921e+02
  • DATA_BASEから必要な値を抜き取りNastranのフォーマットに
    合うようファイルを作成します。
class File_NAS(File_Base):
    def Write_File(self,filename,DATA):
        with open(filename, 'w') as f:
            f.write("BEGIN BULK\n")
            f.writelines(self.NODE_SET_W(DATA))
            f.writelines(self.ELEMENT_SET_W(DATA))
            f.writelines(self.PRO_SET_W(DATA))
            f.write("ENDDATA")
    def NODE_SET_W(self,DATA):
        temp=[]
        for key,value in DATA.N.items():
            temps=["GRID",str(key),""]+list(map(lambda i :str(round(i,2)), value.CRD))
            temp.append(self.Nas_Str_Join(temps))
        return temp
    def ELEMENT_SET_W(self,DATA):
        temp=[]
        for key,value in DATA.EL.items():
            EL_keyword= "CTRIA3" if len(value.NID)==3 else "CQUAD4"
            temp.append(self.Nas_Str_Join([EL_keyword,key,value.PID] + value.NID))
        return temp
    def PRO_SET_W(self,DATA):
        temp=[]
        for key,value in DATA.PR.items():
            temp.append(self.Nas_Str_Join(["PSHELL",key,value.MID,value.T,value.MID,"","","0"]))
        return temp
    def Nas_Str_Join(self,List_Data):
        temp=""
        for i in list(map(lambda i:str(i).rjust(8),List_Data[1:])):
            temp+=i
        S=str(List_Data[0]).ljust(8) + temp +"\n"
        return S
  • 使い方
if __name__ == '__main__':
    test_in="読み込むNastranファイルのパス"
    test_out="書き出すNastranファイルのパス"
    Nastran=File_NAS()
    NAS_DATA=Nastran.Read_File(test_in)
    Nastran.Write_File(test_out,NAS_DATA)

今後

  • 今回は、何もやってないのでメッシュモデルに変化はありませんが、
    モデルの移動やPIDや材料の自動設定などをやっていきたいです。

Discussion