您是否曾想过将 COMSOL Multiphysics 中的网格和分析数据导出到文本文件?在把信息传递到另一个软件程序或电子表格中时,或许会提出这样的需求,而且希望能够基于其他待用工具的需要,对数据的写入格式进行准确适当的自定义。有了“App 开发器”,我们能轻松地完成这项操作。下面来了解一下吧!
跨软件平台协作
假设您正在处理一个 COMSOL Multiphysics 模型,对一个系统执行了热分析,同时还在与另一个小组的成员合作研究其他类型的分析。但是其他同事没有您那么好运,不会使用 COMSOL Multiphysics,却又需要访问您的模拟结果。幸运的是,您可以将分析数据快速地转换为特定的文本文件格式,让同事们可以使用他们的软件工具来读取数据。
一个简单的、拥有不同网格类型的 COMSOL Multiphysics 热模型。
假设您正在对模型执行稳态热分析,且模型中包含四面体、金字塔、棱柱和六面体(砖)网格,如上图所示。再进一步假设,要读取数据的软件工具要求使用线性离散化,这意味着我们必须导出每个单元角节点(顶点)上的温度,并利用温度的线性插值来计算单元内的温度场。综上所述,我们需要导出所有顶点位置、每个顶点的温度数据,以及某个顶点定义每个单元时采用的描述和方式。
当然,此类信息有许多不同的写入方式。为了符合本文的目的,我们选择简单的逗号分隔的格式,其通用范例如下。
N, 1, 0.0, 0.0, 0.0 ... N, 1000, 10.0, 10.0, 10.0 D, 1, 332.0 ... D, 1000, 343.0 TET, 1, 2, 4, 6, 3 ... TET, 100, 42, 43, 41, 45 PYR, 101, 47, 48, 41, 40, 44 ... PRISM, 201, 66, 67, 65, 72, 74, 73 ... HEX, 301, 81, 82, 83, 84, 91, 92, 93, 94 ...
下面,我们将解读不同行所表达的信息。所有行均以文本字符串开头,说明该行记录了哪一类信息,后面的信息由逗号分隔成不同字段。第一行提供了顶点(节点)位置的信息:
N, 1, 0.0, 0.0, 0.0
N
字符后的第一个字段是节点编号,它是一个任意数,第二、第三和第四个字段是该节点的 x、y 和 z 轴位置,所以第一个节点位于全局坐标系的原点。上面的示例总共有一千个节点。
接下来,每个节点位置的温度计算数据被写在单独的一行中:
D, 1, 332.0
其中 D
字符后的第一个字段是节点编号,第二个字段是此节点位置上的温度。
其余行提供了单元及其如何由节点定义的相关信息。我们先看第一个单元的定义:
TET, 1, 2, 4, 6, 3
这一行定义的是四面体(TET
)单元。第一个字段是单元编号,后方的四个字段告诉我们哪些节点定义了四面体。在这里,节点 2、4、6 和 3 分别对应单元的 1 至 4 节点。这类信息又称作单元连接。要理解这个概念,可以参考下方的单元图示。
四面体单元由四个节点定义。
在四面体单元的示例中,节点 2、4 和 6 是前三个节点。如果我们使用右手法则,按照顺序就可以从这三个节点得到指向第四个节点(节点编号为 3)的方向向量。下图显示了所有三维单元类型的单元排序。
四种不同类型的三维单元的单元编号惯例。
既然已经说明了需要导出到文本文件的数据格式,下面我们将详细介绍“App 开发器”如何完成导出任务。
使用“App 开发器”导出数据
我们选择利用现有的三维模型来开发 App,如前文所述,模型中已经计算出了稳态温度场。为了开发 App,我们要切换到“App 开发器”,然后在这里定义图形化用户界面,并在后台写入数据处理代码。App 的界面简明扼要,只需要一个按钮 去调用方法 和用于显示某些信息的消息日志。在这个 App 中只加入了一项功能:当用户单击按钮后,上文所述的数据格式会被写入文本文件,且写入内容的摘要会显示在消息日志中。
App 有一个用于调用方法(2)的按钮(1),该方法可以将网格和解数据写入文件,并将部分统计信息写入消息日志(3)。
如下所示,此方法包含了全部数据处理代码,而且添加了行号,文本字符串被标记成了红色。
1 StringBuffer FileB = new StringBuffer(); 2 double[][] d_Vtx = model.mesh("mesh1").getVertex(); 3 String[][] s_Vtx = toString(d_Vtx); 4 for (int m = 0; m < s_Vtx[0].length; m++) { 5 FileB.append("N, "+(m+1)+", "+s_Vtx[0][m]+", "+s_Vtx[1][m]+", "+s_Vtx[2][m]+"\n"); 6 } 7 model.result().numerical().create("interp", "Interp").setInterpolationCoordinates(d_Vtx); 8 model.result().numerical("interp").set("expr", "T"); 9 double[][][] AllData = model.result().numerical("interp").getData(); 10 model.result().numerical().remove("interp"); 11 for (int m = 0; m < AllData[0][0].length; m++) { 12 FileB.append("D, "+(m+1)+", "+AllData[0][0][m]+"\n"); 13 } 14 int[][] Ei; 15 int numTets = model.mesh("mesh1").getNumElem("tet"); 16 if (numTets > 0) { 17 Ei = model.mesh("mesh1").getElem("tet"); 18 for (int m = 0; m < numTets; m++) { 19 FileB.append("TET, "+(m+1)+", "+(Ei[0][m]+1)+", "+(Ei[1][m]+1)+", "+(Ei[2][m]+1)+", "+(Ei[3][m]+1)+"\n"); 20 } 21 } 22 int numPyrs = model.mesh("mesh1").getNumElem("pyr"); 23 if (numPyrs > 0) { 24 Ei = model.mesh("mesh1").getElem("pyr"); 25 for (int m = 0; m < numPyrs; m++) { 26 FileB.append("PYR, "+(m+1+numTets)+", "+(Ei[0][m]+1)+", "+(Ei[1][m]+1)+", "+(Ei[2][m]+1)+", "+(Ei[3][m]+1)+", "+(Ei[4][m]+1)+"\n"); 27 } 28 } 29 int numPrisms = model.mesh("mesh1").getNumElem("prism"); 30 if (numPrisms > 0) { 31 Ei = model.mesh("mesh1").getElem("prism"); 32 for (int m = 0; m < numPrisms; m++) { 32 FileB.append("PRISM, "+(m+1+numTets+numPyrs)+", "+(Ei[0][m]+1)+", "+(Ei[1][m]+1)+", "+(Ei[2][m]+1)+", "+(Ei[3][m]+1)+", "+(Ei[4][m]+1)+", "+(Ei[5][m]+1)+"\n"); 34 } 35 } 36 int numHexes = model.mesh("mesh1").getNumElem("hex"); 37 if (numHexes > 0) { 38 Ei = model.mesh("mesh1").getElem("hex"); 39 for (int m = 0; m < numHexes; m++) { 40 FileB.append("HEX, "+(m+1+numTets+numPyrs+numPrisms)+", "+(Ei[0][m]+1)+", "+(Ei[1][m]+1)+", "+(Ei[2][m]+1)+", "+(Ei[3][m]+1)+", "+(Ei[4][m]+1)+", "+(Ei[5][m]+1)+", "+(Ei[6][m]+1)+", "+(Ei[7][m]+1)+"\n"); 41 } 42 } 43 writeFile("user:///output.txt", FileB.toString()); 44 message("Data written to file output.txt in the user directory."); 45 message(s_Vtx[0].length+" Nodes\n"+numTets+" Tetrahedral Elements\n"+numPyrs+" Pyramid Elements\n"+numPrisms+" Prismatic Elements\n"+numHexes+" Hexahedral Elements\n");
让我们逐行解释一下这个方法。
- 创建一个字符串缓冲区,存储待写入文件的数据。
- 从模型中提取所有网格顶点的位置,并将数据存入二维双精度型数组。
- 因为数据将被写入文本文件,所以将网格位置的数字数据转换为字符串数据。
- 开始 for 循环,对所有节点位置进行顺序处理。
- 每个节点为一行,添加到字符串缓冲区,其中包含节点索引和 xyz 轴位置。
- 停止节点上的 for 循环。
- 建立插值特征,提取之前已被提取的节点位置数据。
- 设置用于计算节点的表达式。在本例中,变量 T 表示我们正在提取的温度。
- 利用插值特征来提取全部温度数据,并存储在三维双精度数组中。
- 删除已经不需要的插值功能。
- 针对全部提取数据建立一个 for 循环。由于我们只提取一个字段(温度),并假设模型只有一组解,所以只需要给数组的最后一个维度编制索引。
- 添加数据行到字符串缓冲区,行中包括节点编号和该节点上的温度值。
- 停止所有输出数据的 for 循环。
- 初始化用于存储单元索引数据的空二维数组。
- 从模型中提取四面体单元的数量。
- 检查是否还有待导出的四面体单元。
- 从模型中提取四面体单元的连接数据。
- 建立处理所有四面体单元的 for 循环。
- 每个四面体单元为一行,添加到字符串缓冲区,其中包含单元编号和节点编号。
- 停止处理所有四面体单元的循环。
- 结束检查是否存在任何四面体单元的 if 语句。
第 22 至 42 行简单地重复了第 15 至 21 行的功能来处理其他单元类型。请注意,单元编号会基于之前的单元编号递增。此外,在整个方法中,所有节点的索引都增加了一,这是因为 COMSOL Multiphysics 是从零开始进行索引。但是在所需的输出格式中,我们要从一开始进行全部节点和单元的索引。在 43 行,整个字符串缓冲区被转换成字符串,然后写入文件。在第 44 和 45行,在消息日志中打印一些相关的消息。
导出的文件名称为 output.txt
,可在用户目录中获取。如下方截图所示,您可以打开文件菜单 > 首选项 > 文件 > App 文件,在软件首选项中指定磁盘中的目录位置,也可以根据需要更改目录。
在软件首选项中指定输出文件的位置。
至此,我们创建了一个完整的方法。下方截图显示了 App 的当前运行情况。
App 报告已写入文件的内容。
使用“App 开发器”导出数据的结语
在上文中,我们演示了如何创建一个非常简单的 App,它的作用是导出 COMSOL Multiphysics 中稳态热仿真的网格和结果。您可以直接把数据从 App 复制粘贴到文本文件或电子表格中。当然,App 能够引入更多复杂性,包括:
- 从瞬态仿真中导出多组表示不同载荷工况或和不同时间的数据。
- 编制数据格式,将其转换为固定格式的文件类型。
- 导出高阶单元类型和插值方法。
- 导出向量数据或者单元之间的不连续数据。
当然,我们不会马上解决所有情况,不过如果您有兴趣将上述修改添加到自己的定制化 App 中,可参考下列资源:
- 下载 Introduction to Application Builder 手册
- 观看视频,学习“App 开发器”和 COMSOL Server™ 的基础使用知识
- 阅读博客文章,查看仿真 App 的一系列应用案例
在查找关于构建特定类型 App 的信息,或者有其他需要解答的建模问题?请联系我们。
评论 (5)
hansu zhang
2018-12-15您好,
请问对数据规模比较大的网格,如果字符串缓冲区不足以容纳节点索引和 xyz 轴位置,要怎么解决这个问题呢,或者说如何定义字符串缓冲区的大小呢?
谢谢
多大 于
2023-12-29那如何在app中,直接导出stp格式的三维图呢
Lei Cao
2024-01-03 COMSOL 员工于大多, 您好!
感谢您的评论。
您可以通过 App 开发器中录制代码功能获取导出几何的 COMSOL API 语句,再关联到按钮执行命令中。
如果有进一步问题,建议您联系COMSOL的技术支持团队:
在线支持中心:www.denkrieger.com/support
Email: support@comsol.com
谢谢!
Yun Xiaoxiao
2024-07-24你好,如何在comsol中查看网格节点编号呢
Hao Li
2024-08-02 COMSOL 员工目前软件中暂时没有开放网格节点编号,暂时无法在软件中直接查看。