DEXCSランチャーv2.5 製作メモ / 追記事項



DEXCS2021のリリースに向けて、各種チュートリアルの動作確認、マニュアルを制作中だが、この段階で発覚した機能不足(仕様の見落とし)点についてここに取り纏めておく。

patchタイプの指定

旧版のDEXCSマクロにおいては、パーツ毎に境界のタイプを指定する事が出来ていた。新版のDEXCSワークベンチでは、これに相当する設定箇所が無いので、特に何も指定しないままで放置してあった。その事を放念したまま各種チュートリアルの動作確認をしたのだが、これが原因で動作しないという事は無かった。

しかし、マニュアル作成の段階で、よくよく調べると、下図に示すように、

マニュアル中のグリッドエディタ説明図

境界のタイプがすべて wall になってしまっていた。これまで赤矢印部は、当たり前のように、patch としていた箇所であった。patch でなくとも、wallでも問題なく計算できるという点には今更ながら「知らなんだー」なんであるが、かといって、このままでは、OpenFOAMの常識的に違和感有りと言わざる得ない。

ただ、今回作成したGUI画面上で、旧版マクロのように境界タイプを指定する事はアーキテクチャー的に無理なので、ここではデフォルトがpatchになるようにしておくのが無難。ただ境界層レイヤー指定のあるパーツはwallとしておくのは問題無いだろうという、ややいい加減な方針で取り組む事とした。

まず、以下のようにして、境界層レイヤーの数が1以上のパッチの数(patchNumber)と名前リスト(__patch__)を取得しておく。

__patch__ = []
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) 
                    patchNumber = patchNumber + 1

このリストを使って、表示パーツ(obj)毎に、リストに含まれるかどうかを判定し、含まれる(changeLabel=1)ならば、wall、そうでなければ type に変更するようにしたのが以下のコードになる。

for obj in doc.Objects:
     if obj.ViewObject.Visibility:
            changeLabel = 0
            if patchNumber > 0 :
                for i in range(patchNumber):
                    if obj.Label == __patch__[i] :
                        changeLabel = 1
            if changeLabel == 1:
                strings9 = [
                '\t\t' + str(obj.Label) + '\n',
                '\t\t{\n',
                '\t\t\tnewName '+ str(obj.Label) + ';\n',
                '\t\t\ttype wall;\n',
                '\t\t}\n',
                ]
            else:
                strings9 = [
                '\t\t' + str(obj.Label) + '\n',
                '\t\t{\n',
                '\t\t\tnewName '+ str(obj.Label) + ';\n',
                '\t\t\ttype patch;\n',
                '\t\t}\n',
                ]
            meshDict.writelines(strings9)

一応、これにて、マニュアル中のグリッドエディタ画面の説明図は従来通りのものになってくれる。但し、出来上がったmeshDict の renameBoudary ブロックは以下のようなものになってしまう。

renameBoundary
{
	newPatchNames
	{
		Dexcs
		{
			newName Dexcs;
			type wall;
		}
		inlet
		{
			newName inlet;
			type patch;
		}
		outlet
		{
			newName outlet;
			type patch;
		}
		wall
		{
			newName wall;
			type patch;
		}
		regionBox
		{
			newName regionBox;
			type patch;
		}
		dexcsCfdAnalysis
		{
			newName dexcsCfdAnalysis;
			type patch;
		}
		CFDMesh
		{
			newName CFDMesh;
			type patch;
		}
		CfdSolver
		{
			newName CfdSolver;
			type patch;
		}
		MeshRefinement
		{
			newName MeshRefinement;
			type patch;
		}
		MeshRefinement001
		{
			newName MeshRefinement001;
			type patch;
		}
	}
}

つまり、朱字部regionBox以下の境界でないパーツ名までリストアップされてしまう。実際問題としてこれらがリストアップされたからといって、メッシュ作成に問題が生じるでもなく、最終的な出来上がりのpolyMesh/boundary ファイル中に、これらがリストアップされる事もない。

したがって、実用上はこれで問題無いと言えるのだが、やはり気持ち悪いので、これらがリストアップされないようにすることを考えた。基本的には、region指定用パーツと、Dexcsワークベンチ関連のコンテナである。

まず、region指定用パーツであるが、その数(regionNumber)とパーツリスト(__region__)はすでに取得されているので、以下のようにして、除外判定できる。

regionLabel = 0
if regionNumber >0:
    for iregion in range(regionNumber):
       if obj.Label == __region__[iregion]:
            regionLabel = 1

