💬

CAEのためのPython6(メッシュ入れ替えツール)

2023/09/26に公開

動機

  • 日頃のAssy作業で一番多いのがベースになるモデルがあって、
    部品の一部を入れ替えていく作業かと思います。
    この作業1つ2つなら大したことないんですが、数が多くなると必要な部品を消して
    しまったり、節点や要素のリナンバーをミスしてモデルを壊してしまうことがあります。
    また、プリソフトでこの作業をやっていると節点と要素だけ消せばいいところをパートやセットノードなどを削除してしまう危険性があります。(私はこれが原因で部品がすり抜けてました)
    やっていることは単純なので、計算結果の前でしまった!!と頭抱えこまないように自動化していきたいと思います。

今回やること

  • ベースになる交換元ファイルと交換するメッシュデータだけのファイルを
    GUIにドラック&ドロップすると動作する。
  • 交換するファイル内のPIDと同じ番号が交換元ファイルにあった場合は交換し、ない場合は
    追加します。
  • 交換ないし追加した節点や要素の番号は、交換元ファイルのそれぞれの末番+1以降に
    リナンバーされます
  • 節点、要素以外は交換元ファイルから変更はしない。

プログラムの説明

  • importするライブラリ等
from distutils.command.build_scripts import first_line_re
from tempfile import template
import numpy as np
from abc import ABCMeta,abstractmethod
import chardet
import copy
from tkinter import messagebox
import collections
import re
import tkinter as tk
from tkinterdnd2 import *
import os
  • Ls-Dyna用のインプット形式に合わせて節点、要素、それ以外に分けて
    加工しやすいようにDATA_BASEクラスに保存する。
    また、保存したデータからLS-Dyna形式のファイルを作成します。
  • ファイル読み書きの基底クラス
class File_Base(metaclass=ABCMeta) :
    def First_Read_File(self,File,Noread_keyword="$"):
        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(Noread_keyword)==-1 ]
    def Separate_Str_Count_Liens(self,Liens,num=8):
        return [[Line[x:x+num].replace("\n","") for x in range(0, len(Line), num) if Line[x:x+num]!="\n"] for Line in Liens]
    def Separate_Liens(self,Liens,Keyword,num):
        return (i for i in Liens if i[:num].find(Keyword)!=-1) 
    
    def Separate_Ranges(self,Liens,KeyFunc,TargetMark="*"):
        temp=[]
        jug=False
        for i in Liens:
            if i[:1]==TargetMark:
                jug=False
            if KeyFunc(i) :
                jug=True
            if jug:
                temp.append(i)
        return temp
    def Str_Join(self,List_Data,LR_Change_num,num,newline=True):
        temp=[str(i) for i in List_Data]
        temp2="\n" if newline else ""
        return "".join([i.ljust(num) for i in temp[:LR_Change_num]] 
                       + [i.rjust(num) for i in temp[LR_Change_num:]]) + temp2   
    @abstractmethod
    def Read_File(self,filename):
        pass
    @abstractmethod
    def Write_File(self,filename,DATA):
        pass
  • 節点クラス CRD:xyz座標
class NODE:
    @property
    def CRD(self):
        return self.__CRD
    @CRD.setter
    def CRD(self,value):
        self.__CRD = value if type(value) is np.array else np.array(value)
    def __init__(self,value):
        self.__CRD = value if type(value) is np.array else np.array(value)   
  • 要素クラス(シェルのみ対応)PID:パートID NID:構成する節点ID
class ELEMENT:
    @property
    def PID(self):
        return self.__PID
    @PID.setter
    def PID(self,value):
        self.__PID = value if type(value) is int else int(value)
    @property
    def NID(self):
        return self.__NID
    @NID.setter
    def NID(self,value):
        self.__NID = value if type(value) is list and type(value[0]) is int else [int(i) for i in value]
    def __init__(self,value):
        self.__PID = value if type(value[0]) is int else int(value[0]) 
        self.__NID = value if type(value[1]) is list and type(value[1][0]) is int else [int(i) for i in value[1][:8]]   
  • 読み取ったデータの保存クラス
class DATA_BASE:
#N:節点のディクショナリ(key:ID,value:NODEクラス)
    @property
    def N(self):
        return self.__N
    @N.setter
    def N(self,value):
        if type(value) is dict:
            self.__N= value
