DEXCSランチャー v2.5 製作メモ / 解析・雛形ケースの設定



課題の背景

これまでFreeCADモデルの存在するディレクトリが解析ケースフォルダであるとして様々なツールを作成・動作確認してきた。また最初にFreeCADモデルの存在するディレクトリがケースファイルでない場合には、DEXCSの標準チュートリアル問題の設定ファイル(/opt/DEXCS/template/dexcs)を雛形ファイルとしてケースファイルを作成していた。

最終的に、解析ケースフォルダと、雛形ケースファイルは任意に変更できるようにしたいと考えるのが普通であろう。従来のDEXCSマクロにおいては、解析ケースフォルダを変更する機能は有していたが、雛形ケースファイルを選択変更する機能はなかったので、特定のケースファイル内へ出力するという使い方で対処してきており、実質的な使い方の上で大きな違いはないが、内部仕様を知らない人から見た上での解り易さが大きく違うので、ここでは雛形ケースファイルを変更する機能も実装したい。

少し余談になるが、この雛形ケースファイルを変更するメニューが出来たとして、将来的な拡張機能として、TreeFoamの「newCaseの作成」メニュー援用して、ここで選んだケースを採用する方法が考えられるが、一通りの機能が実装できた後の課題としたい。

現状確認

コンボビューにおいて、解析コンテナ(dexcsCfdAnalysis)を選択すると、プロパティ欄に[Output Path]が表示されている。

この表示テキスト欄右端のボタンを押すと、フォルダ選択ダイヤログが現れて、任意のフォルダを選択できる。この選択は有効で、以下メッシュ作成〜ソルバー実行は指定したフォルダで実行してくれる事は確認できている。但し、このワークベンチ上での操作に限定されるもので、DEXCSツールボタンによる操作には反映されす、エラーになってしまう(のは現段階では仕方無い)。

一方、「編集」⇒「設定」画面において[CfdOF]を選択すると、以下の設定画面が現れる。

ここに、[Default output directory]とあり、これも同様に変更することができるようになっている。しかしながら、ここで変更したとしても、先のプロパティには反映されない。

以上の状況から、課題は以下のように整理される。

  1. 雛形ケースファイルを変更する方法を実装する必要がある。これには、Output directory に関する上記2つの設定欄に併記される形で、Template Case を変更できるようにしておくのが普通であろう。
  2. 解析コンテナのプロパティ欄で設定した値と、「設定画面」で設定したこれらの値を連動させる仕組みが必要。
  3. DEXCSツールボタンで、Output directory のプロパティを認識させる方法が必要。
  4. 「設定画面」をCfdOF オリジナルから、dexcsCfdOF に変更したい。機能面で本質的な問題ではないが。

以下、上述の順とは不同であるが、改造点を記しておく。

設定画面(initGui.py ⇒dexcsCfdPreferencePage.py)

initGui.py において、

from dexcsCfdPreferencePage import dexcsCfdPreferencePage
ICONS_PATH = os.path.join(dexcsCfdTools.get_module_path(), "Gui", "Resources", "icons")
QtCore.QDir.addSearchPath("icons", ICONS_PATH)
FreeCADGui.addPreferencePage(dexcsCfdPreferencePage, "dexcsCfdOF")

アイコンは、Gui/Resources/iconsフォルダ中に収納されて、CfdOFのオリジナルアイコン(preference-cfdof.png)そのものを変更するのは躊躇われたので、新たにpreference-dexcscfdof.pngを用意して収納した。

dexcsCfdPreferencePage.py においては、Template Case を変更できるようにしたいが、デフォルトはDEXCS標準チュートリアル(/opt/DEXCS/template/dexcs)としたいので、最初にグローバルパラメタとして、

DEXCS_TEMPLATE = "/opt/DEXCS/template/dexcs"

定義しておく。以下、OutputDirectoryについて記述している箇所を見つけて、その下にTemplateCaseについて同様に追加しただけである。

