Python 複数のCADファイル(.step)をメッシュファイルへCUIで自動生成する「Gmsh」

'22/05/21更新:雛形コードを可読性のために若干見直し
 本記事では、CADファイルの汎用形式(.step)をPythonライブラリの「Gmsh」を用いてメッシュ分割する雛形コードを載せました。出力するメッシュ形式は、Gmshフォーマット(.msh)と市販のFEMソフトAbaqusフォーマット(.inp)です。WindowsLinux(Ubuntu)環境下で動作確認しています。

■ライブラリのインストール方法

◆pipの場合

pip uninstall gmsh-dev gmsh-sdk gmsh-sdk-git
pip install --upgrade gmsh

下記はその参照リンク先です。

pypi.org

◆Anaconda環境下の場合

conda config --add channels conda-forge
conda install gmsh python-gmsh

下記はその参照リンク先です。

github.com

 

■以降で、本プログラムの実施例を示します

▼ CADファイル(.step)を準備する

 下図は、CADソフト(オープンソースのFreeCAD)で作成した3次元構造の例です。4つの領域ソリッドで構成されたひとつのcomponent  solidです。これを拡張子.stepのCADファイルで出力します。

▼ Gmshによるメッシュ分割

 下図は、本プログラムを実行して、CADファイル(.step)からメッシュファイル(.inp, .msh)を生成した2つの例です。この2つは、メッシュ設定が異なります。

 

■本プログラム

#!/usr/bin/python3
# -*- coding: utf-8 -*-
import os, sys, glob
import shutil
import gmsh
import time


##### パラメータ ここから
# CADファイル(.step)のあるフォルダをリストで抽出
cadfile_list = glob.glob(r'./CAD_stepfiles/*.step')

# 出力先フォルダ'
out_dirpath = './FEM_inpfiles'

# メッシュ条件を辞書で設定(keyがファイル名になる)
my_mesh_method_dict = {
# 1. 辺当たりの節点数を指定したメッシュ
#'transfinite':(20,), 

# 2. 段階メッシュ
'gradual_1':(0.01, 3),
'gradual_2':(0.01, 10),

# 3. 一様メッシュ
#'uniform':(5,)
}

# 要素タイプを2次にする場合:True しない場合:False
is_secondary_element = False

# 領域間の重複する線や面を削除する場合:True しない場合:False
is_fragment = True

# 出来栄えをgmsh GUIを立ち上げて目視確認する(複数ある場合は順番に)
is_check_gmsh_gui = True

##### パラメータ ここまで

measure_time_list = []

# 初期化
'''
if os.path.isdir(out_dirpath): # 保損先フォルダがある場合
    shutil.rmtree(out_dirpath) # 一旦中身ごと削除
    time.sleep(1)
os.mkdir(out_dirpath) # 保存先フォルダ作成
'''

