33 Star 67 Fork 63

poplee/openFoamUserManual

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
克隆/下载
11.Controlling_OpenFOAM.md 26.00 KB
一键复制 编辑 原始数据 按行查看 历史
poplee 提交于 2023-11-22 10:08 . update 11.Controlling_OpenFOAM.md.

11 控制OpenFOAM

11.1 施加控制的手段

经典的UNIX应用程序有几种常用控制配置的手段[^20]。
- 全系统的运行控制文件
这种方法的一个例子是Linux或UNIX系统中的/etc文件。对于OpenFOAM来说,这种全系统的运行控制文件位于$FOAM_ETC中,可能是home/user/OpenFOAM/OpenFOAM-3.0.0/etc。在那里,我们可以找到全局的controlDict,控制OpenFOAM在整个安装过程中的行为。
- 全系统环境变量
在Linux系统中,全系统的变量是$HOSTNAME,它是用来识别网络中的计算机的名称。这个名字对于所有登录在某台机器上的用户来说都是一样的,它可以也不应该被用户改变。对于OpenFOAM来说,这样的全系统环境变量是$FOAM_ETC、$FOAM_INST_DIR或$WM_THIRD_PARTY_DIR。这些变量对某一安装的所有用户都是一样的。
系统范围内的设置和用户定义的设置之间的区别是模糊的,当我们把OpenFOAM安装在我们的主目录下时,那么我们就是管理员和我们安装的单一用户。这种区分是针对集群而言的,集群为许多用户提供一个安装。
- 用户定义的运行控制文件
用户定义的运行控制文件的一个完美例子是用户主目录下的.bashrc文件。这个文件包含了用户特定的设置。在OpenFOAM的安装过程中,需要对这个文件进行编辑,使OpenFOAM的安装对用户可用。
- 用户设置的环境变量
这些并不十分常见。例如在Linux或UNIX系统中,用户可能会设置$EDITOR变量,然后应用程序,可能会调用编辑器,可以简单地查询这个变量来调用用户的首选编辑器。
- 在命令行上传递的开关和参数
这些都是非常常见的。一个广为人知的例子是命令行参数-h、-help或--help,用于显示应用程序的使用摘要。

以上列出的控制手段的顺序是从系统级到每个执行级的递减。有了在五种机制中选择控制应用程序行为的自由,软件开发者就有了明智选择配置方法的责任。没有人愿意在每次运行应用程序时都传递相同的、从未改变过的命令行参数。

11.1.1 变量

变量是存储重复需要的信息的最佳位置。例如,在每个需要知道OpenFOAM在系统中安装位置的运行控制文件中指定OpenFOAM的安装目录是没有意义的,相反,在OpenFOAM的一个全局运行控制文件中定义了一个变量$FOAM_INST_DIR。在所有需要知道安装路径的其他运行控制文件中,都会使用这个变量。这样,就避免了信息的冗余。想象一下,如果一些信息被存储在多个地方,而这些信息又发生了变化,那么可怜的集群管理员就会很难受。希望你能找到并更新这些数据的所有位置。

变量提供了使用相同名称(即变量)的自由,无论实际信息是什么。OpenFOAM总是安装在$FOAM_INST_DIR,无论是/home/user/OpenFOAM、/opt/OpenFOAM还是/home/user/Desktop/important_softWare。

11.1.2 字典

字典是OpenFOAM的运行控制文件。OpenFOAM的大部分控制都设置在所谓的字典中。一个重要的字典是controlDict文件。字典提供了一种方便的方法来存储任意大小的结构化信息,这在使用变量或命令行参数时是几乎不可能的。想象一下,每次运行求解器时都要输入 controlDict 的所有内容。

全局字典和局部字典的区别使我们在调试一个案例的设置时不至于弄乱OpenFOAM的安装。

11.1.3 命令行参数

除了字典之外,还有一些命令行参数来控制OpenFOAM求解器和工具的某些方面。命令行参数是向应用程序传递信息的最佳方式,这些信息可能会在一次运行中发生变化,即使情况相同也是如此。
一个例子是-parallel命令行开关。无论我们是用单个进程还是并行运行一个案例,该案例的设置都是不变的。因此,通过一个案例文件告诉求解器以并行方式运行是不一致的。