(103行目あたり)
self.form.tb_choose_output_dir.clicked.connect(self.chooseOutputDir)
self.form.le_output_dir.textChanged.connect(self.outputDirChanged)
self.form.tb_choose_template_case.clicked.connect(self.chooseTemplateCase)
self.form.le_template_case.setText(DEXCS_TEMPLATE)
self.form.le_template_case.textChanged.connect(self.templateCaseChanged)
(209行目あたり)
def outputDirChanged(self, text):
    self.output_dir = text

def templateCaseChanged(self, text):
    self.template_case = text

以上の変更によって、改変された設定画面を以下に示す。

なお、画面データ(dexcsCfdPreferencePage.ui)は、QtDesignerを使って、オリジナルデータ(CfdReferencePage.ui)を改変したものである。その際に、HISAなるSoftwareに関する設定項目があり、dexcsCfdOfでは使用することがないので、削除を試みたが、FreeCADの起動時にエラー表示で立ち上がらなくなってしまう。その原因解明は当面ペンディングして先へ進めることとした。

解析コンテナにおけるプロパティ追加(dexcsCfdAnalysis.py)

こちらもOutput…について記述されている箇所に続けて、Templateに係る記述に変更しただけである(朱字部)。

(54行目あたり)
model = FreeCAD.ActiveDocument.FileName
#print(model)
modelDir = os.path.dirname(model)
templateCase = "/opt/DEXCS/template/dexcs"
addObjectProperty(obj, "OutputPath", modelDir, "App::PropertyPath", "",
                  "Path to which cases are written (blank to use system default)")
addObjectProperty(obj, "TemplateCase", templateCase, "App::PropertyPath", "",
                  "Path to Template case Dir (blank to use system default)")

なお、この時点では、先の「設定」画面で設定した値との連動までは考えていない。とりあえずこの画面で変更したパラメタを使って、メッシュ作成や解析実行が、所定の動作するかどうか確認していく。

また、OutputDirectoryに関するパラメタの用語は、必ずしも統一されていない(Pathが使われていたり、小文字で始まっていたりする)が、当面、これはこれで合わせておく必要はある。

解析ケースの作成

解析ケースはメッシュ作成タスク画面(_dexcsTaskPanelCfdMsh.py)で[Write]ボタンを押すと作成される。

#cart_mesh = self.cart_mesh
self.analysis = dexcsCfdTools.getParentAnalysisObject(self.mesh_obj)
output_path = dexcsCfdTools.getOutputPath(self.analysis) + os.path.sep
template_case = dexcsCfdTools.getTemplateCase(self.analysis)
try:
    QApplication.setOverrideCursor(Qt.WaitCursor)
    FreeCADGui.doCommand("dexcsCfMesh = dexcsCfMeshTools.MainControl()")
    #FreeCADGui.doCommand("dexcsCfMesh.perform("+ "'" + cart_mesh.meshCaseDir + "'" + ")")
    FreeCADGui.doCommand("dexcsCfMesh.perform("+ "'" + output_path + "', '" + template_case + "'" + ")")       

緑字でコメントアウトした部分を、朱字に変更した。ポイントは、dexcsCfMesh(オリジナルのDEXCSマクロを改造したもの)のperform()関数にてケースファイルを作成しているのであるが、これまで出力先(output_path)だけを指定していたのに対し、併せてtemplate_caseも引数に加えたという点。そして、解析コンテナにて指定したプロパティ値の取得方法である。

解析の出力先(output_case)を取得するのに、オリジナルでは、cart_mesh.meshCaseDir という形で、別途定義してある関数を使用していた。これに合わせてtemplate_caseを取得する関数を作成する事も考えられたが、その方法がよく分からなかった。そこで試行錯誤の末、ここに記した方法に辿り着いた。

