DEXCSランチャー v2.5 製作メモ / 5.2



5.2. ポスト処理イメージと実装方針

現時点で、想定しているポスト処理のGUIイメージ(ここまで出来たら良いなぁ)を図56.に示す。

図56.ポスト処理のGUIイメージ

起動すると、postProcessingフォルダ中に収納されている(.vtk)以外の全ファイルがリストアップされ、表示したいファイルのトグルをオンにすると、全カラムリスト(+アルファ)が展開表示される。系列番号/名の欄には、対象ファイル中に記されたコメント行から取得されたテキストが表示され、デフォルトではこれがそのまま凡例名の欄に転記して表示されているが、この欄は編集可能にしておく。乗数欄はデフォルトで[1]つまりそのまま表示され、ここも編集可能にしておき、たとえば[-2]とすれば、-2を掛けた値がプロットされるということである。また横、縦の欄は横軸、縦軸として表示対象を指定するもので、横は1つしか指定できない。縦は複数指定可能である。(+アルファ)というのは、ベクトルデータが存在する場合に、その絶対値を表示できるよう使用する行として追加挿入される仕組みとしておくものである。

下段の4つのボタンの役割については、説明するまでないであろう。

またこれらのイメージが、実際に作り込んで使ってみてという経験を経て、変化していく事は大いに考えられる。したがって、あまりこのイメージには拘泥しないで、先にボタンを押した時に実行される機能を作り込むというか、作れないことには先へ進めないので、そのあたりをつける事を当面の課題としたい。

具体的には、上記の画面状態を設定ファイルとして保存することになるので、実際の具体例で仮の設定ファイルを作成しておく。この設定ファイルを読み込んで、その書式も今の所未定だが、書式の設定も含めて所定のグラフ画面を表示できるようになるかどうかの確認が必要であろう。実際の具体例としては、これまでDEXCS標準チュートリアルでサンプル表示していたプロット例があるので、これらに対してそれぞれ個別の設定ファイルを手作業で作成、これを読み込んで、Plotモジュールを使ったグラフ表示で再現できるようにすれば良さそうだ(第1ステップ)。最低限ここまで出来ていれば、DEXCS2021としてリリースする事には問題無いと思っている。

第2ステップはGUI画面の作成で、postProcessingフォルダ中のデータが意図した通りに表示できるようにすること。これを、DEXCS標準チュートリアル以外の問題でも確認できれば良いであろう。

最後の第3ステップとして、操作ボタンへの関数割り付けということになりそうだが、[Plot]ボタンを押した時の関数には、第1ステップのコードをかなり流用できるであろうし、書式が確定しておれば[Save]や[Load]ボタンを押した時の関数の構築も容易だろうと思っている。

5.3. ステップ1: 設定ファイルに基づく複グラフ作成

完成した第2ステップの複グラフ作成イメージを図59.に示す。

図59. 設定ファイルひ基づく複グラフ作成イメージ

DEXCS標準チュートリアルでは、事前に用意した4つのプロット用設定データ(*.dplt)がsystemフォルダ下に収納されており、どれかを選択すれば相応のプロットサンプルが出力されるというものである。4つのプロットサンプルは、これまでDEXCSチュートリアルで例示してきたサンプルをほとんどそのまま再現するようにした。

設定ファイルはテキストファイルであり、その内容を見れば容易に処理内容を推察できるものと思われる。

実は、当初この書式で実装していって、DEXCS標準チュートリアルにおいては、問題無く動作していたのだが、他の問題に適用してみたところ、大きく2つの問題があることが判明した。すなわち、

  • 異なる横軸系列の混在グラフを描くことができない。
  • 横軸系列で同一点に複数データが存在した時にエラーとなってしまう。

第1項目に関しては、データ系列の指定を各系列に対して個別に指定するようにすれば良いのではないかと思われたのであるが、見かけ上の複グラフは作成できたのであるが、よく見ると縦軸が横軸の期待した点にプロットされない。図64に簡単なプロット例を示すが、本来左側に示すようにプロットされるべきところが、右側のようにプロットされてしまった。右側の上と下では、プロットの系列の指定順序を入れ替えただけである。

図66.

どちらか一方のグラフは正しく表示されているが、もう一方のグラフは横軸が一致していおらず、単に順列でプロットされているかの様相であった。実際のプロットアルゴリズムまで調べた訳ではないが、対応策として、全グラフに共通な横軸系列を作成し、その共通系列に対して、各々のデータを再配置しててやれば良いことがわかった。

図67.

図67.に模式的示しておいたが、黒色のデータ系列と青色のデータ系列が存在した場合に、まず共通の横軸系列

[x1 x1 x2 x3 x2 x3 x4]

を作成する。ここでデータは当然、小さいものから大きくなるように並べられ、図ではx3とx2の大小関係が微妙であるが識別できるものであれば、上のように作成するが、全く同一の場合は重複値として削除しておく。プログラムで書くと以下のようになった。

for k in range(len(Y_File)) :
    postX = process_column_X(X_File[k], X_column[k], X_scaleFactor[k])
    PostsX.append(postX)
    if Y_Vector[k] == "1":
        PlotValue = process_column_vector(Y_File[k], Y_column[k], Y_scaleFactor[k])
    else:
        PlotValue = process_column(Y_File[k], Y_column[k], Y_scaleFactor[k])
    PostsY[Y_Legend[k]] = PlotValue
    k = k + 1

NewPostX=[]
for k in range(len(Y_File)) :
    for i in range(len(PostsX[k])):
        NewPostX.append(PostsX[k][i])
NewPostX = list(set(NewPostX))
NewPostX.sort()

process_column_X()という関数によって、ファイル名と、カラム番号、スケールファクタを指定してデータセットを作成しているが(詳細は後述)、系列ごとに取得したX軸データセットPostsX[k]から、新しく共通X軸データセットNewPostXを作成している(朱字部)。

次に、この横軸データセットを使って、元データの縦軸(Y)データセットを構築しなおす。つまり、

              NewPostX:[x1 x1 x2 x3 x2 x3 x4]