命令行开关是命令行参数,它不需要任何额外的信息。在求解器名称中添加-help,就足以使求解器显示其使用摘要。另一方面,一个命令行参数需要额外的信息。一个例子是用于告诉后处理工具对哪些时间步骤采取行动的-time参数。在没有任何进一步信息的情况下单独传递-时间,会让工具毫无头绪,并且会发出错误信息。

11.2 字典的语法

字典需要遵守一定的格式。OpenFOAM用户指南指出,字典遵循的语法与C++语法相似。

*文件格式遵循C++源代码的一些一般原则*。  

在字典中输入数据的最基本格式是“键-值”对。键值对的值可以是任何种类的数据,例如一个数字、一个列表或一个字典。

11.2.1 关键词--香蕉测试

由于OpenFOAM没有提供图形化的菜单,在某些情况下,允许的条目是不可见的,不像图形化程序那么一目了然。如果一个键期待一个有限的数据集的值,那么用户可以输入一个肯定不适用的值,比如说“banana”。然后,OpenFOAM就会产生一个错误信息,其中就会有一个允许的条目列表。

--> FOAM FATAL IO ERROR :
expected startTime , firstTime or latestTime found ’ banana ’

清单37:错误的关键字——香蕉测试

清单37显示了当值banana被分配给控制模拟开始时间的startFrom键时显示的错误信息。该错误信息包含一个说明,其格式为:预期的X、Y或Z发现ABC。
如果在一个字典中,有几个键值对是错误的,只有第一个键值对会产生错误,因为OpenFOAM会中止所有进一步的操作。

陷阱:假设和默认值。 在某些情况下,香蕉测试的行为与预期不同。清单38显示了当香蕉测试被用于controlDict的控制压缩时,OpenFOAM返回的警告信息。关于这个控件的描述见第11.3.2节。在这种情况下,OpenFOAM并没有中止,而是继续运行这个案例。OpenFOAM没有返回错误信息并退出,而是简单地假设了一个值来代替无效的条目。

--> FOAM Warning :
From function IOstream :: compressionEnum ( const word &)
in file db / IOstreams / IOstreams / IOstream .C at line 80
bad compression specifier ’ banana ’, using ’ uncompressed ’

清单38:失败的香蕉测试

11.2.2 必须的和可选的设置

有些设置是求解器所期望的。如果它们不存在,OpenFOAM将返回一个错误信息。其他的设置有一个默认值,如果用户没有指定值,就会使用这个值。从这个意义上讲,设置可以分为强制性设置和可选设置。

由于强制性的设置在没有设置的情况下会导致错误,所以只有在所有的强制性设置都完成的情况下才能运行模拟。

关于错误
+ 当没有进行强制性设置时,会有一个错误。
+ 如果省略了一个可选的设置(这是必须的),则没有错误信息。所有的可选控件都有一个默认值,并会替换掉错误信息。
+ 如果做了一个设置,而这个设置是不需要的,则没有错误信息。解算器会简单地忽略它。因此,在controlDict中定义可变时间步长并不一定意味着模拟是以可变时间步长进行的,例如,如果使用icoFoam(一个固定时间步长的求解器)。
+ 有时错误信息指向一个实际上没有问题的关键字的设置。见第11.2.3节。

参见第57.3节关于从字典中读取关键字的详细讨论--包括对一些源代码的彻底考察。

11.2.3 陷阱:分号(;)

与C++类似,行与行之间以分号结束。清单39显示了0-目录下的文件U1的内容。定义出口的边界条件(BC)的行没有正确结束。清单40显示了引发的错误信息。这条错误信息没有提到出口,而是提到了walls--关键词墙壁是未定义的。墙的边界条件的定义在出口定义之后。其中一个原因可能是,OpenFOAM在缺少分号导致语法错误后终止了对文件的读取,因此墙壁的边界条件仍未定义。
这个例子表明,如果按字面意思理解,错误信息有时意义不大。这个错误是在出口的边界条件的定义上发生的。如果只检查墙体边界的定义,错误信息的原因仍然不清楚,因为墙体边界条件的定义是完全正确的。

dimensions [0 1 -1 0 0 0 0];
internalField uniform (0 0 0) ;
boundaryField
{
    inlet
    {
        type fixedValue ;
        value uniform (0 0 0.03704) ;
    }
    outlet
    {
        type zeroGradient
    }
    walls
    {
        type fixedValue ;
        value uniform (0 0 0) ;
    } 
}