解析コンテナ上のtemplate_caseを取得するのに、dexcsCfdTools.getTemplateCase(self.analysis)を使っているが、output_path を取得するdexcsCfdTools.getOutputPath(self.analysis)に倣って、dexcsCfdTools.py 中の、def getDefaultOutputPath():と、def getOutputPath(analysis):ブロックの下に、変数名が異なるだけで全く同じ手続のdef getDefaultTemplateCase(): と、def getTemplateCase(analysis):を追加した。

(75行目あたり)
DEXCS_TEMPLATE = "/opt/DEXCS/template"

def getDefaultOutputPath():
    prefs = getPreferencesLocation()
    output_path = FreeCAD.ParamGet(prefs).GetString("DefaultOutputPath", "")
    #print('output_path = ' + output_path)
    if not output_path:
        #output_path = tempfile.gettempdir()
        output_path = os.path.dirname(FreeCAD.ActiveDocument.FileName) 
    output_path = os.path.normpath(output_path)
    return output_path

def getDefaultTemplateCase():
    prefs = getPreferencesLocation()
    template_case = FreeCAD.ParamGet(prefs).GetString("DefaultTemplateCase", "")
    #print('template_case = ' + template_case)
    if not template_case:
        #template_case = tempfile.gettempdir()
        template_case = DEXCS_TEMPLATE
    template_case = os.path.normpath(template_case)
    return template_case

def getOutputPath(analysis):
    if analysis and 'OutputPath' in analysis.PropertiesList:
        output_path = analysis.OutputPath
    else:
        output_path = ""
    if not output_path:
        output_path = getDefaultOutputPath()
    output_path = os.path.normpath(output_path)
    return output_path

def getTemplateCase(analysis):
    if analysis and 'TemplateCase' in analysis.PropertiesList:
        template_case = analysis.TemplateCase
    else:
        template_case = ""
    if not template_case:
        template_case = getDefaultTemplateCase()
    template_case = os.path.normpath(template_case)
    return template_case

この変更に伴って、dexcsCfMesh(オリジナルのDEXCSマクロを改造したもの)のperform()関数の変更が必要になるが、変更はわずかで、以下朱字部を追加するだけであった。

(1601行目あたり)
def perform(self, CaseFilePath, TemplateCase):
(1648行目あたり)        
if TemplateCase:
    templateSolver =  TemplateCase
else:   
    templateSolver = self.SOLVER_PATH_TEMPLATE

以上で、解析コンテナのプロパティ値にて、解析ケース(出力先)、雛形ケースファイルを任意に設定できるようになり、メッシュ作成、解析実行できることを確認した。あとは、

  1. プレファレンスの設定画面での設定値と上記プロパティ値を連動させる
  2. DEXCSランチャーのツールボタンの動作を整合させる

プレファレンス設定画面再考

プレファレンスの設定画面での設定値と解析コンテナのプロパティ値が連動していない点が問題ではあったが、ユースケースを考えると、単に連動すれば良いというものでもなさそうだ。つまり、プレファレンスの設定画面は常時利用するものではないということである。解析コンテナで出力フォルダを変更したからといって、それが恒久的な設定になってしまうのも考えものである。そこで、解析フォルダと雛形ケースファイル(フォルダ)については、使用方法とし、以下の考え方を基本にするとした。

解析コンテナを新規作成した場合にプレファレンスの設定画面で設定したものをデフォルトとし、その設定を引き継ぐ。その後解析コンテナにて変更することは可能だが、変更したからといってプレファレンスまでは変更しない(一方向の連動のみ)。

プレファレンス設定の初期値は、user.cfg に記載されているものを使用するが、解析ケースについては、実在するディレクトリの場合にのみそれを採用する。そうでない場合には、[model_dir]が採用され、モデルの存在するディレクトリである事を示すようにする(下図参照)。

これを実現するのに、これまでオリジナルのCfdOFでは解析フォルダは特定のフォルダを使用する事を想定していた為、本改造では暫定的にInitGui.py def __init__(self) において、