問題は、Dexcsワークベンチ関連のコンテナであった。当初、CfdOFのコード中、随所に

if hasattr(obj, "Proxy") and isinstance(obj.Proxy, _CfdMeshRefinement):

という判定文があり、これを真似すれば良いだろうと考えた。つまり、上記はメッシュ細分化コンテナを特定する為の判定文であった。このうちの、hasattr(obj, “Proxy”) だけを指定すれば、Dexcsワークベンチ関連のコンテナを指定できると考えてやってみたのだが、これだと確かに関連コンテナを除外できたものの、それ以外のもの(上の例ではwallも)除外されてしまった。何故こういうことになるのかよくわからないのだが、これ以上時間も掛けられないので已む無く、コンテナの名前で除外する事にした。つまり、

lenObjLabel = len(obj.Label)
skipLabel = 0
if lenObjLabel>6:
    if obj.Label[0:7] == 'CFDMesh':
        skipLabel = 1
    if obj.Label[0:7] == 'CfdSolv':
        skipLabel = 1
    if obj.Label[0:7] == 'MeshRef':
        skipLabel = 1
    if obj.Label[0:7] == 'dexcsCf':
        skipLabel = 1

という判定ブロックにて、skipLabel = 1 のものは除外した。

何かもう少しスマートな書き方がいくらでもありそうだが、まぁここが素人プログラマの限界ってことで。とりあえず先に進む事にした。それよりも、モデル中に上述の名前で始まるパーツ名があると厄介な事になるかもしれないので、ここはメモしておきたい。

前へ 目次

DEXCSランチャー v2.5 製作メモ / ランチャーツールボタン



課題の背景

先の記事でDEXCSランチャーのツールボタンにおいて、解析ケース(出力先)を整合させる事が出来たので、ユーザー目線での問題は解決できた。しかし、次期DEXCS2021においてFreeCADのバージョン選定に際して、従来通り、FreeCAD-daily版を使用していくのであれば、このままでも使えそうだが、daily版は安定性に不安があり、なおかつFEMモジュールにおいて、Netgenメッシャーが使えないという問題がある。可能であれば、AppImage版を使いたいのだが、そうするとDEXCSツールボタンのうち、TreeFoamのサブプログラムを使用するボタンが機能しなくなってしまう。

今回、CfdOFをdexcsCfdFとして改変作業を実施した事によって、CfdOFのコードの中から、別プロセスを起動する方法を学ぶことができた。これを参考にすれば、上述の問題(AppImage版で動作しない)が解決できる可能性があったので、挑戦してみることとした。因みに、CfdOFは、AppImage版だけでなく、Windows版、Mac版でも動作することを前提としたプログラムになっている点を追記しておく(動作は確認していないが)。

着想

別プロセスを起動する方法は、CfdOFにおいても、DEXCSツールボタン(あるいはTreeFoam)においても、状況に応じた起動スクリプトをカレントディレクトリ内に作成し、そのスクリプトを起動するプロセスを立ち上げているのは変わりない。スクリプトの内容も取り立てて違いがあるわけでもなかった。違いはプロセスを起動する方法であった。

DEXCSツールボタン(あるいはTreeFoam)では、python で一般に使われる、os.system()なり、subprocess.run()を使っていた。一方、CfdOFでは、QProcessを使っていたという違いであった。

方策1

editConstantFolder.py を例にあげて説明する。以下の緑字部分を、朱字部に変更した。

f=open("./run","w")
f.write(cont)
f.close()
#実行権付与
os.system("chmod a+x run")
#実行
#comm = "xfce4-terminal --execute bash ./run"
#comm= "gnome-terminal --geometry=80x15 --zoom=0.9 -- bash --rcfile ./run"
#os.system(comm.encode("utf-8"))
cmd = dexcsCfdTools.makeRunCommand('./run', modelDir, source_env=False)
env = QtCore.QProcessEnvironment.systemEnvironment()
dexcsCfdTools.removeAppimageEnvironment(env)
process = QtCore.QProcess()
process.setProcessEnvironment(env)
process.start(cmd[0], cmd[1:])

このコード中のポイントは2点、朱字部最下行で、「着想」にて記したprocess(Qprocess)を実行している点と、朱字部3行目dexcsCfdTools.removeAppimageEnvironment(env)の、文字通りAppimage環境を除去している関数を使っているという点である。FreeCADのAppImage版でなければ、この行が無くとも実行されるが、AppImage版でこの行が無いと実行されない。また、この行だけ機能するようにしたとして、従来の緑字部分の起動方法を使っても実行してくれない。