# 上記で定義したメッシュ条件の辞書をループして、メッシュ生成する
for my_mesh_method_key, my_meth_method_value in my_mesh_method_dict.items():
    for cadfile in cadfile_list:
        # ファイルパスからファイル名の取得
        file_name = os.path.basename(cadfile)
        
        # ファイル名から、拡張子なしのファイル名と拡張子の取得
        file_name, extension = os.path.splitext(file_name)
        print("processing " + file_name, my_mesh_method_key)
        
        start = time.time()
        
        ##### 前処理 #####
        
        # オブジェクトの生成
        gmsh.initialize()

        # 進行状況を表示する場合:1
        # Default value: 0
        gmsh.option.setNumber("General.Terminal", 1)

        # ファイル開く
        gmsh.model.occ.importShapes(cadfile)
        gmsh.model.occ.synchronize()
        ents = gmsh.model.getEntities()
        #print("entities", ents)
        
        # フラグメント調整。領域間の界面(面や辺)を連続にする。重複ある場合は取り除く
        if is_fragment:
            try:
                ent3D = gmsh.model.getEntities(3)
                gmsh.model.occ.fragment(ent3D[:1], ent3D[1:],
                                        removeObject=True, removeTool=True)
                gmsh.model.occ.synchronize()
                gmsh.model.setVisibility(ents, True, recursive = True)
                
                #領域名を再定義
                for k, dimtag in enumerate(gmsh.model.getEntities(3)):
                    value = gmsh.model.addPhysicalGroup(3, [dimtag[1]], tag=dimtag[1])
                    gmsh.model.setPhysicalName(3, value, 'volume'+str(value))
                                  
                for k, dimtag in enumerate(gmsh.model.getEntities(2)):
                    value = gmsh.model.addPhysicalGroup(2, [dimtag[1]], tag=dimtag[1])
                    gmsh.model.setPhysicalName(2, value, 'surface'+str(value))
            except ValueError as e: # 領域がひとつのみの構造の場合は次の処理へ
                print(e)
        
        
        ##### メッシュ分割 #####

        # 3Dメッシュアルゴリズム
        # (1: Delaunay, 3: Initial mesh only, 4: Frontal, 7: MMG3D, 9: R-tree, 10: HXT)
        # Default value: 1
        gmsh.option.setNumber("Mesh.Algorithm3D", 1)
        
        # 1. 辺当たりの節点数を指定したメッシュ
        if 'transfinite' in my_mesh_method_key:
            NN = my_meth_method_value[0]
            for c in gmsh.model.getEntities(1):
                    gmsh.model.mesh.setTransfiniteCurve(c[1], NN)
            for s in gmsh.model.getEntities(2):
                gmsh.model.mesh.setTransfiniteSurface(s[1])
                # 四辺形要素にするか。Falseの場合に三角形
                is_rectangle  = False
                if is_rectangle:
                    gmsh.model.mesh.setRecombine(s[0], s[1])
                gmsh.model.mesh.setSmoothing(s[0], s[1], 100)
            for v in gmsh.model.getEntities(3):
                gmsh.option.setNumber("Mesh.Recombine3DLevel", 2)
                gmsh.option.setNumber("Mesh.Recombine3DConformity", 1)
                gmsh.model.mesh.setTransfiniteVolume(v[1])
                
        # 2. 段階メッシュ
        elif 'gradual' in my_mesh_method_key:
            element_size_min = my_meth_method_value[0]
            element_size_max = my_meth_method_value[1]

            options = dict()
            options['Mesh.CharacteristicLengthMin'] = element_size_min
            options['Mesh.CharacteristicLengthMax'] = element_size_max
            #options['Mesh.CharacteristicLengthFromPoints'] = 0
            #options['Mesh.Algorithm'] = 8
            #options['Mesh.CharacteristicLengthExtendFromBoundary'] = 1
            #options['Mesh.Smoothing'] = 5
            #options['Mesh.RecombinationAlgorithm'] = 0
            options['Mesh.RecombineAll'] = 0 # 4面体の場合0, 6面体の場合1
            #options['Geometry.Tolerance'] = 0.01
            for mo, val in iter(options.items()):
                gmsh.option.setNumber(mo, val)
            
        # 3. 一様メッシュ
        elif 'uniform' in my_mesh_method_key:
            element_size = my_meth_method_value[0]
            ent = gmsh.model.getEntities(0)
            #print('entity', ent)
            gmsh.model.mesh.setSize(ent, element_size)

        # メッシュ分割
        #gmsh.model.mesh.generate(1) # 1次元メッシュ
        #gmsh.model.mesh.generate(2) # 2次元メッシュ
        gmsh.model.mesh.generate(3) # 3次元メッシュ
        
        # 2次要素にする場合
        if is_secondary_element:
            gmsh.model.mesh.setOrder(2)


        ###### 後処理 #####
        
        # Gmshに関するメッシュフォーマット
        gmsh.option.setNumber("Mesh.MshFileVersion", 2.2)
        
        # 設定した値をファイル名に使用するために、タプルをアンパック代入で取得
        a, *b = my_meth_method_value
        if b:
            value_str = str(a) + '_' + str(b[0])
        else:
            value_str = str(a)
        
        out_file_path = os.path.join(out_dirpath, 
                                     file_name + '_' 
                                     + 'gmsh_'
                                     + my_mesh_method_key + '_'
                                     + value_str)
        
        # gmshフォーマットで保存
        save_file_path = out_file_path + '.msh'
        gmsh.write(save_file_path)

        # Abaqusフォーマットで保存
        save_file_path = out_file_path + '.inp'
        gmsh.write(save_file_path)
        
        # 出来栄えをgmsh GUIを立ち上げて目視確認する
        if '-nopopup' not in sys.argv and is_check_gmsh_gui:
            gmsh.fltk.run()

        # オブジェクトを閉じる
        gmsh.finalize()
        
        # 処理時間をリストへ格納
        elapsed_time = time.time() - start
        out_file_name = os.path.basename(out_file_path)
        measure_time_list.append((out_file_name, elapsed_time))

# 処理に要した時間をcsvファイルに出力する
with open('processing_time_of_gmsh.csv', 'w', encoding='utf-8') as f:
    for i, mydata in enumerate(measure_time_list, start=1):
        if i == 1:
            f.write(r'No,Name,Time' + '\n')
        buf = ','.join([str(i), mydata[0], str(round(mydata[1], 1))])
        f.write(buf + '\n')

print('gmsh finished')

 Gmshのメッシュオプションは多々あります。しかし、構造によっては、メッシュ生成に失敗することも少なくありません。

 参考までに、「Gmsh」と同様のライブラリ「Netgen」の場合は今のところ百発百中でメッシュできます。リンクは下記です。

hk29.hatenablog.jp

(その他参考URL)

github.com

tutorialmore.com

以上

<広告>