prefs = dexcsCfdTools.getPreferencesLocation()
FreeCAD.ParamGet(prefs).SetString("DefaultOutputPath", "")

として、user.cfg の値に依らないで、ここでデータをクリアしていたが、この部分を元に戻した。また、プレファレンス設定画面の表示には、

(156行目あたり)
self.output_dir = dexcsCfdTools.getDefaultOutputPath()

が使われるので、

def getDefaultOutputPath():
    prefs = getPreferencesLocation()
    output_path = FreeCAD.ParamGet(prefs).GetString("DefaultOutputPath", "")
    if not output_path:
        output_path = 'model_dir'
    output_path = os.path.normpath(output_path)
    return output_path

とし、さらに解析コンテナがロードされた際に設定される出力フォルダ名を、以下のように

    def initProperties(self, obj):
        model = FreeCAD.ActiveDocument.FileName
        prefs = dexcsCfdTools.getPreferencesLocation()
        model_Dir = FreeCAD.ParamGet(prefs).GetString("DefaultOutputPath", "")
        if os.path.exists(model_Dir):
            modelDir = model_Dir
        else:
            modelDir = os.path.dirname(model)
        templateCase = FreeCAD.ParamGet(prefs).GetString("DefaultTemplateCase", "")

プレファレンス設定画面(user.cfgに記載)の表示にて指定したディレクトリが、実在するかどうかの判定をして、実在する場合にのみ採用するようにした。

DEXCSランチャーツールボタン

解析ケース(出力先)をモデルが収納されたディレクトリ以外に設定した場合に、DEXCSランチャーのツールボタンが起動しないのは、この時点ではそういう仕様であるので仕方ない。DEXCSランチャーでは、モデルが収納されたディレクトリに、.CaseFileDictを収納して、ここに出力先を記すようにしており、この時点で、このファイルが存在しないからということである。

そこで、メッシュ作成タスク画面(_dexcsTaskPanelCfdMsh.py)で[Write]ボタンを押した時に、出力先が確定した後で、以下の朱字部分を追加して、.CaseFileDict を作成するようにした。

output_path = dexcsCfdTools.getOutputPath(self.analysis) + os.path.sep

if output_path[-1] == os.path.sep:
    output_path1 = output_path[:-1] 
else:
    output_path1 = output_path
dictName = os.path.dirname(FreeCAD.ActiveDocument.FileName)  + "/.CaseFileDict"
writeDict = open(dictName , 'w')
writeDict.writelines(output_path1)
writeDict.close()

なお、出力先パス名の最後の文字がos.path.sep(パス分離記号)の場合、それを含まないようにしているのは、これが残っていると、TreeFoamが起動しなくなってしまう為であった(その他のランチャーボタンは、あってもなくても正常に起動する)。

旧版DEXCSマクロ

DEXCS2021では、旧版のDEXCSマクロも混在させた状態でリリースすることを考えているが、それとDEXCSツールバーボタンの動作を確認した。

DEXCSワークベンチによる解析コンテナを使用しない状態であれば、基本的に旧版での操作方法の通りで動作する。基本的にと記したのは、plotWatcherと、ポスト処理においてPlotモジュールが起動されるようになった点である。特にplotWatcher機能は、このボタンで使用する限り自動更新されない点は、今の所仕方無いとしておく。