清单39:边界条件的定义中缺少分号

--> FOAM FATAL IO ERROR :
keyword walls is undefined in dictionary "/ home / user / OpenFOAM / user -2.1. x/ run / twoPhaseEulerFoam
    / case /0/ U1 :: boundaryField "
file : / home / user / OpenFOAM / user -2.1. x/ run / twoPhaseEulerFoam / case /0/ U1 :: boundaryField from line
    25 to line 47.
From function dictionary :: subDict ( const word & keyword ) const
in file db / dictionary / dictionary . C at line 461.
FOAM exiting

清单40:由于缺少分号而导致的错误信息

11.2.4 开关

除了键值对,还有开关。这些开关启用或禁用一个功能或特性。因此,它们只能有一个逻辑值。

允许的值是:on/off, true/false 或者 yes/no。关于有效条目的详细讨论见第57.4.1节。

11.3 controlDict

在这个字典里有关于时间步长、模拟时间或向硬盘写数据的控制。

controlDict 中的设置不仅可以被求解器读取,也可以被各种实用工具读取。例如,一些网格修改工具会服从关键字startFrom和startTime的设置。在使用一些工具进行预处理时,必须牢记这一点。

11.3.1 时间控制

在本节中,列出了与时间步长和模拟时间有关的最重要的控制。注意这个列表并不完整。

startFrom控制仿真的开始时间。这个关键词有三个可能的选项。
-firstTime 仿真从时间目录集合中最早的时间步长开始。
-startTime 仿真从startTime关键字条目所指定的时间开始。
-latestTime 仿真从时间目录集合中的最新时间步长开始。
startTime 仿真开始的起始时间。只有当startFrom startTime被指定时才相关。否则该条目将被完全忽略[^19]。
stopAt控制模拟的结束。可能的值是{endTime, nextWrite, noWriteNow, writeNow}。
-endTime当达到指定时间时,模拟就会停止。
-writeNow 仿真在当前时间步骤完成后停止,当前的结果被写入磁盘。
endTime 仿真的结束时间
deltaT如果仿真使用固定时间步长,则为仿真的时间步长。在可变时间步长的仿真中,这个值定义了初始时间步长。
adjustTimeStep控制时间步长是固定的还是可变的[^20]。如果省略了这个关键词,默认情况下会假定一个固定的时间步长。如果使用可调时间步长,那么时间步长∆t通过库朗数准则来控制。关于库朗数及其对时间步长的影响,请参见第57.6.4节。
runTimeModifiable控制OpenFOAM是否应该在每个时间步长的开始时读取某些字典(比如controlDict)。如果这个选项被启用,可以通过将stopAt设置为{nextWrite, noWriteNow, writeNow}中的一个值来停止模拟,见第12.2节。
maxCo当仿真运行的时间步长可调时,我们可以指定最大的库朗数,它被用来对时间步长施加上限。
maxDeltaT当我们用可调整的时间步长运行模拟时,我们可以为最大时间步长提供一个手动的、硬性的上界。这一设置限制了时间步长,而不考虑其他允许更大时间步长的条件,例如库朗数准则。

在第57.6节中讨论了关于时间步长控制的更详细的内容。

11.3.2 数据写入

在controlDict中,可以找到关于数据写入的控制。通常情况下,没有必要保存模拟的每个时间步骤。OpenFOAM提供了几种方法来定义如何以及何时将数据写入硬盘。
writeControl控制向文件写入数据的时间。允许的值是{adjustableRunTime, clockTime, cpuTime, runTime, timeStep}。
-runTime当这个选项被选中时,那么每隔一秒就会写入数据。这个选项对时间步长没有影响。因此,写入数据的时间间隔可能/不会与writeInterval中的条目完全一致,即对于1s的时间间隔,数据可能在t = 1.0012, 2.0005, ...s写入。
-adjustableRunTime该选项允许求解器调整时间步长,以便在每一个writeInterval秒内都能写入数据。这个选项对时间步长施加了一个上限。两次写入数据的实例之间必须至少有5个时间步长,即∆t ≤ 0.2 * writeInterval。
-时间步数数据每隔writeInterval时间步数被写入。在这种情况下,writeInterval是一个整数,而不是一个时间。
writeInterval 一个控制数据写入时间间隔的值。这个值从分配给writeControl的值中获得其意义。
writeFormat 控制数据如何被写入硬盘。有可能写入文本文件或二进制文件。因此,选项是{ascii, binary}。
writePrecision控制写到硬盘上的数值的精度。
writeCompression控制是否对写入的文件进行压缩。默认情况下,压缩被禁用。当它被激活时,所有写入的文件都用gzip进行压缩。
timeFormat控制用于写入时间步骤文件夹的格式。
timePrecision指定小数点后的数字。默认值是6。
purgeWrite这个设置控制是否清除旧的时间步骤。默认值是0,这意味着不进行清除。对于启用清除旧时间步长,有效值是正整数。如果启用非零值N,则只保留最后的N个时间步骤。一旦模拟将N个时间步数写入磁盘,每保存一个新的时间步数,最旧的时间步数将被删除。初始时间步长不受影响,将始终保持在算例中保持一致[^21]。