このコードだけから読み取れるポイントは2つあったが、これ以外にもう1点ポイントがある。editConstantFolder.py を例にあげて説明したが、editSystemFolder.pyにおいても、プログラムの内容はほとんど同じで、 runの内容で、/constant を対象にするか、/systemを対象にするかの違いでしかなかった。したがって、上記と全く同じ変更にて実行できるものと思われた。しかし、、、動かないのであった。

対象フォルダの違いだけかと思っていたが、実はプログラム全体の構造にも若干の違いがあった。結論からいうと、defブロックの有無であった。editSystemFolder.pyは、defブロックを有しない、ベタのプログラムであった。最終的に、プログラムの最終行に、以下のダミーのdefブロックを追加すれば動くことを確認した。editSystemFolder.py以外のマクロで、defブロックの無いものは、同様の変更が必要であった。

def dummyFunction(): # 何故かこれがないとうまく動かない      
    pass

余談になるが、最初にeditConstantFolder.pyの改造に取り組んだことがラッキーであった。editSystemFolder.pyを先に取り組んでいたとしたら、この解決法(ダミーのdefブロック追加)は有りえなかっただろう。逆に言えば他の解決法はあるのかもしれないが、お手上げになっていた可能性大であった。

他にも、ifブロックのネスト深さの違いもあったが、これは関係無かった。

方策2

方策1によって、DEXCSツールボタンのrunSolver.py以外の全てが機能する(ボタンを押せば所定の画面が現れる)ようにはなった。

ただ、runSolver.pyも、従来のように、端末画面が現れて、計算ログが表示されることはないのであるが、計算そのものは実行してくれている。また、そもそもDEXCSワークベンチで実行すれば良いので、これは無くす方向で考えているので、当面問題無いこととした。

問題は、runTreeFoam.pyとrunGridEditor.pyであり、根は同じ現象に至った。初期画面は現れるのであるが、肝心なGridEditor画面が表示されないのであった。runGridEditor.pyでは、TreeFoamモジュールのopenGridEditorDialog.pyが起動され、以下の画面が立ち上がる。

ここで、「開く」ボタンを押せば、おなじみの表形式画面が現れるはずなのだが、これがあがってこないということである。TreeFoamも同じで、TreeFoam本体は立ち上がるが、GridEditorを起動できない(他のボタンは問題無さそうであったが)。これを解決するには、TreeFoam本体の改造が必要になった。「開く」ボタンを押した時に実行されていたのは、

        pyTreeFoam.run(caseDir).command(comm)

であり、pyTreeFoam.py中の関数が使用されている。この実体は、

    def command(self, comm):
        """ commandを実行する。"""
        os.chdir(self.caseDir)
        dummy_proc = subprocess.run(comm, shell=True) 

という簡単なものであったが、この内容が問題であった。方策1で問題になったのと類似で、こちらはsubprocess.run にてプロセスを実行していた。

したがって、方策1と同様の対策によって解決するのではないかと思って対処したところ、GridEditorの表形式画面が現れるようになった。

めでたし、めでたし、となるはずであったが、、、

方策3

今度は、editConstantFolder.pyや、editSystemFolder.pyの挙動がおかしくなった。起動画面は問題無かったが、実際にエディタ画面が表示された段階にて、中味が真っ白な画面になってしまった。

これも調べると、上述のpyTreeFoam.py 中の同じ関数を経由してエディターを起動している事がわかった。この関数を変更した結果が、こちらには災いしたということであった。

諸々試行錯誤の末、今の所下記変更にて、問題は生じていないようである。

    def command(self, comm):
        """ commandを実行する。"""
        os.chdir(self.caseDir)
        #print('comm = ',comm)
        if comm[0] == '.':
            from PyQt5 import QtCore
            process = QtCore.QProcess()
            working_dir = self.caseDir
            if working_dir:
                process.setWorkingDirectory(working_dir)
            qcomm = 'bash -c ' + '"' + comm + '"'
            ret = process.startDetached(qcomm)
        else:
            dummy_proc = subprocess.run(comm, shell=True) 

つまり、スクリプトを作成して実行するタイプのもの(というか、環境変数を変更して実行するもの)は、Qprocessを使って起動する必要がありそうで、そうでないもの(エディターを起動するなど)は、subprocess.run で問題なく動きそうだということと、かつスクリプトで動くものは、最初に「.」という環境変数組み込みが記述されているという前提での処置である。逆に言うと、スクリプトの内容で、最初に「.」以外の文字があると、AppImage版では動かない可能性があることになる。