一方、DEXCSワークベンチによる解析コンテナを作成した状態において、旧版DEXCSマクロを起動すると、エラーで起動できない。エラーの内容は、解析コンテナがShapeオブジェクトを有していないというものであったので、ここは、以下のように、朱字部で例外処理させることで回避できた。

            try:
                if obj.Shape:
                    if obj.Shape.BoundBox.XMax > xmax:
                        xmax = obj.Shape.BoundBox.XMax
                    if obj.Shape.BoundBox.XMin < xmin:
                        xmin = obj.Shape.BoundBox.XMin
                    if obj.Shape.BoundBox.YMax > ymax:
                        ymax = obj.Shape.BoundBox.YMax
                    if obj.Shape.BoundBox.YMin < ymin:
                        ymin = obj.Shape.BoundBox.YMin
                    if obj.Shape.BoundBox.ZMax > zmax:
                        zmax = obj.Shape.BoundBox.ZMax
                    if obj.Shape.BoundBox.ZMin < zmin:
                        zmin = obj.Shape.BoundBox.ZMin
            except AttributeError:
                pass

これにより、解析コンテナを非表示状態にしてマクロを立ち上げれば、旧版DEXCSマクロの挙動は全く同一のものになる。解析コンテナを表示状態のままマクロを立ち上げると、解析コンテナも含めてマクロ画面にリストアップされることになり、そのまま無理やりエクスポートすると、解析コンテナからastファイルが作れないとなって立ち行かなくなる。これはそういう仕様なので仕方無い。

何はともあれ、解析・雛形ケースを任意に設定可能となり、旧版DEXCSマクロや、DEXCSランチャーボタンでの動作とも整合するようにはなった(と思う)。

前へ 目次

DEXCSランチャー v2.5 製作メモ / 日本語化

タスク画面の原版と日本語化版を、以下に対比して表示した。



日本語化方法

FreeCADの拡張モジュールを多言語化する方法があるだろう事は、CfdOFの元本ソースInitGui.py中(71行目)に、

        # enable QtCore translation here, todo

なるコメント行があり、ここで何らかの方法を組み込む仕組みがありそうな点は推察できているが、現時点では不明であったので、当面従来のDEXCSランチャーで実施している方法に倣って実施した。

とはいうもの、従来の方法は、pythonでは一般的な方法と考えられているgettextを使う方法で、これはソース中の変換したいテキスト部分(通常はダブルクオーテーションによって括られている)を、_(“text”) といった形で括っておくもので、この部分が変換対象になって、変換辞書に一致したテキストが見つかれば、そのテキストに対応した訳語に変換してくれるというものであった。しかるにCfdOFにおけるタスク画面は、QtDesignerによって作成された .ui ファイルをインポートする形のpythonプログラムにて作成されており、画面中に表示される名前は全て.uiファイル中に定義されている。そしてこの.uiファイル中での定義方法は、

    <string>Edit</string>

となっており、テキストそのものがダブルクオーテーションによって括られていないという事がわかった。従って、この部分を_()で括ったところで、表示が_(Edit) に変更されることにしかならない。

QtDesignerによって作成された .ui ファイルをインポートするでなく、pythonプログラムに変換する方法(ネット検索ではこの方法が上位でヒットする)であればこういうことにはならない(別途作成中の、Plotメニューでも確認済み)であるので、タスク画面作成プログラムそのものも、.ui ファイルをインポートするでなく、変換方式に変更する事も考えられなくもなかったが、単純な作業にはなりそうにないので躊躇われた。

そこで、以下の方法を考えた。つまり、いずれのタスク画面も、def __init__() ブロックにおいて、.uiファイルをインポートしたその直後において、タスク画面を、self.form = … という呼び出しで作成しているので、その直後に変換したいテキストラベルやボタンの名前を、再度、

     self.form.label.setText(_("Text"))

という形で書き換えてしまおうというものである。具体的に、メッシュ作成タスク画面作成プログラム(_dexcsTaskPanelCfdMesh.py)における変更(追加)コードを以下に示しておく。

まず、冒頭、

import sys
import gettext

localedir = os.path.expanduser("~") + "/.FreeCAD/Mod/dexcsCfMesh/locale"
if sys.version_info.major == 3:
	gettext.install("dexcsCfMeshSetting", localedir)
else:
	gettext.install("dexcsCfMeshSetting", localedir, unicode=True)