陷阱:时间精度
OpenFOAM能够在需要时自动增加timePrecision参数的值,例如,由于(动态)时间步长的减少[^22]。这通常是在模拟发散和(动态)时间步长减少的情况下发生的。然而,没有发散的模拟也可能产生增加时间精度的需要。

Increased the timePrecision from 6 to 7 to distinguish between timeNames at time 4.70884

清单41:在自动增加timePrecision值的情况下,解算器的示范性输出。

如果一个提高了时间精度的模拟要从最新的时间步长重新开始或继续进行,那么所选择的时间精度可能不足以表示现在的时间步长值,即timePrecision为3不足以表示t=0.1023 s的最新时间步长值,OpenFOAM将应用四舍五入以达到所选择的逗号后面的数字。因此,OpenFOAM将无法找到时间t=0.102 s的文件。

这种行为对于一个不知情的用户来说是很难发现的。在这种情况下,检测的唯一线索在于逗号后面的第四位数字,它只出现在时间步长目录的名称中,而不是在OpenFOAM所查找的timeName中。清单42显示了相应的错误信息和案例目录的列表。读者可以自行决定这是否是一个容易发现的错误。作者花了一些时间,这促使他在这个错误和错误行为的小册子中阐述了这个问题。

--> FOAM FATAL IO ERROR : cannot find file
file : / home / user / OpenFOAM / user -2.3. x/ run / icoFoam / cavity /0.102/ p at line 0.
From function regIOobject :: readStream ()
in file db / regIOobject / regIOobjectRead .C at line 73.
FOAM exiting
user@host :∼/ OpenFOAM / user -2.3. x/ run / icoFoam / cavity$ ls
0 0.1023 constant system
user@host :∼/ OpenFOAM / user -2.3. x/ run / icoFoam / cavity$

清单42:在上一次模拟运行中自动增加timePrecision值而导致的错误示例。由于OpenFOAM无法找到正确的时间步长,我们无法重新启动仿真。

因此,在这种情况下,除了一般的模拟设置外,模拟的时间步长由functionObject控制。这可能会导致一种情况,当函数对象的写入间隔足够小时,运行仿真时不会达到允许的最大库朗数。这是正常的行为,因为有多个标准来决定时间步长的大小,比喻说:价低者获胜(the lowest bidder wins)。

有点坑爹:时间步长控制和writeInterval。 OpenFOAM手册上说,当我们选择runTime作为写控制时,数据会在每一个writeInterval秒写到磁盘。然而,这确实允许出现一种奇怪的情况,即写入间隔实际上(远远)大于writeInterval。
由于我们选择了runTime作为写入控制,writeInterval对时间步长没有影响。如果时间步长控制允许时间步长大于writeInterval,例如通过库朗数准则,那么OpenFOAM就会照办。在这种情况下,在每一个时间步长之后,OpenFOAM都会注意到,从上次写到磁盘的时间大于writeInterval。因此,现在是再次写入磁盘的时候了。
如果我们不想要这种行为,那么我们需要选择可调整的运行时间作为我们的写入控制,因为这样的话,时间步长将与writeInterval进行检查。