#EL:要素のディクショナリ(key:ID,value:ELMENTクラス)
    @property
    def EL(self):
        return self.__EL
    @EL.setter
    def EL(self,value):
        if type(value) is dict:
            self.__EL=value
    @property
#節点、要素以外の項目を一つの文字として保存
    def Other(self):
        return self.__Ohter
    @Other.setter
    def Ohter(self,value):
        if type(value) is list:
            self.__Ohter=value
    def __init__(self):
        self.__N={}
        self.__EL={}
  • Ls-Dynaファイルの読み書きクラス
class File_key(File_Base):    
    def Read_File(self,filename,DATA): 
        sp=super()
        temp=sp.First_Read_File(filename)
        N=sp.Separate_Str_Count_Liens(sp.Separate_Ranges(temp,lambda i:i[:5]=='*NODE'))
        EL=sp.Separate_Str_Count_Liens(sp.Separate_Ranges(temp,lambda i:i[:14]=='*ELEMENT_SHELL'))
        DATA.Ohter=sp.Separate_Ranges(temp,lambda i:i[:5]!="*NODE" and i[:14]!='*ELEMENT_SHELL' and i[:1]=="*")[:-1] 
        Input_DATAList=[(N[1:],self.NODE_R_Func),(EL[1:],self.ELEMENT_R_Func)]
        
        [self.SET_R(i[0],DATA,i[1]) for i in Input_DATAList]
        return DATA
    def List_Joins(self,temp):
        if len(temp)==0:
            return []
        num=[i for i,j in enumerate(temp) if j[0]=="*" or j==temp[-1]]
        num[-1]=num[-1]+1
        num2=[[num[i],num[i+1]] for i in range(len(num)-1)]
        return [temp[i[0]:i[1]] for i in num2]
    def tryint(self,s):  # 正規表現を使ってintか判断する
        return True if re.fullmatch('[-+]?\d+', s.replace(" ","")) else False
    def SET_R(self,temp1,DATA,Func):
        if temp1!=0:
            for i,value in enumerate(temp1):
                temp= "".join(value[0]).replace("\n","")
                NAME="" if self.tryint(value[0][0]) else temp
                num= 0 if self.tryint(value[0][0]) else 1
                
                Func(value,DATA,NAME,num)
                
    def NODE_R_Func(self,value,DATA,NAME,num):
        DATA.N.setdefault(int(value[0]),NODE(np.array([value[1]+value[2],value[3]+value[4],value[5]+value[6]])))
    def ELEMENT_R_Func(self,value,DATA,NAME,num):
        DATA.EL.setdefault(int(value[0]),ELEMENT([value[1],value[2:]]))
    def Write_File(self, filename, DATA):
        with open(filename, 'w') as f:
            f.writelines(DATA.Other)
            f.writelines(self.NODE_W(DATA))
            f.writelines(self.ELEMENT_W(DATA))
            f.write("*END")
    def NODE_W(self,DATA):
        sp=super()
        return["*NODE\n"] + [str(key).rjust(8) + sp.Str_Join(value.CRD,0,16,False) 
                                   + sp.Str_Join([0,0],0,8) for key,value in DATA.N.items()]
    def ELEMENT_W(self,DATA):
        sp=super()
        return ["*ELEMENT_SHELL\n"] +[sp.Str_Join([key,value.PID] + value.NID,0,8) for key,value in DATA.EL.items()]
    def List_devide_into_small_portions(self,temp,num):
           return [temp[i*num:(i+1)*num] for i in range(int(len(temp)/num) + 1)]
  • GUIに2つのファイルをドラック&ドロップすることがトリガーになるようにしている。