として、gettext処理(訳語変換)プログラムを実行。辞書ファイルが、FreeCAD/Mod/dexcsCfMesh/localeにあるという前提である。使用環境のpythonのバージョンに応じて呼び出し方法を変えている。

def __init__() ブロックにおいては、set.form = … の後、以下のように青字部分を追加している。

self.form = FreeCADGui.PySideUic.loadUi(os.path.join(os.path.dirname(__file__), "dexcsTaskPanelCfdMesh.ui"))

self.form.setWindowTitle(_("dexcsCFD Mesh"))
self.form.pb_write_mesh.setText(_("Write mesh case<"))
self.form.l_featureAngle.setText(_("Feature Angle(deg)"))
self.form.pb_stop_mesh.setText(_("Stop"))
self.form.l_scaleToMeter.setText(_("Scale to Meter:"))
self.form.pb_edit_mesh.setText(_("Edit"))
self.form.pb_run_mesh.setText(_("Run mesher"))
self.form.label_3.setText(_("Mesh verification<"))
self.form.label_5.setText(_("Mesher<"))
self.form.label_6.setText(_("Mesh<"))
self.form.pb_checkmesh.setText(_("CheckMesh"))
self.form.label.setText(_("Mesh Parameters<"))
self.form.check_optimiseLayer.setText(_("optimiseLayer"))
self.form.label_maxNumIterations.setText(_("maxNumIterations"))
self.form.label_maxAllowedThickness.setText(_("maximum allowed thickness:"))
self.form.label_featureSizeFactor.setText(_("featureSizeFactor"))
self.form.label_reCalculateNormals.setText(_("reCalculateNormals"))
self.form.label_nSmoothNormals.setText(_("Number of iterations:"))
self.form.check_keepCells.setText(_("keepCellsIntersectingBoundary"))
self.form.l_max.setText(_("Base element size:"))
#self.form.cb_workflowControls.setText(_("workflowControls"))
self.form.label_2.setText(_("Status"))

なお、ダブルクオーテーションで括られたテキスト名は、dexcsTaskPanelCfdMesh.ui 中,<string>…</string>で括られた該当箇所のテキスト名をカット&ペーストで転記したものであり、老眼一歩手前のマウス操作の手違いで、余分な箇所(<)まで転記されたものが何箇所か残っていた。本記事冒頭に掲載した英語版画面イメージにおいて、一部表記に不具合があるのはこの為である。訳語を作成する段階において気付いていたのだが、とりあえず動作確認を優先してそのまま進めた(もちろん今後手直し予定)。

また、下から2行目はコメントアウトしないとエラーになった為、これもとりあえず全体的な動作確認を優先しコメントアウトしたもので、この部分も今後手直し予定である。

また、日本語訳語において、一部原文のまま表記している箇所がいくつか存在する。これは闇雲に日本語化すれば良いというものでない。たとえば、Paraviewをパラビューとしても却って分かり難くなってしまう。また、cfMeshの設定パラメタの名前そのものである。設定ファイル(meshDict)中の名前と違う表記にしてしまうと、meshDict中の何処に相当する箇所のパラメタなのかわからなくなってしまう。

とはいうもの、現時点での表示で、境界線が曖昧である点も否めない。日本語化して分かり難くなる可能性のある語句は重複表記の方が良いかもしれないが、画面サイズ上の問題があるので、ToolsTipsで補完するのが良いかもしれない。

注意事項

英語表記と日本語表記の切り替えは、OSの設定言語に依存して自動的に切り替わる。FreeCAD本体には、OSの設定言語には依らず、独自に言語を設定できる仕組みが存在する(「編集」⇒「設定」⇒設定メニュー:言語を変更)が、これを使ってFreeCAD本体の表示言語を変更しても、ここで設定した言語表記には反映されないという点はお断りしておく。

前へ 目次

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において積み残しになりそうです。

 前へ 目次 次へ