与其说是陷阱,不如说是观察:时间步长控制和writeInterval
如果我们选择 adjustableRunTime作为我们的写入控制,那么时间步长会被调整到符合writeInterval所设定的间隔。据观察,在这种情况下,时间步长的调整遵循以下边界。
$$ \delta t \le \frac{writeInterval}{5} $$ 因此,OpenFOAM在将数据写入磁盘之间至少要执行5个时间步骤。这种行为可能会让人困惑,而且文档中也没有解释。而且,你信任的作者也没能从OpenFOAM的源代码中推断出这种行为。

11.3.3 加载附加库

额外的库可以通过controlDict中的指令来加载。清单43显示了如何包含一个外部库(在这里是一个OpenFOAM中没有的湍流模型)。这个模型可以在https://github.com/AlbertoPa/dynamicSmagorinsky/下载。

libs ( " libdynamicSmagorinskyModel . so " ) ;

清单43:加载附加库;controlDict条目

请注意,清单43中的一行是一个关键字(libs),后面是一个列表式条目。因此,关键字和开头的小括号之间的空格是至关重要的,因为空格是用来分隔关键字和它的值的。

11.3.4 函数

函数,或者在OpenFOAM中称为functionObjects,提供了各种各样的额外功能,例如探测值或运行时后处理。见第49节。
函数可以在运行时启用或禁用。

11.3.5 外包一个字典

有些定义可以外包在一个单独的字典中,例如,探测功能对象的定义。

全包

在这种情况下,探针被完全定义在 controlDict 中。

functions
{
    probes1
    {
        type probes ;
        functionObjectLibs (" libsampling . so ") ;
        fields
        ( 
		    p
			U
        );
        outputControl outputTime ;
        outputInterval 0.01;
		
        probeLocations
        (
            (0.5 0.5 0.05)
        );
    } 
}

清单44: controlDict中探针的定义

单独的probesDict。 在这种情况下,探针的定义是在一个单独的文件中完成的-probesDict。在controlDict中,这个字典的名字被分配给关键词字典。这个字典必须位于案例的系统目录下。不需要把这个字典的路径指定给这个关键词。

functions
{
	probes1
	{
		type probes ;
		functionObjectLibs (" libsampling . so ") ;
		
		dictionary probesDict ;
	} 
}

清单45:探针的外部定义;ControlDict中的条目

fields
( 
	p
	U
);

outputControl outputTime ;
outputInterval 0.01;

probeLocations
(
	(20.5 0.5 0.05)
);

清单46:文件probesDict中的探针定义

全部外部引入 也有可能将functionObject的整个定义移到一个单独的文件中。在这种情况下,会使用#include这个宏。这个宏类似于C++中的预处理器宏。

functions
{
	# include " cuttingPlane "
}

清单47:完全外部定义的functionObject;controllDict中的条目

cuttingPlane
{
	type surfaces ;
	functionObjectLibs (" libsampling . so ") ;
	outputControl outputTime ;
	
	surfaceFormat raw ;
	fields ( alpha1 );
	
	interpolationScheme cellPoint ;
	surfaces
	(
		yNormal
		{
			type cuttingPlane ;
			planeType pointAndNormal ;
			pointAndNormalDict
			{
				basePoint (0 0.1 0) ;
				normalVector (0 1 0) ;
			}
			
			interpolate true ;
		}
	);
}

清单48:在一个单独的名为cuttingPlane的文件中定义一个cuttingPlane函数对象

11.3.6 陷阱

timePrecision 如果时间精度不够,OpenFOAM会发出一条警告信息,并增加时间精度而不中止运行中的仿真。

清单49显示了这样一条警告信息。仿真时间超过了100秒,OpenFOAM认为时间精度已经不够了。

--> FOAM Warning :
From function Time :: operator ++()
in file db / Time / Time .C at line 1024
Increased the timePrecision from 6 to 13 to distinguish between timeNames at time 100.001

清单49:警告信息:自动提高时间精度

提高时间精度的一个副作用是模拟时间的轻微偏移。该模拟的时间步长为0.001秒,时间步长每0.5秒写一次。在清单50中可以清楚地看到,时间步长的文件夹名称显示了这种偏移。这种对时间步长文件夹名称的影响是作者注意到自动提高时间精度后的结果。

然而,自动提高时间精度对模拟没有负面影响。本节的目的是解释这种影响的原因。

101.5000000002
101.0000000002
100.5000000002
100
99.5
99
98.5

清单50:提高时间精度后的时间步长折叠器

11.4 字典的运行时间修改