class MyApp(TkinterDnD.Tk):
    def __init__(self):
        super().__init__()
        width = 400
        height = 100
        self.geometry(f'{width}x{height}')
        self.minsize(width, height)
        self.maxsize(width, height)
        self.title(f'メッシュ入れ替えツール')
	# ドラック&ドロップのハンドラの登録
        self.drop_target_register(DND_FILES)
        self.dnd_bind('<<Drop>>', self.Func_jug)
        
        # メインフレームの作成と設置
        frame = tk.Frame(self)
        frame.pack(fill = tk.BOTH, pady=10)
        T1 = tk.Label(frame, text="Baseにするkeyファイルと入れ替える\nメッシュのkeyファイルをドラック&ドロップする", font=("MSゴシック", "10", "bold"))
        T1.pack()
        
    def Func_jug(self,e):
    # ドラック&ドロップされた2つのファイルの内、節点と要素以外の項目がより多く記述されているファイルを交換元と判別する
        FileList=e.data.split(" ")
        if self.JUG_sub(e,".k") and len(FileList)==2:
            DATA1=File_key().Read_File(FileList[0],DATA_BASE())
            DATA2=File_key().Read_File(FileList[1],DATA_BASE())
            M= Mesh_Changer(DATA1,DATA2) if len(DATA1.Other) >len(DATA2.Other) else Mesh_Chager(DATA2,DATA1)  
            M.Mesh_Changer_Run()  
            File_key().Write_File(FileList[0].replace(".k","_mod.k"),M.DATA_Main)
            self.quit()
        else:
            self.quit()
    def JUG_sub(self,e,key):
        #拡張子がすべてkeyと同じが確認する
        return len([os.path.splitext(i)[1] for i in e.data.split(" ") 
                    if os.path.splitext(i)[1]==key])==len(e.data.split(" "))
  • 今回のメインクラス
class Mesh_Changer():
    def __init__(self,DATA1,DATA2):
        self.DATA_Main=DATA1 #交換元ファイルのデータ
        self.DATA_Sub=DATA2  #交換、追加するメッシュファイルのデータ
        
    def Mesh_Changer_Run(self):
        self.Renumber()
        self.Delet_Old_Mesh()
        self.Combine_Mesh()
    def Change_PID_get(self):
    #交換するメッシュファイル内のPIDのリストを作成
        temp=(value.PID for key,value in self.DATA_Sub.EL.items())
        return [key for key,value in collections.Counter(temp).items()]
    def Renumber(self):
    #交換するメッシュファイルの節点、要素を交換元の番号の移行にリナンバーする
        DATA_Main_MaxNid=max(self.DATA_Main.N)+1
        temp=dict([[j,DATA_Main_MaxNid+i] for i,j in enumerate(self.DATA_Sub.N)])
        for i in self.DATA_Sub.EL.values():
            i.NID=[temp[j] for j in i.NID if j!=0] 
        self.DATA_Sub.N=dict([[value,self.DATA_Sub.N[key]] for key ,value in temp.items()])
        DATA_Main_MaxELid=max(self.DATA_Main.EL)+1
        self.DATA_Sub.EL=dict([[DATA_Main_MaxELid+i,j] for i,j in enumerate(self.DATA_Sub.EL.values())])
        
    def Delet_Old_Mesh(self):
    #交換元にある交換すべきPIDを持つ要素と構成する節点を削除する
        A=self.Change_PID_get()
        self.DATA_Main.EL=dict([[key,value] for key,value in self.DATA_Main.EL.items()
                             if A.count(value.PID)==0 ])
        temp=[]
        [temp.extend(value.NID) for key,value in self.DATA_Main.EL.items()]
        temp2=[key for key,value in collections.Counter(temp).items()]
        self.DATA_Main.N=dict([[key,value] for key,value in self.DATA_Main.N.items()
                             if temp2.count(key)>0 ])
    def Combine_Mesh(self):
    #削除対象を削除した交換元とリナンバー済みの交、追加するメッシュデータを合わせる
        self.DATA_Main.N=self.DATA_Main.N | self.DATA_Sub.N
        self.DATA_Main.EL=self.DATA_Main.EL | self.DATA_Sub.EL
  • メイン文
if __name__ == '__main__':
    app = MyApp()
    app.mainloop()

使い方

  • 交換元のLs-Dynaファイルと交換するメッシュファイル(ともに.kである必要があります。)を同時にドラック&ドロップすると交換元ファイル名_mod.kという交換したファイルが作成されます。

今後

  • 今回のプログラムは、節点と要素のみ操作しているので交換する部品にヒストリーノードやセットノードがあった場合は、修正が必要になります。
  • この辺も自動化できたらいいなと思います。

Discussion