DEXCSランチャーから起動するTreeFoamのサブモジュール(プログラム)については、GridEditorの表形式画面が出る前に一瞬ダイヤログ画面が現れてすぐ消滅するという問題以外は、今の所、致命的問題が表出していないが、TreeFoamトータルの観点から問題があるかもしれない。このあたり、

  • 上記改造をDEXCS版固有で適用していくしかないのか
  • 上記改造をTreeFoam本体に適用する可能性
  • 他にもう少し手は無いか

など、TreeFoamの作者さんに問い合わせてみることとしたい。

方策4

方策3について、TreeFoam作者さんにも動作検証してもらったところ、FreeCADのランチャーボタンから起動したTreeFoamを使うと、いくつか問題が生じる事が判明した。

1.並列処理のdecomposeParによるメッシュ分割が機能しない。(分割できない。)
2.メッシュ作成時の特徴線(featureEdge)が抽出できない。

などである。特に1.については、FreeCADのランチャーボタンから起動した並列処理画面においても同様であった。

一方、FreeCAD-Daily版であるが、最新版は ver-0.20となっており、

起動オプションのカスタマイズが柔軟になった。すなわち、これまでDEXCSツールバーのダウングレードアイコンを使えるようにするには、一旦、ドラフトワークベンチを表示させる必要があった。今回のDEXCSワークベンチにおいてもスタート画面をDEXCSワークベンチにしておくとDEXCSツールバーのダウングレードアイコンが表示されないのは同様であったが、FreeCAD-Daily の新板を使うとこの問題が解消された。DEXCSランチャーボタンの挙動も問題無い。さらに、FEMワークベンチにおいて、様々な境界条件設定ツールが追加されていたという発見もあった。

一方で、今回のDEXCS2021においては、OpenFOAMのバイナリインストールや、Helyx-OSの搭載中止によって、isoイメージのサイズに余裕が出来ており、FreeCADについてDaily版とAppImage版の両方を収納する事も可能であることを確認できた。

また、CfdOFのソース中には、FreeCADがAppImage版であるかどうかを判定するコードがあったので、これを活用すれば、上述の問題に対する場合分けも可能となる。そこで、最終的に、以下の方針であたることとした。つまり、

DEXCS2021の標準では最新Daily版(ver-0.20)を使う設定とし、ニッチユーザー向けにAppImage(ver-0.19)に切り替えられるようにする。AppImageで使う場合には、DEXCSツールバーから、TreeFoamのボタンと、並列ボタンを押した場合に、正常に動作しない旨の警告を出すようにする、というものである。なお、念の為に記しておくが、FreeCADはDaily版を使おうが、AppImage版を使おうが、FreeCADのマクロやワークベンチ(一式がホームディレクトリ下の.FreeCAD中に収納される)は同じものを使うことになる。

具体的に、AppImage版からTreeFoamの起動ボタンを押すと、以下の警告メッセージが表示され、Daily版ではこのメッセージが現れないるようにした。

因みにこのメッセージは、以下のようにしてAppImage版かどうかの判定をした結果次第で表示されることになる。

env = QtCore.QProcessEnvironment.systemEnvironment()

if env.contains("APPIMAGE"):
    message = (_("this FreeCAD is AppImage version.\n  some function of TreeFoam doesen't work.\n if you want utilize the function, use normal TreeFoam clicked by dock-launcher button.")) 
    ans = QtGui.QMessageBox.critical(None, _("AppImage Warning"), message, QtGui.QMessageBox.Yes)
    dexcsCfdTools.removeAppimageEnvironment(env)

また、FreeCADをDaily版/AppImage版 切り替える方法は、それぞれの起動コマンドを、

/usr/bin/freecad-daily -> /etc/alternatives/freecad-daily
/usr/bin/freecad-AppImage -> /opt/freecad

として、/usr/bin フォルダ下に収納しておく。FreeCADの起動コマンドを、freecad として以下がデフォルトの状態。

/usr/bin/freecad -> /bin/freecad-daily

AppImage版に切り替えたい時は、

$ sudo rm /usr/bin/freecad
$ sudo ln -s /usr/bin/freecad-AppImage /usr/bin/freecad

として使ってもらおうというものである。少々面倒だが、どうしてもAppImage版を使いたい人というのは、ある程度オープンCAEを使い慣れた人に特化されるであろうので我慢してもらうことにする。

前へ 目次

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: ポスト処理用インタフェース機能の実装

前へ 目次 次へ