如果开关runTimeModifiable被设置为true,on或yes;如果一个文件发生了变化,某些文件(例如controlDict或fvSolution)会被重新读取。通过这种方式,例如,在模拟过程中可以改变写入间隔时间。如果OpenFOAM检测到运行时的修改,它会在终端机上发出一个信息。

regIOobject :: readIfModified () :
Re - reading object controlDict from file "/ home / user / OpenFOAM / user -2.1. x/ run /
multiphaseEulerFoam / bubbleColumn / system / controlDict "

清单51:在求解器运行时检测到controlDict的修改情况

11.5 fvSolution字典

文件fvSolution包含所有控制求解器和求解算法的设置。这个文件必须包含两个字典。第一个控制求解器,第二个控制求解算法。

11.5.1 解算器控制

解算器字典包含决定解算器工作的设置(例如,解算方法、公差等)。

11.5.2 解算方法控制

控制求解算法的字典是以求解算法本身命名的。例如,控制 PIMPLE 算法的字典的名称是 PIMPLE。注意,这个字典的名字是大写字母,与其它大多数字典不同。 清单 52 显示了一个 PIMPLE 字典的例子。关于 PIMPLE 算法的详细讨论见第 42.2 节。

PIMPLE
{
	nOuterCorrectors 1;
	nCorrectors 2;
	nNonOrthogonalCorrectors 0;
	pRefCell 0;
	pRefValue 0;
}

清单52:PIMPLE字典

11.6 命令行参数

OpenFOAM的求解器和工具可以通过一组命令行参数来控制。其中有些参数是所有或许多可执行程序所共有的,有些参数可能是某个工具所特有的。

11.6.1 获取帮助。-help

最重要的命令行参数是-help。这个参数对OpenFOAM的所有求解器和工具都是通用的,它显示的是各自工具的概要。

11.6.2 获得控制:-dict

某些工具希望能找到一个包含必要信息的特定字典。通过-dict选项,用户可以告诉可执行文件,在哪里寻找字典。据作者所知,所有期待字典的工具都假定有一个默认的位置和文件名。例如,在老版本的OpenFOAM blockMesh中,期望在案例根目录下的constant/polyMesh子目录中找到一个名为blockMeshDict的字典,在新版本中,它也会检查系统目录。如果使用人选择把包含的字典放到一个不同的文件夹里,需要用-dict命令行参数来传递字典的路径。

没有控制字典
由-help显示的帮助摘要,在某些情况下,对-dict选项的描述是:从指定的位置读取控制字典。然而,用 -dict 选项指定的字典不是 controlDict。因此,所有进入 controlDict 的条目都需要进入 controlDict。对于某些工具来说,-dict 选项的描述似乎有点含糊不清。在这种情况下,控制字典的意思是控制这个特定工具的字典,比如 blockMeshDict 控制 blockMesh 或 snappyHexMeshDict 控制 snappyHexMesh。

[^19]如果模拟被设置为从firstTime或latestTime开始,这个关键字可以被省略,或者这个关键字的值可以是任何东西--startTime的香蕉测试不会导致错误,如果模拟从一个特定的开始时间开始,会出现什么情况?
[^20]这个关键词只对具有可变时间步长的求解器很重要。固定时间步长的求解器会简单地忽略这个控制,不显示任何警告或错误信息。
[^21]在文件TimeIO.C中,我们看到,每次到达写入时间时,当前的时间步长被添加到一个FIFO栈中。随后,堆栈的大小与purgeWrite值进行检查。如果堆栈比较大,那么就会有一个项目被删除。
[^22]在具有固定时间步长的模拟中,timePrecision值的动态增加表明时间精度不足以充分表示时间步长的设置。这将导致在第一个时间步长被写入磁盘后时间精度的自动增加。也就是说,如果∆t不能用逗号后的时间精度数字表示,那么t1 + ∆t也不能被表示。因此,t1和t1 + ∆t会得到相同的时间名称,因此无法区分。关于这个问题的更多实现细节,请参见第57.6.3节。

马建仓 AI 助手
尝试更多
代码解读
代码找茬
代码优化
1
https://gitee.com/poplee/openFoamUserManual.git
git@gitee.com:poplee/openFoamUserManual.git
poplee
openFoamUserManual
openFoamUserManual
master

搜索帮助

344bd9b3 5694891 D2dac590 5694891