Y : [y1 y2 y3 y4] ⇒ [y1 ## y2 y3 ## ## y4]

Y : [y1 y2 y3]     ⇒ [## y1 ## ## y2 y3 ##]

といった具合である。ここで ## と記した部分には、前後のデータを使って直線補間値をあてはめるというのが普通の考え方であろう。プログラムは、以下のようになった。

for k in range(len(Y_File)) :
    iIns = 0
    preFound = 0
    for j in range(len(NewPostX)):
        found = 0
        imin = -1
        for i in range(len(PostsX[k])):
            if PostsX[k][i] == NewPostX[j]: #新X軸データに対応する元Y軸データが存在
                found = 1
                preFound = 0
                break
            if PostsX[k][i] < NewPostX[j] : 
                imin = i                    # 新X軸データに最も近い元データのカラム番号⇒imin
        if found == 0: #新X軸データに対応する元Y軸データが存在しない場合には、補間データを作成
            iL = imin 
            iH = iL + 1
            try:
                ratio = (NewPostX[j]-PostsX[k][iL])/(PostsX[k][iH]-PostsX[k][iL])
            except:
                ratio = 1
            if iL < 0 :
                insY = None
            elif iH > len(PostsX[k])-1 :
                insY = None
            else:
                insY = PostsY[Y_Legend[k]][iL+iIns-preFound] + (PostsY[Y_Legend[k]][iH+iIns]-PostsY[Y_Legend[k]][iL+iIns-preFound])*ratio

            PostsY[Y_Legend[k]].insert(j,insY)
            preFound = preFound + 1
            iIns = iIns + 1

NewPostXのj番目の点において、相応するYデータが存在しない場合に、その前後(IL/ IH)のデータセットを使って補間値(insY)を計算して、 PostsY[Y_Legend[k]].insert(j,insY) としてYデータを追加している。但し前後のデータセットと記したが、この挿入操作を実行することで、元データの位置情報(IL/IH)と、再構築されたYデータの位置情報がずれてくるので、iIns や、preFoundといったカウンタを使って、その補正を行っている。

また、定義されたデータ範囲外のデータについてはそのまま水平もしくは勾配を延長するやり方も考えられたが、 i試行錯誤で nsY = None とすることで、プロットされなくなることがわかったのでこれを採用した。

あと、process_column_…にて、ポストデータファイルを読み込んで、データセットを作成する方法であるが、3つの類似型がある。基本は、process_column()であり、以下の内容になっている。

def process_column(plotFile,columnNumber,scaleFactor):
   
    f = open(plotFile,"r")
    text = f.readlines()
    f.close()

    postV = []
    for line in text:
        split = line.split()
        if split[0] != "#" :
            try:
                postV.append( float((split[columnNumber].replace('(','')).replace(')','')) * scaleFactor )
            except:
                pass
    return postV

plotFileを読み込んで,注釈行(行頭が#)でなかったら、columnNumber位置の数字に,scaleFactorを掛けてデータセット(postV)に収納している。ベクトルデータなどは、括弧記号が数値と隣り合って表記される場合があるので、これを取り去ることも併せやっている。

process_column_vector(…)は、columnNumberで始まる3つの数字がベクトルデータであるという前提の元、ベクトルの絶対値を返すようにしたものである。

def process_column_vector(plotFile,columnNumber,scaleFactor):
   
    f = open(plotFile,"r")
    text = f.readlines()
    f.close()
    postV = []
    for line in text:
        split = line.split()
        if split[0] != "#" :
            try:
                pX = float((split[columnNumber].replace('(','')).replace(')','')) 
                pY = float((split[columnNumber+1].replace('(','')).replace(')','')) 
                pZ = float((split[columnNumber+2].replace('(','')).replace(')','')) 
                pMag = math.sqrt( pX*pX + pY*pY + pZ*pZ )
                postV.append( pMag * scaleFactor )
            except:
                pass
    return postV

問題は、process_column_X(…)であろう。当初は、process_column(…)を使って、DEXCS標準チュートリアル問題では何ら問題が無かったのであるが、他の問題で遭遇した2番目の問題、つまり同一時間で複数の出力データが存在するケースでエラーになるのを回避する為の方策として、打算的な方法を考えたということである。

def process_column_X(plotFile,columnNumber,scaleFactor):
   
    f = open(plotFile,"r")
    text = f.readlines()
    f.close()
    preX=100000
    epsX=1e-9

    postV = []
    for line in text:
        split = line.split()
        if split[0] != "#" :
            try:
                tempX = float((split[columnNumber].replace('(','')).replace(')','')) * scaleFactor 
                if preX == tempX :
                    postX = postX + epsX
                else:
                    postX = float((split[columnNumber].replace('(','')).replace(')','')) * scaleFactor 
                    preX = postX
                postV.append( postX )
            except:
                pass
    return postV

やっていることは、読み取った値が以前の値(preX)に等しいかどうかを見て、等しかったら微少量(epsX = 1e-9)増やしてやろうというものである。この1e-9や、preX=100000という数字は、このままだとケースによっては不具合が生じる可能性もあるが、当面の処置ということにしておく。

5.4. ステップ2: ポスト処理用インタフェース画面の作成

5.5. ステップ3: ポスト処理用インタフェース機能の実装

前へ 目次 次へ

DEXCSランチャー v2.5 製作メモ / 5

5. Plot ワークベンチを使った post 処理

5.1. 単グラフの自動作成

図54. postProcessingファイルのプロット方法



既に3-1.でログファイルを対象に残渣図を描けているので、まずはこれをベースにプロット対象を、ログファイルでなく、postProcessingフォルダ中の後処理データを対象に単グラフ(横軸に最初の数値カラム、縦軸に2番目の数値カラム)作成用のマクロを作成することとした。何はともあれ、これが出来ないことには、現行のDEXCSランチャーで、JGPを使って作成できている複合グラフの作成も覚束ないことになってしまう。

ほぼ1日間の試行錯誤を経て、図54.に示すような手順で、postProcessingフォルダ中、setフォルダ(vtkファイルが収納されている)を除くすべてのファイルに対して、単にのマウス操作で、プロットしたいファイルを選択するだけで、それなりのプロット図を描けるようになったことを確認した。postProcessingフォルダのファイル内容は、それぞれ異なり、特にデータ系列がたくさんある場合、系列を選択してプロットしたいところであるが、今の所、第1列をX軸に、次に現れる数値列をY軸にプロットするようにしただけである。ファイルの中に記してあるコメント行読み取ってそれぞれの列に相応するコメントをX軸ラベルと凡例の名前として表示するようにしている。

第1列をX軸にするのはよいとして、単純にY軸を第2列としなかったのは、たとえば、functionの、

type        fieldMinMax;

で作成されるポストデータは、

# Field minima and maxima
# Time        	field         	min           	location(min) 	processor     	max           	location(max) 	processor     
1             	p             	0.000000e+00	(1.000000e+00 -5.103853e+00 -1.403853e+00)	1	4.345071e+00	(-6.866991e+00 -3.964603e+00 4.443966e-02)	0
2             	p             	0.000000e+00	(1.000000e+00 -5.103853e+00 -1.403853e+00)	1	9.219603e+02	(-6.867227e+00 -2.440362e+00 -2.634384e-01)	0
3             	p             	-5.074832e+01	(-3.359984e+00 -3.414010e+00 6.110496e-01)	1	1.860776e+02	(-4.972372e+00 -3.691786e+00 1.827246e-01)	0

となっており、第2列が数字でないこともあるからである。

また、type probes: で作成されるデータは、

# Probe 0 (0 -3.5 0.2)
#       Probe             0
#        Time
            1      0.369279
            2       129.815
            3      -6.30889

となっており、この場合は凡例名を上記ロジックでは読み取れないという若干の問題は残されている。

初期残渣を表示するのに使ったマクロは、runPlotWather.pyであったので、これを雛形にrunPlotPost.pyを作成した。このマクロ(プログラム)の基本的な処理の流れは、

  1. プロット対象データの取得
  2. 作画用インスタンス作成
  3. プロット用のデータ抽出処理
  4. 作画インスタンスに抽出データを渡す
  5. 作画インスタンスの更新(refresh)

であり、3-1.で記したように、作画インスタンスを作成するプログラムは自動更新の仕組みを有しているが、ログファイルの更新を監視する仕組みが(今の所)存在しないので機能しない。というか、この機能を残したままプログラムを改変しようとすると、プログラムのデバッグがなかなかに困難であった。そこでここでは、作画インスタンスを作成するモジュールでは自動更新させない仕組みに改変し、更新(処理の流れの第5項目)は、マクロ本体にて実施することにした。また、これは、3-1.で作成した初期残渣表示マクロについても同じ事が言えるので、こちらも同様に変更しておいた。つまり、dexcsCfdResidualPlot.py にて、以下の3行をコメントアウトし、

#self.Timer = QtCore.QTimer()
#self.Timer.timeout.connect(self.refresh)
#self.Timer.start(2000)

一方、呼び出す側の、runPlotWatcher.py では、

def _plotResidual(logFile, niter):
        print("log=",logFile)
        f=open(modelDir+"/"+logFile)
        loglines = f.readlines()
        #f.close()
        process_output(loglines, niter)
        #residualPlot.updateResiduals
        residualPlot.updated = True
        residualPlot.refresh()

緑字部分をコメントアウトして、以下の朱字部分を追加した。なお、これにより、グラフが表示されるまでの待ち時間が少なくなった。

基本的に初期残渣を表示するのに使った dexcsCfdResidualPlot.py を雛形に、dexcsCfdPostPlot.py にて、まずPlotモジュールを使って作画用のインスタンスを作成する。dexcsCfdPostPlot.pyの内容は、以下の通りである。

import FreeCAD
from freecad.plot import Plot

class PostPlot:
    def __init__(self):
        self.fig = Plot.figure(FreeCAD.ActiveDocument.Name + "PostProcessing")

        self.updated = False
        self.postX = []
        self.postY = {}

    def updatePosts(self, labelX, postX, postY):
        self.updated = True
        self.labelX = labelX
        self.postX = postX
        self.postY = postY

    def refresh(self):
        if self.updated:
            self.updated = False
            ax = self.fig.axes
            ax.cla()
            ax.set_title("Simulation postProcessing")
            ax.set_xlabel(self.labelX)
            ax.set_ylabel("postData")

            for k in self.postY:
                if self.postY[k]:
                    x = self.postX
                    y = self.postY[k]
                    ax.plot(x, y, label=k, linewidth=1)

            ax.grid()
            ax.legend(loc='lower right')

            self.fig.canvas.draw()

残渣プロットの場合は、グラフにデータを渡す部分で、

            ax.plot(self.residuals[k], label=k, linewidth=1)

となっていた。この場合、横軸の指定は特になかったということである。無ければ、単純に1から増える自然数で補完されているということであろう。これをステップ数で読み替えれば済んだのであった。ここで考えているポスト処理は、横軸がステップ数以外のもの(たとえばサンプリングデータ)にも対応したいので、横軸を任意に変更したかった。横軸を追加して指定する方法については、試行錯誤の結果、単純に引数を追加してやれば良いということであった。

結局、dexcsCfdResidualPlot.pyで使っていたresidualsという変数を、postYに変更。横軸を追加するに際して、朱字部分が追加されたという見方をしてもらえればよい。また、dexcsCfdResidualPlot.pyでは、対数軸表示に関連して、細々とした設定が随所にあったが、それらの部分は全て削除し、いわばPlotモジュールにデータを渡して、デフォルト設定でグラフを描かせるようにした。有り難いことに、このデフォルト設定で、通常のGNUPLOTに比べれば十分に美しいと思えるのだが、いかがであろう?(図55)

図55. 作画例(デフォルト設定)

一方、これを呼び出す側(runPlotPost.py)も、シンプルになった。メインルーチンは以下の通りである。

(fileName, selectedFilter) = QtGui.QFileDialog.getOpenFileName( None, _("Select a postProcess file"),modelDir + "/postProcessing")

name = os.path.splitext(fileName)[0]

if name != "":

    f = open(fileName,"r")
    lines = f.readlines()
    f.close()
    print("postProcessing=",f.name)

    PlotValue = []

    postPlot = PostPlot()

    _plotPost(lines)

ポスト処理対象のファイルの内容を一気に読みとって、下から2行目で作画用インスタンス作成、最後の一行でデータを渡しているだけである。def _plotPost(postlines)の内容は、

def _plotPost(postlines):
        process_output(postlines)
        postPlot.updated = True
        postPlot.refresh()

データをprocess_output(postlines) 経由でインスタンスに渡すのと、画面インスタンスを更新しているだけ。データを渡す本体は、postProcessingデータをカラム処理して、プロット出力用に、

def process_output(text):
    # 単グラフの自動表示
    # 最初に見つかった数値列をX軸に、次に見つかった数値列をY軸表示    
    for line in text:
        line = line.replace('(','')
        line = line.replace(')','')
    # 最終行で数値カラムを探す
    split = line.split()
    key=[]
    ikey=0
    for index in split:
        if is_num(index):
            key.append(ikey) 
        ikey = ikey + 1

    niter = 0
    postX = []
    #print(key)
    for line in text:
        split = line.split()
        #print(split)
        if split[0] != "#" :
            postX.append(float((split[key[0]].replace('(','')).replace('),','')))
            PlotValue.append(float((split[key[1]].replace('(','')).replace('),','')))
            niter = niter +1
        else:
            #数値行でない行の、最終行を取得しておく
            # これをラベルや凡例にする(の場合が多い)
            splitTitle = split

    #非数値最終行から、X軸ラベルや凡例名を切り出す
    #key[1]列目⇒X軸タイトル
    #key[2]列目⇒凡例名
    #見つからなかったら "unknown"
    try:
        labelX = splitTitle[key[1]]
    except:
        labelX = "unknownX"
    try:
        legend = splitTitle[key[2]]
    except:
        legend = "unknownY"

    if legend == "unknownY":
        try:
            legend = splitTitle[key[1]+1]
        except:
            legend = "unknownY"

    if niter > 1:
        postPlot.updatePosts(labelX, postX, OrderedDict([
            (legend, PlotValue)]))
    else:
        message = (_("there in no data for plotting."))
        ans = QtGui.QMessageBox.critical(None, _("check postProcessing data"), message, QtGui.QMessageBox.Yes)

labelX, postX, OrderedDict([(legend, PlotValue)]という3つのパラメタを作成している。ここで、カラム処理の基本は、行単位で見て先頭に#のある行はコメント行で、そうでない行がデータ行となっているので、データ行はスプリット関数でカラム毎に分割できるのだが、ポスト処理データによっては書式が微妙に異なっているので、実際にプログラミングをする際にいくつか例外処理が必要であった。その他注意点もあって、要点を以下に取り纏めておく。

  • ポスト処理データは基本的に数値列だが、ベクトルデータは括弧付きで、しかも数値との間のスペース無しで表示されるのでスプリット処理しただけでは数値として認識されない。
  • 上記括弧を取り除いたとしても、それが数値なのかどうかを適切に判断できる標準関数(たとえば、isNumなど)は存在しないので、数値に変換できるかどうかを調べる関数(is_num)を作成する必要があった。といっても、公開されているものを借用しただけであるが(以下の関数)。
def is_num(s):
    #文字列が数値に変換できるか判定 https://note.nkmk.me/python-str-num-determine/
    try:
        float(s)
    except :
        return False
    else:
        return True
  • スプリット処理しただけでの状態は文字列で、これを単純にプロット用のデータ系列(postX,postValue)にappendするだけでも、グラフを作成してくれるが、単純に勾配1の直線にしかならない。数値化(float)してappendする必要があった。
  • 横軸データは、ラベル(labelX)と数値列(postX)を別変数として定義しているが、縦軸は辞書配列(OrderedDict)として、変数の一系列として収納している。これは多グラフへの拡張も想定している為である。

なお、本プログラムはこのままでは、ポスト処理データとして意味のある内容が入っているかどうかを確認する程度の用途にしか使えないであろう。最低限でもカラムを指定して表示できるようにする必要はある。また先に記した凡例名の読み取り不全の問題もある。そこまで作り込んでから公開としないで、ある意味中途半端な状態で公開しているのは、まずは可能性はありそうだとの感触が得られたので、とりあえずここまでのノウハウを取り纏めておいた方が良いであろうという点と、当面のリリースあるいは最終ゴールとして、どこまで作り込むかのイメージを明らかにしてから取り組んだ方が良いであろうとの判断があったからである。

 前へ 目次 次へ

DEXCSランチャー v2.5 製作メモ / 4.5



4.5 まとめ

以上で、CfdOFを改変して、DEXCSランチャーの機能の肩代わりと、予定していた機能追加を凡そ実現できたと考えている(次章のpost処理機能は、CfdOFの改変とは直接関係ない)。

ここで改めて、DEXCS標準チュートリアルを題材に改変したdexcsCfdOFの動作を確認し、表示や操作方法の面で問題になるかもしれない項目を(これまでの取り組みで今後の課題とした項目も含めて)取り纏めておくことにした。

4.5.1 動作確認

先に、3.7. 中間まとめで実施したものと比較しながら説明する。ワークベンチを切り替え、[CfdAnalysisis]ボタンをクリックするまでは同じであるが、ここでは、この段階で、

図42

CFDMeshコンテナも併せて追加されるようになっている。これをダブルクリックすれば、改変したメッシュ作成タスク画面(dexcsCFD Mesh)が表示される。

図43

これを一旦閉じて、CFDMeshコンテナを選択した状態にあれば、[メッシュ細分化]ボタンが有効になるので、これをクリックすると、同様に改変したメッシュ細分化のタスク画面(Mesh refinement)が現れる。

図44

オリジナルのCdfOF設定画面に比べると、設定項目が増えたという煩わしさはあるかもしれないが、細分化レベルとセルサイズが連動して動く辺りは解りやすくなったのではないかと思う。追加した項目についても、cfMeshに関する知識の無い人には難しいかもしれないが、何もチェックせずそのままでも使えるので大きな問題はないが、後でもう少し考えることとしたい。

図45

DEXCS標準チュートリアルの場合の推奨設定は、Dexcsを対象とした表面細分化と、regionBoxを対象とした内部領域細分化を指定することになる(図)が、対象オブジェクト毎の区別でなく、細分化方法毎の区別である点には留意したい。すなわち細分化方法というのは、

  • 表面細分化か内部細分化
  • 細分化レベルの違い
  • レイヤ-の有無、またそのパラメタの違い
  • keepCellIntersectingPatchesオプションの有無
  • removeCellsIntersectingPatchオプションの有無

の違いに応じて必要な数だけ追加することになるのであって、オブジェクトを対象に個々に設定する必要はなく、Referencesのリストの中で複数のオブジェクトを指定することが出来る。但し一つのオブジェクトに対して、複数の異なる設定があった場合の結果がどうなるかは、やってみないとわからない(どちらが設定されるかはケースバイケースになると思われる)。

細分化設定が終了したら、メッシュ作成タスク画面に戻る。グローバルオプションや、Scale to Meter といった改変項目はあるが、DEXCS標準チュートリアルの場合はこのまま[Write mesh case]⇒[Run mesher]⇒{Paraview]と押していって、ソルバー実行までの操作は3.7. 中間まとめで実施したのと全く同一の手順で同一の結果になる。

4.5.2 問題点など

メッシュ作成タスク画面

  • コンボビューのタスク画面は縦スクロール可能だが横スクロールができない。改変によって横幅が大きくなった事により、画面サイズの変更等の手間が増える。⇒横幅が大きくならないレイアウトにできれば良い。
  • Visualisation ブロックの、[Load surface mesh][Clear]ボタンの意味不明⇒当面削除
  • 代わりに[checkMesh]でどうか
  • Mesh Parameter ブロックの[Element dimension:],[Mesh utility]⇒当面削除
  • optimiseLayer オプションのmaximum allowed thickness のデフォルト値 0.01 ⇒ 0.05 ⇒(5)

メッシュ作成コンテナ

  • [Characteristic Length Max] ⇒ [Base Mesh Size] ⇒(2)
  • 上記のロジック変更⇒(3)
  • 使用しないまたは不要なパラメタ
    • Case Name
    • Number Of Processes
    • Number Of Treads
    • STLLinear Deflection
    • Cells Between Levels
    • Edge Refinement
    • (Element Dimension)
    • (Mesh Utility)
    • Part

メッシュ細分化タスク画面

  • Boundary Layersにチェックを入れた場合の[Number of layers:]デフォルト値 1⇒ 3 ⇒(6)
  • [keepCells…][removeCells…]を[MoreOption]で一括 ⇒(8)
  • References ⇒ Object ⇒(7)
  • Refinement Parameter (cfMesh) のラベル不要、パラメタも[MoreOption]へ

メッシュ細分化コンテナ

  • snappy Hex… 以下は不要 ⇒(9)

ソルバー

  • Input Case Name ?
  • 並列オプション
    • タスク画面で設定項目なし
    • プロパティで設定はあるが、機能していない
  • Iteration Control / Time Step Control は使用していない

その他全般

  • ワークベンチのアイコン
  • プロパティのい表示される数値の小数点以下桁数の問題
  • CfdOFのオリジナルソース(ファイル名)の改変⇒別名(dexcs….py)⇒(1)
  • testDict_…. ⇒ dexcsCfdDict_… ⇒(4)
  • 日本語辞書

4.5.3 追加のソース改変

前節の問題点に対し、現時点で対応可能であった箇所について、以下に取り纏めておくが、対応の順番は行辺りばったりである点はお断りしておく。

(1) CfdOFのオリジナルソース(ファイル名)の改変⇒別名(dexcs….py)

最終的に、CfdOFのオリジナルソースからの変更点をdiffツールを使って明確にしたくなるであろうから、現時点でCfdOFオリジナルのファイル名と、それに対応するdecxsCfdOFのファイル名との対応表を以下に示しておく。

基本は、オリジナルのコードを少しでも変更したら改名しているが、改名したファイルをインポートするファイルの名前も変更する必要が出てくるので、最終的にはほとんどすべてのファイル名を変更する必要が生じてしまう。さすがにinitGUI.py の名前まで変更してしまうと、(多分)ワークベンチが起動しなくなってしまうので、そこまでは改名しなかったが、そのまま使えているファイルもいくつか存在する。

CfdOFオリジナルdexcsCfdOF
CfdAnalysis.pydexcsCfdAnalysis.py
CfdCaseWriterFoam.pydexcsCfdCaseWriterFoam.py
CfdConsoleProcess.pydexcsCfdConsoleProcess.py
CfdFaceSelectWidget.pyCfdFaceSelectWidget.py
CfdFluidBoundary.py
CfdFluidMaterial.py
CfdInitialiseFlowField.py
CfdMesh.pydexcsCfdMesh.py
CfdMeshRefinement.pydexcsCfdMeshRefinement.py
CfdMeshTools.py
CfdPhysicsSelection.pydexcsCfdMeshTools.py
CfdPreferencePage.pydexcsCfdPreferencePage.py
CfdPreferencePage.uiCfdPreferencePage.ui
CfdResidualPlot.pyCfdResidualPlot.py
CfdRunnableFoam.pydexcsCfdRunnableFoam.py
CfdSolverFoam.pydexcsCfdSolverFoam.py
CfdTools.pydexcsCfdTools.py
CfdZone.py
Init.pyInit.py
InitGui.pyInitGui.py
README.mdREADME.md
TaskPanelCfdFluidBoundary.ui
TaskPanelCfdFluidProperties.ui
TaskPanelCfdInitialiseInternalField.ui
TaskPanelCfdListOfFaces.uiTaskPanelCfdListOfFaces.ui
TaskPanelCfdMesh.uidexcsTaskPanelCfdMesh.ui
TaskPanelCfdMeshRefinement.uidexcsTaskPanelCfdMeshRefinement.ui
TaskPanelCfdSolverControl.uiTaskPanelCfdSolverControl.ui
TaskPanelCfdZone.ui
TaskPanelPhysics.ui
TemplateBuilder.pyTemplateBuilder.py
TestCfd.py
WindowsRunWrapper.pyWindowsRunWrapper.py
_TaskPanelCfdFluidBoundary.py
_TaskPanelCfdFluidProperties.py
_TaskPanelCfdInitialiseInternalFlowField.py
_TaskPanelCfdMesh.py_dexcsTaskPanelCfdMesh.py
_TaskPanelCfdMeshRefinement.py_dexcsTaskPanelCfdMeshRefinement.py
_TaskPanelCfdPhysicsSelection.py
_TaskPanelCfdSolverControl.py_dexcsTaskPanelCfdSolverControl.py
_TaskPanelCfdZone.py
metadata.txtmetadata.txt
GuiGui
data/CfdFluidMaterialProperties
data/defaults
data/defaultsMesh
data/dexcs
data/dexcsMesh
testFiles
dexcsCfMeshTools.py
表 CfdOFオリジナルソースとdexcsCfdOF でのファイル名対応表

右欄黒字以外のファイルは、左欄のオリジナルファイルをベースに改変が施されている。また最下行の、dexcsCfMeshTools.py は、DEXCSマクロのオリジナルコードを改変したものであり、その上の緑字フォルダ内に収録されたファイルは、オリジナルのdata/defaults, data/defaultsMeshに存在したparaViewの立ち上げ用スクリプトだけをフォルダ名を変更して追加したものである。

(2) CharacteristicLengthMax ⇒ BaseCellSize

基本的にテキスト検索して、置き換えるだけである。

  • _dexcsTaskPanelCfdMesh.py
def load(self):
    """ Fills the widgets """
    setQuantity(self.form.if_max, self.mesh_obj.CharacteristicLengthMax)
def store(self):
    FreeCADGui.doCommand("FreeCAD.ActiveDocument.{}.MeshUtility "
                         "= '{}'".format(self.mesh_obj.Name, self.form.cb_utility.currentText()))
    FreeCADGui.doCommand("\nFreeCAD.ActiveDocument.{}.CharacteristicLengthMax "
                         "= '{}'".format(self.mesh_obj.Name, getQuantity(self.form.if_max)))
  • _dexcsTaskPanelCfdMeshRefinement.py
(L73)    self.baseMeshSize = Units.Quantity(self.mesh_obj.CharacteristicLengthMax).Value
(L109)   self.mesh_obj.CharacteristicLengthMax = float(cellLength) * 2**(self.form.if_reflevel.value())
  • dexcsCfdMesh.py
(L138) addObjectProperty(obj, "CharacteristicLengthMax", "0 m", "App::PropertyLength", "Mesh Parameters",
                          "Max mesh element size (0.0 = infinity)")
  • dexcsCfdMeshTools.py
(L54)       self.clmax = Units.Quantity(self.mesh_obj.CharacteristicLengthMax).Value
 (L86)      self.mesh_obj.CharacteristicLengthMax = sumOf3Edges / 60.0       
  • dexcsCfMeshTools.py
(L1694)  testDict_maxCellSize = self.mesh_obj.CharacteristicLengthMax * self.mesh_obj.ScaleToMeter

(3) BaseMeshSize のロジック変更

前項において、dexcsCfdMeshTools.pyにてBaseCellSizeを計算していることが改めてわかったので、ついでにこのロジックも変更した。

#self.mesh_obj.BaseCellSize = sumOf3Edges / 60.0       
self.mesh_obj.BaseCellSize = ((xmax-xmin)*(ymax-ymin)*(zmax-zmin)/6000) ** (1/3)       

旧ロジックもコメント行として残しておくことにした。6000という数字は、Dexcsの標準チュートリアル問題にて、旧ロジックと大体同じ値になるように設定したものである。

(4) testDict_…. ⇒ dexcsCfdDict_…

前々項において、testDict_…という変数名を使用している箇所に辿り着いているので、前項と同様、ついでにこれも修正しておく。

(L1693)        
#testDict = True
testDict_maxCellSize = self.mesh_obj.BaseCellSize * self.mesh_obj.ScaleToMeter
testDict_minCellSize = Model.EMPTY_STR
testDict_untangleLayerCHKOption = 0
testDict_optimiseLayerCHKOption = self.mesh_obj.optimiseLayer
testDict_opt_nSmoothNormals= str(self.mesh_obj.opt_nSmoothNormals)
testDict_opt_maxNumIterations= str(self.mesh_obj.opt_maxNumIterations)
testDict_opt_featureSizeFactor= str(self.mesh_obj.opt_featureSizeFactor)
if self.mesh_obj.opt_reCalculateNormals==1:
    testDict_opt_reCalculateNormalsCHKOption = "1"
else:
    testDict_opt_reCalculateNormalsCHKOption = "0"
testDict_opt_relThicknessTol= str(self.mesh_obj.opt_relThicknessTol)
testDict_keepCellsIntersectingBoundaryCHKOption = self.mesh_obj.keepCellsIntersectingBoundary

なお、L1693をコメント行に変更したのは、オリジナルのDEXCSマクロにおいて、細分化指定パラメタの数字をCellSize / RefLevelのどちらで解釈させるかに応じて、隠しパラメタ的にmeshDict中に出力していた箇所(下記)

(L1714)
#if self.viewControl.get_refinementOption() == 1 :
#if (testDict) :
#    strings = ['//CellSize\n']
#else:
#    strings = ['//RefLevel\n']
#meshDict.writelines(strings)        

があった為、これまでは、self.viewControl.を使わずにこの部分を整合させようとして暫定的に使用していたものであるが、今後は必要無しと判断し、上記ブロックも併せてコメントアウトした(最終的には削除の予定)。

(L1733)        'maxCellSize\t' + str(testDict_maxCellSize) + ';\n'
(L1741)
minCellSizeValue = testDict_minCellSize
if str(minCellSizeValue) != Model.EMPTY_STR:
    meshDict.write('minCellSize\t' + str(minCellSizeValue) + ';\n')
else:	
    meshDict.write('//minCellSize\t' + ';\n')

FmsFileName = os.path.basename(self.fmsFileName)

#if self.viewControl.get_untangleLayerCHKOption() == 1 :
if testDict_untangleLayerCHKOption == 1 :
    untangleLayerString = '\tuntangleLayers    0; // \n'
else :
    untangleLayerString = '\t// untangleLayers    0; // \n'

#if self.viewControl.get_optimiseLayerCHKOption() == 1 :
if testDict_optimiseLayerCHKOption == 1 :
(L1800)
'  \t\tnSmoothNormals\t' + testDict_opt_nSmoothNormals + ';\n'
'\t\n'
'  \t\t// maximum number of iterations\n'
'  \t\t// of the whole procedure (optional)\n'
#'  \t\tmaxNumIterations\t5;\n'
'  \t\tmaxNumIterations\t' + testDict_opt_maxNumIterations + ';\n'
'\t\n'
'  \t\t// ratio between the maximum layer thickness\n'
'  \t\t// and the estimated feature size (optional)\n'
#'  \t\tfeatureSizeFactor\t0.4;\n'
'  \t\tfeatureSizeFactor\t' + testDict_opt_featureSizeFactor + ';\n'
'\t\n'
'  \t\t// activale 1 or deactivate 0 calculation of normal\n'
'  \t\t// (optional)\n'
#'  \t\treCalculateNormals\t1;\n'
'  \t\treCalculateNormals\t' + testDict_opt_reCalculateNormalsCHKOption + ';\n'
'\t\n'
'  \t\t// maximum allowed thickness variation of thickness\n'
'  \t\t// between two neighbouring points, devided by\n'
'  \t\t// the distance between the points (optional)\n'
#'  \t\trelThicknessTol\t0.01;\n'
'  \t\trelThicknessTol\t' + testDict_opt_relThicknessTol + ';\n'
(L1931)
if testDict_keepCellsIntersectingBoundaryCHKOption == 1 :

(5) optimiseLayer オプションのmaximum allowed thickness のデフォルト値

dexcsCfdMesh.py

(L131) 
addObjectProperty(obj, "opt_relThicknessTol", 0.05, "App::PropertyFloat", "Mesh Parameters",
                  "maximum allowed thickness variation of thickness between two neighbouring points, devided by the distance between the points")
 

(6) Boundary Layersにチェックを入れた場合の[Number of layers:]デフォルト値

_dexcsTaskPanelCfdMeshRefinement.py の、def updateUI(self)において、以下追加

if self.form.check_boundlayer.isChecked():
    if self.form.if_numlayer.value()==1:
        self.form.if_numlayer.setValue(3)

また、def load(self) において、

if (self.obj.KeepCell == True) or (self.obj.RemoveCell == True): 
    self.form.check_moreoption.setChecked(self.obj.KeepCell)

(7) References ⇒ Object

dexcsTaskPanelCfdMeshRefinement.ui 中の下記2行の表示ラベルだけを変更。

(L527)
(L577)
         <string>Objects</string>

なお、このラベルは、

    <widget class="QFrame" name="ReferencesFrame">

の中で記述されているので、ラベル名を変更したら、このwidget名も変更した方が、プログラムを理解するには解り易いだろうと考えられる。しかしここまで変更すると、これに関連した箇所も数10箇所変更する必要がありそうだった。これをやったとしても、ユーザー目線からは見えない部分の変更でしかない。したがって、関連箇所は変更していない。

(8) [keepCells…][removeCells…]を[MoreOption]で一括

More Option のチェックボックスを追加したので、この状態変化

self.form.check_boundlayer.stateChanged.connect(self.updateUI)
self.form.check_moreoption.stateChanged.connect(self.updateUI)

def updateUI(self) では、

if self.form.check_moreoption.isChecked():
    self.form.moreoption_frame.setVisible(True)
else:
    self.form.moreoption_frame.setVisible(False)
    self.form.check_keepCells.setChecked(False)
    self.form.check_removeCells.setChecked(False)

また

(9) メッシュ細分化コンテナのsnappy Hex…は不要(邪魔)

将来的には、snappyHexにも対応できるようになるかもしれないので、当面は削除するでなく、コメントアウト等して単に表示させないようにしたい。

本プロパティは、その定義部をコメントアウトしたところ、

# snappy:
#addObjectProperty(obj, "RegionEdgeRefinement", 1, "App::PropertyFloat", "snappyHexMesh",
#                  "Relative edge (feature) refinement")

だけでなく、def load(self) において、これを参照している箇所があり、エラーとなるので、これもコメントアウトする必要があった。

#self.form.if_edgerefinement.setValue(self.obj.RegionEdgeRefinement)

def accept(self)

#FreeCADGui.doCommand("FreeCAD.ActiveDocument.{}.RegionEdgeRefinement "
#                     "= {}".format(self.obj.Name, self.form.if_edgerefinement.value()))

他にも、参照している箇所は存在するが、現状想定される使用範囲の中で参照されることの無さそうな部分は、将来的な拡張の可能性を考えて、そのまま残すこととしている。

4.5.4 タスク画面とプロパティコンテナの再構築整理

ここまでタスク画面を変更するのに、インクルードされる拡張子が.uiのファイルを、エディタで直接変更してきたが、ファイルマネージャ上で、このファイルをダブルクリックすれば、Qt Desiner というGUIツールが立ち上がることがわかった(図)。

図46. Qt Designer

これを使って、

  • ボタンやテキストなどのウィジェットの位置や表示名を変更して保存すれば、FreeCAD上で何の問題もなく反映される。
  • 新たにウィジェットを追加しても、FreeCAD上のタスク画面には問題なく反映される。
  • ウィジェットを削除する場合は、objectNameの名前を覚えておく。削除したことで問題があるようなら、FreeCAD上のタスク画面操作をした際にそのオブジェクトに関するエラーが表示される。単に未定義エラーであればプログラムの該当箇所をコメントアウトするなりの対処ができる。
  • これら一連の操作をするに際して、.uiファイルの実体については、何ら参照や編集の必要は生じなかった。

ということがわかったので、以降は、これを使ってタスク画面を再構築した。

(1) タスク画面の再構築

以下に、再構築前後のタスク画面を比較表示しておくが、改変後の図中で、赤枠で括った部分は、ウィジェットを追加したというだけで、この時点で機能は実装していない。

図47. メッシュ作成タスク画面の再構築
図48. メッシュ細分化タスク画面の再構築
図49. ソルバー実行タスク画面の再構築

(2) プロパティリストの再構築

コンテナを選択した時にプロパティリストに表示される項目については、それが定義されるコード箇所、すなわち

  • メッシュコンテナ dexcsCfdMesh.py の、class _CfMesh / def initProperties() セクション
  • メッシュ細分化コンテナ dexcsCfdMeshRefinement.py の、class _CfMeshRefinement / def initProperties() セクション
  • ソルバー実行コンテナdexcsCfdSolverFoam.py の、class _CfdSolverFoam / def __init__() セクション

において、表示させたくないプロパティに相当する部分を一旦コメントアウトする。FreeCADで動作確認。エラー無く実行可能、もしくは削除したことにより、エラーが表示される場合に、それが単に被参照エラーで、その参照部分をコメントアウトするだけで、プログラム本体の実行に支障が来たすことがないようであれば、コメントアウトによる削除作業完了とした。

削除することによる影響が多くの箇所に及ぶものは、元に戻している。

図50. メッシュ作成コンテナ
図51. メッシュ細分化コンテナ
図52. ソルバー実行コンテナ

なお、以上の再構築によって、dexcsCfdOFの動作に支障が無かったことは確認済みであるが、DEXCS標準チュートリアル問題での使い方でしか確認していないという点もお断りしておく。

以上で、4.5.2.で取り纏めた問題点のうちの表示上の問題の大半は解決したと考えているが、残された課題として、プロパティの値が空欄になっている箇所がある点は取り上げられるだろう。これに関しては、先に述べたように、これを削除すると、プログラム全体の多くの箇所に修正が必要になることがあげられるが、一方、CfdOF本来の使い方でなく、dexcsCfdOFとしての使い方を考えると、何らかの情報表示箇所として流用できそうな面もあった。⇒今後の課題としたい。

4.5.5 追加ウィジェットの機能拡張

前項で追加したウィジェットは大きく分けて2点。

  • メッシュ作成タスク画面におけるCheckMesh ボタン
  • ソルバータスク画面における、並列計算パラメタ

それぞれに必要な機能は説明するまでもないと思われ、これらを実装していくが、その前に後者についてはタスク画面で設定した値とコンテナ上で表示されるプロパティ値を一致させる仕組みが必要である。まずこれらのパラメタに相応させるプロパティの名前であるが、これは、dexcsCfdSolverFoam.py の、class _CfdSolverFoam / def __init__() セクションにおいて、

addObjectProperty(obj, "ParallelCores", 2, "App::PropertyInteger", "Solver",
                  "Number of cores on which to run parallel analysis")
known_method = ['scotch','simple','hierachical','metis','manual']
addObjectProperty(obj, "ParallelMethod", known_method, "App::PropertyEnumeration", "Solver",
                  "Method on which to run parallel analysis")

と定義した。ここに、ParallelCoresは、CfdOFオリジナルで定義されていたものを流用し、ParallelMethodは新規に追加したものである。ソルバータスク画面の起動時には、_dexcsTaskPanelCfdSolverControl.py の、class _dexcsTaskPanelCfdSolverControl / def __init__() セクションにおいて、上記プロパティと以下のように関連付けられて、画面が立ち上がる。

self.form.if_ncpu.setValue(self.solver_object.ParallelCores) 
known_method = ['simple','hierachical','scotch','metis','manual']
self.form.cb_method.addItems(known_method)
index_method = self.form.cb_method.findText(self.solver_object.ParallelMethod)
self.form.cb_method.setCurrentIndex(index_method)

また、ソルバータスク画面で、write mesh ボタンを押すと、その時点で設定されていた値が、def UpdateUI(self)によって、プロパティの値として、

FreeCADGui.doCommand("FreeCAD.ActiveDocument.{}.ParallelMethod "
                     "= '{}'".format(self.solver_object.Name, self.form.cb_method.currentText()))
FreeCADGui.doCommand("\nFreeCAD.ActiveDocument.{}.ParallelCores "
                             "= {}".format(self.solver_object.Name, self.form.if_ncpu.value()))

として保存されるようにした。以上で、ソルバータスク画面上で新たに追加したパラメタとコンテナのプロパティ

  • if_ncpu / ParrallelCores
  • cb_method / ParallelMethod

の値が連動するようにはなっているが、プログラム的には2点ほど問題を残している箇所があることをお断りしておく。つまり、known_method を2箇所で定義しており、本来はどちらか一方で定義しておいて、他方は定義済みのそれを参照する形にしたかったが、どちらが先に定義されるのか、また他方の参照方法を調べることが必要で、その手間を惜しんで、便宜的にそのままの状態になっている。

また、もう1点は、[Write] ボタンを押さないとプロパティに反映されないということである。これも本来は、タスク画面上で変更し、タスク画面を閉じたらプロパティに反映されるようにしたかったが、そうなっていない。これは、タスク画面を閉じる時に発生するイベントを調べきれていない為で、これがわかれば、そこに上記コードを配置するだけであるという点もまたお断りしておく。

以上、とりあえず、GUI上の動作としては問題ないレベルになったので、追加ウィジェットに対する機能強化をコーディングする。

(1) checkMesh ボタン

このボタンを押したら、OpenFOAMのcheckMesh コマンドを実行し、そのログを表示できるようにすれば良いであろう。ただ、メッシュが存在しない場合には、ボタンを押せない状態にしておくのが望ましい。これは左隣の[Paraview]ボタンと同じなので、同様にコーディングできる。

_dexcsTaskPanelCfdMesh.py class _TaskPanelCfdMesh / def __init__(…)

self.form.pb_paraview.clicked.connect(self.openParaview)
self.form.pb_checkmesh.clicked.connect(self.runCheckMesh)

self.form.pb_paraview.setEnabled(False)
self.form.pb_checkmesh.setEnabled(False)

def updateUI(self)

self.form.pb_paraview.setEnabled(os.path.exists(os.path.join(case_path, "pv.foam")))
self.form.pb_checkmesh.setEnabled(os.path.exists(os.path.join(case_path, "pv.foam")))

def runMesh(self):

self.form.pb_paraview.setEnabled(False)
self.form.pb_checkmesh.setEnabled(False)

def meshFinished(…)

if exit_code == 0:
    ......
    self.form.pb_paraview.setEnabled(True)
    self.form.pb_checkmesh.setEnabled(True)
    .....
else:
    .....
    self.form.pb_paraview.setEnabled(False)
    self.form.pb_checkmesh.setEnabled(False)

最後に、def runCheckMesh(self)として、def runMesh(self) を雛形としてコピーして以下のように書き換えた。

def runCheckMesh(self):
    self.Start = time.time()
    cart_mesh = self.cart_mesh
    try:
        QApplication.setOverrideCursor(Qt.WaitCursor)
        self.consoleMessage("Running {} ...".format(self.mesh_obj.MeshUtility))
        cart_mesh.error = False
        cmd = dexcsCfdTools.makeRunCommand('./Allcheck', cart_mesh.meshCaseDir, source_env=False)
        FreeCAD.Console.PrintMessage("Executing: " + ' '.join(cmd) + "\n")
        env_vars = dexcsCfdTools.getRunEnvironment()
        self.mesh_process.start(cmd, env_vars=env_vars)
        if self.mesh_process.waitForStarted():
            self.form.pb_run_mesh.setEnabled(False)  # Prevent user running a second instance
            self.form.pb_stop_mesh.setEnabled(True)
            self.form.pb_paraview.setEnabled(False)
            self.form.pb_checkmesh.setEnabled(False)
            #self.form.pb_load_mesh.setEnabled(False)
            self.consoleMessage("Mesher started")
        else:
            self.consoleMessage("Error starting meshing process", "#FF0000")
            cart_mesh.error = True
    except Exception as ex:
        self.consoleMessage("Error " + type(e).__name__ + ": " + str(ex), '#FF0000')
        raise
    finally:
        QApplication.restoreOverrideCursor()

書き換えた部分のAllcheckに相当する部分のスクリプトがないと動かないのであるが、これはAllmeshスクリプトを作成している箇所で、同時に作成しておけば良いであろう。すなわち、dexcsCfdMeshTools.py のdef perform(self, CaseFilePath):セクションの最後辺りに、Allmeshを作成している箇所があるので、途中からまるごとコピーして、以下改変追加した。

        solverSet = "checkMesh | tee checkMesh.log\n"
        sleep = "sleep 2\n"
        cont = title + envSet + solverSet + sleep
        f=open("./Allcheck","w")
        f.write(cont)
        f.close()
        #実行権付与
        os.system("chmod a+x Allcheck")

以上、本改変(checkMesh機能の組み込み)は、ほとんどコピペ作業だけで完成することが出来た。

(2) 並列計算パラメタ

並列計算については、並列数に応じてソルバーの実行コマンドを変更する設定を追加する点と、分割方法に応じてdecomposeParDict ファイルを変更する必要がある。

前者については、実行用のAllrunスクリプトを作成している箇所(dexcsCfdCaseWriterFoam.py の、def_writeAllrun(…)セクション)で、これまで、プログラムの実行本体として、

 solverSet = solver + " | tee solve.log"

としていた箇所を、以下のように変更

self.ParallelCores = self.solver_obj.ParallelCores
#print(self.ParallelCores)
if self.ParallelCores == 1:

    solverSet = solver + " | tee solve.log"
else:
    solverSet = "decomposePar | tee decomposePar.log\n"
    solverSet = solverSet + "mpirun -np " + str(self.ParallelCores) + " " + solver + " -parallel | tee solve.log"

して、対処できた。但し、プロセッサ数の変更は、前項で記したように、Writeボタンを押した後のタイミングで、しかもスクリプトを作成後のタイミングになっていたので、_dexcsTaskPanelCfdSolverControl / def updateUI(self)セクションで追加した2行を、def write_input_file_handler_dexcs(self):において、実際にAllrunスクリプトが生成される前(冒頭)にも追加した。

並列分割方法の違いは、system/decomposeParDict ファイルを書き換えることになるが、前述のAllrunスクリプトを作成した後に、事前に .FreeCAD/Mod/CfdOF/data/dexcsフォルダ下に収納したファイルの書き換え処理を行っている箇所があるので、ここで併せて実行すればよいだろう。

つまり雛形ファイルとしては、 .FreeCAD/Mod/CfdOF/data/dexcs/system フォルダ下に、以下の内容でdecomposeParDictファイルを追加収納。

FoamFile
{
    version     2.0;
    format      ascii;
    class       dictionary;
    object      decomposeParDict;
}
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //

numberOfSubdomains  %(solver/ParallelCores%);

method              %(solver/ParallelMethod%);


// ************************************************************************* //

settings[]パラメタについては、以下追加するだけである。

settings['solver']['ParallelCores'] = self.solver_obj.ParallelCores
settings['solver']['ParallelMethod'] = self.solver_obj.ParallelMethod

なお、分割方法によって、サブパラメタの設定も必要になる。現時点ではまだサブパラメタを設定するためのGUIを用意できていないので、分割数の設定だけで済む方法(scotch または metis)でしか通用しないものであるという点はお断りしておく。

4.5.6. ソルバー実行タスク画面の再々構築

並列計算が実装できてみると、もう一つ不足しているものがあるのに気付いた。結果の再構築(reconstructPar)である。さらに並列計算用の2つのパラメタは、常時表示させるものでなく、並列計算するかどうかのチェックボックスを介して、チェックされた時のみ表示させた方が良いであろうと思われた(図53)。

図53. ソルバー実行タスク画面の再々構築イメージ

このタスク画面を作成するに際しては、Parallelのチェックボックス如何で、表示を切り替えるパーツをQtFrameというウィジェットの中に収納しておく必要があると思われ、当初オリジナルのウィジェットをベースに改変を試みたが、何故か新たにQtFrameを追加することができなかった。そこでやむなく、新たにスクラッチでウィジェットを作成。最初にQtFrameを追加しておいてから、オリジナルのウィジェットから、すべてのパーツをコピペするという作業にて完成することができた。

追加したチェックボックス(オブジェクト名はcheck_parallel)については、メッシュ細分化タスク画面でレイヤーチェックボックスと同様にコーディング、つまり_dexcsTaskPanelCfdSolverControl.py の、class _dexcsTaskPanelCfdSolverControl / def __init__(…)において、

self.form.check_parallel.stateChanged.connect(self.updateUI)
self.form.check_parallel.setChecked(self.solver_object.ParallelCores > 1)

同じく、def updateUI(self) において

self.form.parallel_frame.setVisible(self.form.check_parallel.isChecked())
if self.form.check_parallel.isChecked():
    if self.form.if_ncpu.value()==1:
        self.form.if_ncpu.setValue(2)
else:
    FreeCADGui.doCommand("\nFreeCAD.ActiveDocument.{}.ParallelCores "
                     "= {}".format(self.solver_object.Name, 1))

1行目で、追加したフレーム(parallel_frame)の表示/非表示を制御している。

なお、この改変に伴って、デフォルトでは並列計算しない(ParallelCores=1)としたいので、その初期値を変更した(dexcsCfdSolverFoam.py class _CfdSolverFoam / def __init__(…))。

addObjectProperty(obj, "ParallelCores", 1, "App::PropertyInteger", "Solver",
                  "Number of cores on which to run parallel analysis")

また、新たに追加した[reconstructPar]ボタンについて、その基本的な機能は、[Paraview]ボタンと同じで、実行内容を変更するだけである。これは、先に[checkMesh]ボタンを追加した際の_dexcsTaskPanelCfdMesh.pyにおける改変と同様の内容を、_dexcsTaskPanelCfdSolverControl.py において実施してやればよいということである。つまり、class _dexcsTaskPanelCfdSolverControl / def __init__(…)において、

self.form.pb_paraview.clicked.connect(self.openParaview)
self.form.pb_reconstruct.clicked.connect(self.runReconstruct)

def updateUI(self) において、

self.form.pb_paraview.setEnabled(os.path.exists(os.path.join(solverDirectory, "pv.foam")))
self.form.pb_reconstruct.setEnabled(os.path.exists(os.path.join(solverDirectory, "Allreconst")))

def write_input_file_handler_dexcs(self)

self.form.pb_paraview.setEnabled(False)
self.form.pb_reconstruct.setEnabled(False)

def runSolverProcess(self):

self.form.pb_paraview.setEnabled(True)
self.form.pb_reconstruct.setEnabled(True)

といった具合に、pb_paraviewについて記述された行をその下にコピペして、pb_paraviewを、pb_reconstructに、その他、手続き名など相応に変更。新たに追加した手続きブロックを、類似ブロックからコピペ改変することになる。

クリックした時の飛び先(self.runReconstruct)については、def runReconstruct(self):を新規作成する必要があるが、これはには、類似の関数として、def runSolverProcess(self):があるので、これをコピペして、以下朱字部分を改変。

def runReconstruct(self):
    self.Start = time.time()

    solverDirectory = os.path.join(self.working_dir, self.solver_object.InputCaseName)
    solverDirectory = os.path.abspath(solverDirectory)
    cmd = self.solver_runner.get_reconst_cmd(solverDirectory)
    FreeCAD.Console.PrintMessage(' '.join(cmd) + '\n')
    envVars = self.solver_runner.getRunEnvironment()
    QApplication.setOverrideCursor(Qt.WaitCursor)
    self.solver_run_process.start(cmd, env_vars=envVars)
    if self.solver_run_process.waitForStarted():
        # Setting solve button to inactive to ensure that two instances of the same simulation aren't started
        # simultaneously
        self.form.pb_write_inp.setEnabled(False)
        self.form.pb_run_solver.setEnabled(False)
        self.form.terminateSolver.setEnabled(True)
        self.form.pb_paraview.setEnabled(True)
        self.form.pb_reconstruct.setEnabled(True)
        self.consoleMessage("Solver started")
    else:
        self.consoleMessage("Error starting solver")
    QApplication.restoreOverrideCursor()

ここで変更した.get_reconst_cmdは、オリジナルでは get_solver_cmd となっていたもので、これは、dexcsCfdRunnableFoam.py にて、定義されている。そこでこの部分をコピペして、get_reconst_cmd を新たに追加してやればよい。

def get_reconst_cmd(self, case_dir):

    # Environment is sourced in run script, so no need to include in run command
    cmd = dexcsCfdTools.makeRunCommand('./Allreconst', case_dir, source_env=False)
    FreeCAD.Console.PrintMessage("Solver run command: " + ' '.join(cmd) + "\n")
    return cmd

また、Allreconstという用のスクリプトファイルが必要になるが、これはAllrunスクリプトを作成している箇所で、同様に作成すれば良い。すなわち、dexcsCfdCaseWriterFoam.py の、class dexcsCfdCaseWriterFoam: / def writeAllrun(self, progressCallback=None):において、Allrunを作成(f.write(cont))した後、以下朱字部を追加。

with p_new.open(mode='w') as f:
    f.write(cont)

fname = os.path.join(self.case_folder, "Allreconst")
p_new = pathlib.Path(fname)
solverSet = "reconstructPar -latestTime | tee reconstructPar.log"
cont = title + envSet + solverSet
with p_new.open(mode='w') as f:
    f.write(cont)
s = os.stat(fname)
os.chmod(fname, s.st_mode | stat.S_IEXEC)

4.6 まとめ改

  • CfdOFを改造して、DEXCSランチャーv2.5のプロトタイプを作成した。
  • 従来のDEXCSマクロにおける懸案事項(以下の第4,6項は除く)はほぼ解消され、操作性、GUIインタフェースも改善された(と思う)。
    1. スケール変換機能の組み込み
    2. meshDict インポート不具合対応
    3. オプションパラメタの GUI 化
    4. テンプレートケースの変更設定
    5. maxCellSize のロジック変更
    6. OF 端末起動
  • このうち、第2項は、インポートが不要となり、メッシュ細分化情報をFreeCADのモデル情報として具備できるようになっている。
  • 第6項はDEXCSツールバーにて実装済み。
  • 第4項は、これから調査し、具体的課題を明確化 ⇒ ほぼ完了
  • DEXCS2021でのリリース(2021/9〜10月)に向けて、出来たら良いなぁ・・・を以下列挙しておく。
    • 日本語化 ⇒ ほぼ完了
    • 並列分割サブメニュー
    • メッシュ細分化タスク画面での細分化対象面の選択時に複リストが出ないようにする
    • テンプレートケース、実行ケースの変更方法を明確化⇒ ほぼ完了
    • CfdOFフォルダ名を変更 ⇒ dexcsCfMesh ⇒ 済み
    • アイコンも変更 ⇒ 済み
    • ソースコード中、不要箇所を削除
    • ちなみにFreeCADのアドオンソフトとして正式登録して欲しいという期待はあるだろうが、DEXCSツールバーと併せてワークベンチ化しないと意味が無さそう。これはもうちょっと先になる。
  • 上記取り組みの前に、次章のPlotワークベンチ応用の可能性を見極めたい。というか、既に3-1.でログファイルを対象に残渣図を描けているので、これをpostProcessフォルダ内のデータ対象に変更、最低限コンボボックス選択で個別にプロットするところまではやりたい ⇒ ほぼ完了
  • マニュアル制作過程において発覚した事項に対する対処も必要であった。
    • これによって、従来のDEXCSマクロにおいて出来ていたが、出来なくなった点もある事も記しておく必要が生じた。つまり、境界タイプの指定が patch(これがデフォルト) または、wall(レイヤー指定のある場合)しかないという点である。

最終的に、上記朱字部分は、DEXCS2021において積み残しになりそうです。

 前へ 目次 次へ

DEXCSランチャーv2.5製作メモ / 4.4

4.4 FreeCAD基本形状を使った細分化領域設定

図40.FreeCADの基本形状を使った細分化領域設定



領域内部を細分化指定する際に、DEXCSマクロではBOX領域指定しかできなかったが、cfMesh本来の設定では、球や円錐といった基本形状で領域を指定する事ができる。しかしこの機能を使いたい場合には、出力されたmeshDict中、下記のコメント行(冒頭に//のある行)の冒頭の//を削除して、数値を手修正して使うという方法しかなかった。

	//coneExample
	//{
	//	type		cone;
	//	cellSize	7.51;
	//	p0			(-100 1873 -320);
	//	p1			(-560 1400 0);
	//	radius0		200;
	//	radius1		300;
	//}
	//hollowConeExample
	//{
	//	type				hollowCone;
	//	additionalRefinementLevels	2;
	//	dditional refinement relative to maxCellSize
	//	p0					(-100 1873 -320);
	//	p1					(-560 1400 0);
	//	radius0_Inner		200;
	//	radius0_Outer		300;
	//	radius1_Inner		200;
	//	radius1_Outer		300;
	//}
	//sphereExample
	//{
	//	type			sphere;
	//	cellSize		7.51;
	//	centre			(0 700 0);
	//	radius			200;
	//	refinementThickness	40;
	//}
	//lineExample
	//{
	//	type		line;
	//	cellSize	7.51;
	//	p0			(-750 1000 450);
	//	p1			(-750 1500 450);
	//	refinementThickness	40;
	//}

一方、FreeCADを使えば、これらの基本形状は容易に作成することが出来て、今回の改変したメッシュ細分化コンテナを使って細分化領域対象パーツとして容易に指定できるようになる(図40)。したがって、選択したパーツの種類に応じた特徴量を取得してmeshDictに反映できるようコード改変を試みた。

これまで、3-6-(2)において、領域内部を細分化対象とするリージョン名リスト(__region__)を取得できているが、さらにその対象オブジェクト(obj)が基本形状であるかどうかを判定して、基本形状である場合はそのタイプに応じて出力書式を変更することにした(以下の朱字部分を追加)。

       if __region__ :
            regionNumber = 0
            for objList in __region__ :
                for obj in doc.Objects:
                    if obj.Label == objList :
                        RefStr = str(__reflevel__[regionNumber])
                        if obj.isDerivedFrom("Part::Box"):
               (Box用 Dict作成)
                         elif obj.isDerivedFrom("Part::Spher"):
               (Sphere用 Dict作成)
                         elif obj.isDerivedFrom("Part::Cylinder"):
               (Cylinder用 Dict作成)
                         elif obj.isDerivedFrom("Part::Cone"):
               (Cone用 Dict作成)
                         else:
                               (DEXCSマクロオリジナルのDict作成)

(1) Box

DEXCSオリジナルマクロでもBoxタイプで指定しているが、Boxタイプということがわかっていれば、中心座標の計算方法が簡略化される(朱字部分)。

centerX = obj.Placement.Base.x + obj.Length.Value * 0.5
centerY = obj.Placement.Base.y + obj.Width.Value * 0.5
centerZ = obj.Placement.Base.z + obj.Height.Value * 0.5
strings7 = [
'\t' + objList + '\n',
'\t{\n',
'\t\ttype box;\n',
'\t\tadditionalRefinementLevels\t' + RefStr + ';\n',
'\t\tcentre (' + str(centerX) + MainControl.SPACE_STR + str(centerY) + MainControl.SPACE_STR + str(centerZ) + ');\n',
'\t\tlengthX\t' + str(obj.Length.Value) + ';\n',
'\t\tlengthY\t' + str(obj.Width.Value) + ';\n',
'\t\tlengthZ\t' + str(obj.Height.Value) + ';\n',
'\t}\n'
           ]

ちなみに、DEXCSオリジナルマクロでは、中心座標を以下のようにして取得しており、これだと対象パーツ(obj)がShape属性を有しているものであれば何であってもよかった。

xmax = obj.Shape.BoundBox.XMax
xmin = obj.Shape.BoundBox.XMin
ymax = obj.Shape.BoundBox.YMax
ymin = obj.Shape.BoundBox.YMin
zmax = obj.Shape.BoundBox.ZMax
zmin = obj.Shape.BoundBox.ZMin
centerX = 0.5*(xmax+xmin)
centerY = 0.5*(ymax+ymin)
centerZ = 0.5*(zmax+zmin)

(2) Sphere

球(Sphere)の場合は、FreeCADにおける球の基準座標(obj.Placement.Base)と、cfMeshで定義する球の中心座標は同じであるので、もっと簡単である。

centerX = obj.Placement.Base.x 
centerY = obj.Placement.Base.y 
centerZ = obj.Placement.Base.z 
strings7 = [
'\t' + objList + '\n',
'\t{\n',
'\t\ttype sphere;\n',
'\t\tadditionalRefinementLevels\t' + RefStr + ';\n',
'\t\tcentre (' + str(centerX) + MainControl.SPACE_STR + str(centerY) + MainControl.SPACE_STR + str(centerZ) + ');\n',
'\t\tradius\t' + str(obj.Radius.Value) + ';\n',
'\t\trefinementThickness\t' + '0' + ';\n',
'\t}\n'
             ]

なお、パラメタとして、refinementThickness も指定できるが、球の径を大きくすることと同じになり、2つのパラメタに区分して定義することにあまり意味があるとも思えなかったので、この値は0としてこれを変更する方法までは考えていない(手作業での変更は可能)。

(3) Cylinder

円柱(Cylinder)を使って、cfMesh のtype line; セクションを作成することができる。但し、円柱は平行移動だけでなく、傾けて配置する(注記1)ことができ、平行移動量の取得は容易であるが、傾けた時の円柱上面の中心座標を求めるのには多大な試行錯誤が必要であった。

pos = FreeCAD.Vector(obj.Placement.Base.x, obj.Placement.Base.y, obj.Placement.Base.z + obj.Height.Value)
center = FreeCAD.Vector(0, 0, - obj.Height.Value)
rot = FreeCAD.Rotation(obj.Placement.Rotation)
cylinderHead = FreeCAD.Placement(pos, rot, center)
p0X = obj.Placement.Base.x
p0Y = obj.Placement.Base.y
p0Z = obj.Placement.Base.z
p1X = cylinderHead.Base.x
p1Y = cylinderHead.Base.y
p1Z = cylinderHead.Base.z
strings7 = [
'\t' + objList + '\n',
'\t{\n',
'\t\ttype line;\n',
'\t\tadditionalRefinementLevels\t' + RefStr + ';\n',
'\t\tp0 (' + str(p0X) + MainControl.SPACE_STR + str(p0Y) + MainControl.SPACE_STR + str(p0Z) + ');\n',
'\t\tp1 (' + str(p1X) + MainControl.SPACE_STR + str(p1Y) + MainControl.SPACE_STR + str(p1Z) + ');\n',
'\t\trefinementThickness\t' + str(obj.Radius.Value) + ';\n',
'\t}\n'
            ]

回転前の円柱上面の中心座標(pos)を円柱下面の中心座標(center)を回転中心として、モデルの回転角(rot)相当回転移動して、回展後の円柱状面の中心座標(cylinderhead)を求めれば良いということであるが、centerは、posに対する相対移動量を指定するところがミソであった。

(4) Cone

円錐(Cone)は、円柱の上面と下面で径が異なるだけであるので、Cylinderの座標指定と同じ方法が使える。2つの径を指定する必要があるというだけの違いである。

center = FreeCAD.Vector(0, 0, - obj.Height.Value)
pos = FreeCAD.Vector(obj.Placement.Base.x, obj.Placement.Base.y, obj.Placement.Base.z + obj.Height.Value)
rot = FreeCAD.Rotation(obj.Placement.Rotation)
cylinderHead = FreeCAD.Placement(pos, rot, center)
p0X = obj.Placement.Base.x
p0Y = obj.Placement.Base.y
p0Z = obj.Placement.Base.z
p1X = cylinderHead.Base.x
p1Y = cylinderHead.Base.y
p1Z = cylinderHead.Base.z
strings7 = [
'\t' + objList + '\n',
'\t{\n',
'\t\ttype cone;\n',
'\t\tadditionalRefinementLevels\t' + RefStr + ';\n',
'\t\tp0 (' + str(p0X) + MainControl.SPACE_STR + str(p0Y) + MainControl.SPACE_STR + str(p0Z) + ');\n',
'\t\tp1 (' + str(p1X) + MainControl.SPACE_STR + str(p1Y) + MainControl.SPACE_STR + str(p1Z) + ');\n',
'\t\tradius0\t' + str(obj.Radius1.Value) + ';\n',
'\t\tradius1\t' + str(obj.Radius2.Value) + ';\n',
'\t}\n'
            ]

以上、4種類の基本形状以外に、cfMeshで定義できるものとして、hollowConeタイプが存在するが、FreeCAD上で相応する基本形状が存在しないので、これについては実装を見送って、従来通りコメントアウト出力を残すようにした。また、内側か外側のどちらかのConeを作成しておいて、両端面における外側または内側の径だけを手作業で追加することはさほど面倒な作業にはならないと思われ、これが実装できていないからといって致命的ではないと考えている。

メッシュ確認例

図41.メッシュ確認例

注記1

傾けるに関しては、BoxタイプであってもFreeCAD上では傾けることはできるが、cfMeshでは傾きを考慮したBox指定ができないので、傾いたBoxタイプを指定すると、傾いていない状態のデータで設定されることになるという点はお断りしておく。

前へ 目次 次へ

DEXCSランチャーv2.5製作メモ / 4.3

4.3 メッシュ細分化タスク画面の改変その2

図38.追加したメッシュ細分化パラメタ



パーツを対象として設定可能なパラメタとして、これまで組み込みが出来ていなかったパラメタを新たに3つ設定できるようにした(図38.における赤枠部分)。すなわち、

  1. keepCellsIntersectingPatches
  2. removeCellsIntersectingPatches
  3. allowDiscontinuity

であり、いずれも使用するかどうかをチェックボックスで設定すれば良いので、GUIの実装やプロパティ化はこれまでのやり方を流用でき、簡単であった。

(1) GUIコンポーネントの追加(dexcsTaskPanelCfdMeshrefinement.ui)

  • keepCellsIntersectingPatches
      <!-- checkBox (keepCellsIntersectingPatches) -->
      <item row="11" column="0" colspan="2">
       <widget class="QCheckBox" name="check_keepCells">
        <property name="text">
         <string>keepCellsIntersectingPatches</string>
        </property>
       </widget>
      </item>
  • removeCellsIntersectingPatches
      <!-- checkBox (removeCellsIntersectingPatches) -->
      <item row="12" column="0" colspan="2">
       <widget class="QCheckBox" name="check_removeCells">
        <property name="text">
         <string>removeCellsIntersectingPatches</string>
        </property>
       </widget>
      </item>
  • allowDiscontinuity
            <!-- checkBox (allowDiscontinuity) -->
            <item row="3" column="0" colspan="2">
            <widget class="QCheckBox" name="check_allowdiscont">
                <property name="text">
                <string>allowDiscontinuity</string>
                </property>
            </widget>
            </item>

(2) プロパティの追加(CfdMeshRfinement.py)

def initProperties(…)に以下追加。

        addObjectProperty(obj, "KeepCell", False, "App::PropertyBool", "",
                          "Keep cells in the mesh template which intersect selected objects)"
        addObjectProperty(obj, "RemoveCell", False, "App::PropertyBool", "",
                          "Remove cells the cells intersecte by the selected objects")
        addObjectProperty(obj, "AllowDiscont", True, "App::PropertyBool", "",
                          "Allow discontinuity of boundary layers")

これにより、プロパティリストが以下のように赤枠部が追加表示される。

図39.追加したプロパティ

(3) メッシュ作成タスク画面とプロパティの関連付け(_dexcsTaskPanelCfdMeshRefinement.py)

def accept(sel)にて、メッシュタスク画面の状態をプロパティリストに反映

        FreeCADGui.doCommand("FreeCAD.ActiveDocument.{}.KeepCell "
                             "= {}".format(self.obj.Name, self.form.check_keepCells.isChecked()))
        FreeCADGui.doCommand("FreeCAD.ActiveDocument.{}.RemoveCell "
                             "= {}".format(self.obj.Name, self.form.check_removeCells.isChecked()))
        FreeCADGui.doCommand("FreeCAD.ActiveDocument.{}.AllowDiscont "
                             "= {}".format(self.obj.Name, self.form.check_allowdiscont.isChecked()))

def load(self) にて、プロパティリストの値をメッシュタスク画面に反映

        self.form.check_keepCells.setChecked(self.obj.KeepCell)
        self.form.check_removeCells.setChecked(self.obj.RemoveCell)
        self.form.check_allowdiscont.setChecked(self.obj.AllowDiscont)

(4) meshDict作成(dexcsCfdNeshTools.py)

  • allowDiscontinuity

3-6-(3) で実施した改変に、以下の朱字部分を追加。

        __patch__ = []
        __nLayer__ = []
        __expRatio__ = []
        __firstLayerHeight__ = []
        __allowDiscont__ = []
        __keepCells__ = []
        __removeCells__ = []
        doc = FreeCAD.activeDocument()
        for obj in doc.Objects:
            if obj.ViewObject.Visibility:
                if hasattr(obj, "Proxy") and isinstance(obj.Proxy, _CfdMeshRefinement):
                    if obj.NumberLayers > 1 :                    
                        for objList in(obj.LinkedObjects):
                            __patch__.append(objList.Label) 
                            __nLayer__.append(obj.NumberLayers) 
                            __expRatio__.append(obj.ExpansionRatio) 
                            __firstLayerHeight__.append(obj.FirstLayerHeight) 
                            if obj.AllowDiscont == 1:
                                __allowDiscont__.append('1')
                            else: 
                                __allowDiscont__.append('0') 
                    if obj.KeepCell == 1:
                        for objList in(obj.LinkedObjects):
                            __keepCells__.append(objList.Label)
                    if obj.RemoveCell == 1:
                        for objList in(obj.LinkedObjects):
                            __removeCells__.append(objList.Label)

紫色部分は、この後の keepCellsIntersectingPatches、にて使用する。

FirstLayerHeight = str(__firstLayerHeight__[patchNumber]).replace('m','') 
FirstLayerHeight = str(float(FirstLayerHeight)*self.mesh_obj.ScaleToMeter)

strings3 = [         
'\t\t'                 + objList + '\n',
'\t\t{\n',
'\t\t\t// number of layers (optional)\n',
'\t\t\tnLayers    '    + str(__nLayer__[patchNumber]) + ';\n',
'\t\t\n',
'\t\t\t// thickness ratio (optional)\n',
'\t\t\tthicknessRatio ' + str(__expRatio__[patchNumber]) + ';\n',
'\t\t\n',
'\t\t\t// max thickness of the first layer (optional)\n',
'\t\t\tmaxFirstLayerThickness ' + FirstLayerHeight + '; // [m]\n',
'\t\t\n',
'\t\t\t// active 1 or inactive 0\n',
'\t\t\tallowDiscontinuity ' + __allowDiscont__[patchNumber] + ';\n',
'\t\t}\n'

なお、青字部分は、3-6-(3) 以降、スケール変更機能を追加したことを踏まえて、それに対応した処置である。

  • keepCellsIntersectingPatches
  • removeCellsIntersectingPatches

どちらもほとんど同じやり方で出来た。基本は、string4[]を定義するブロックにおいて、

        'keepCellsIntersectingPatches\n',
        '{\n',
        #'// patchName\n',
        #'//\t{\n',
        #'//\t\tkeepCells 1; // 1 active or 0 inactive\n',
        #'//\t}\n',
        keepCellsListString,
        '}\n',
        '\n',
        '// remove cells where distinct parts of the mesh are joined together (optional)\n',
        '// active only when keepCellsIntersectingBoundary is active\n',
        '// checkForGluedMesh    0; // 1 active or 0 inactive\n',
        '\n',
        '// remove cells the cells intersected\n',
        '// by the selected patched/subsets\n',
        '// from the mesh template (optional)\n',
        '// it is active when keepCellsIntersectingBoundary\n',
        '// is switched on\n',
        'removeCellsIntersectingPatches\n',
        '{\n',
        #'// patchName\n',
        #'//\t{\n',
        #'//\t\tkeepCells 1; // 0 remove or 1 keep\n',
        #'//\t}\n',
        removeCellsListString,
        '}\n',

緑色部のコメント行として出力していた部分をコメントアウト(削除でもOK)し、予め下記に作成しておいたkeepCellsListStringremoveCellsListString に置き換えるようにした。

keepCellsListString = ""
if __keepCells__ :
    for objList in __keepCells__:
        keepCellsListString = keepCellsListString + "\t" + objList + "\n\t{\n\t\tkeepCells 1; //1 active or 0 inactive \n\t}\n"
else:
    keepCellsListString = keepCellsListString + "//\t" + "patchName" + "\n//\t{\n//\t\tkeepCells 1; //1 active or 0 inactive \n//\t}\n"

removeCellsListString = ""
if __removeCells__ :
    for objList in __removeCells__:
        removeCellsListString = removeCellsListString + "\t" + objList + "\n\t{\n\t\tkeepCells 0; //0 remove or 1 keep \n\t}\n"
else:
    removeCellsListString = removeCellsListString + "//\t" + "patchName" + "\n//\t{\n//\t\tkeepCells 1; //0 remove or 1 keep \n//\t}\n"

また、__keepCells__、__removeCells__のパッチ名リストは、少し上に記した紫色部分で取得してある。

前へ 目次 次へ