使用 App 开发器导出网格和解

作者 Walter Frei

2016年 4月 6日

您是否曾想过将 COMSOL Multiphysics 中的网格和分析数据导出到文本文件?在把信息传递到另一个软件程序或电子表格中时,或许会提出这样的需求,而且希望能够基于其他待用工具的需要,对数据的写入格式进行准确适当的自定义。有了“App 开发器”,我们能轻松地完成这项操作。下面来了解一下吧!

跨软件平台协作

假设您正在处理一个 COMSOL Multiphysics 模型,对一个系统执行了热分析,同时还在与另一个小组的成员合作研究其他类型的分析。但是其他同事没有您那么好运,不会使用 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 字符后的第一个字段是节点编号,它是一个任意数,第二、第三和第四个字段是该节点的 xyz 轴位置,所以第一个节点位于全局坐标系的原点。上面的示例总共有一千个节点。

接下来,每个节点位置的温度计算数据被写在单独的一行中:

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 的界面。
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");

让我们逐行解释一下这个方法。

  1. 创建一个字符串缓冲区,存储待写入文件的数据。
  2. 从模型中提取所有网格顶点的位置,并将数据存入二维双精度型数组。
  3. 因为数据将被写入文本文件,所以将网格位置的数字数据转换为字符串数据。
  4. 开始 for 循环,对所有节点位置进行顺序处理。
  5. 每个节点为一行,添加到字符串缓冲区,其中包含节点索引和 xyz 轴位置。
  6. 停止节点上的 for 循环。
  7. 建立插值特征,提取之前已被提取的节点位置数据。
  8. 设置用于计算节点的表达式。在本例中,变量 T 表示我们正在提取的温度。
  9. 利用插值特征来提取全部温度数据,并存储在三维双精度数组中。
  10. 删除已经不需要的插值功能。
  11. 针对全部提取数据建立一个 for 循环。由于我们只提取一个字段(温度),并假设模型只有一组解,所以只需要给数组的最后一个维度编制索引。
  12. 添加数据行到字符串缓冲区,行中包括节点编号和该节点上的温度值。
  13. 停止所有输出数据的 for 循环。
  14. 初始化用于存储单元索引数据的空二维数组。
  15. 从模型中提取四面体单元的数量。
  16. 检查是否还有待导出的四面体单元。
  17. 从模型中提取四面体单元的连接数据。
  18. 建立处理所有四面体单元的 for 循环。
  19. 每个四面体单元为一行,添加到字符串缓冲区,其中包含单元编号和节点编号。
  20. 停止处理所有四面体单元的循环。
  21. 结束检查是否存在任何四面体单元的 if 语句。

第 22 至 42 行简单地重复了第 15 至 21 行的功能来处理其他单元类型。请注意,单元编号会基于之前的单元编号递增。此外,在整个方法中,所有节点的索引都增加了一,这是因为 COMSOL Multiphysics 是从零开始进行索引。但是在所需的输出格式中,我们要从一开始进行全部节点和单元的索引。在 43 行,整个字符串缓冲区被转换成字符串,然后写入文件。在第 44 和 45行,在消息日志中打印一些相关的消息。

导出的文件名称为 output.txt,可在用户目录中获取。如下方截图所示,您可以打开文件菜单 > 首选项 > 文件 > App 文件,在软件首选项中指定磁盘中的目录位置,也可以根据需要更改目录。

指定输出文件的位置。
在软件首选项中指定输出文件的位置。

至此,我们创建了一个完整的方法。下方截图显示了 App 的当前运行情况。

专用于导出网格与解的 App。
App 报告已写入文件的内容。

使用“App 开发器”导出数据的结语

在上文中,我们演示了如何创建一个非常简单的 App,它的作用是导出 COMSOL Multiphysics 中稳态热仿真的网格和结果。您可以直接把数据从 App 复制粘贴到文本文件或电子表格中。当然,App 能够引入更多复杂性,包括:

  • 从瞬态仿真中导出多组表示不同载荷工况或和不同时间的数据。
  • 编制数据格式,将其转换为固定格式的文件类型。
  • 导出高阶单元类型和插值方法。
  • 导出向量数据或者单元之间的不连续数据。

当然,我们不会马上解决所有情况,不过如果您有兴趣将上述修改添加到自己的定制化 App 中,可参考下列资源:

在查找关于构建特定类型 App 的信息,或者有其他需要解答的建模问题?请联系我们

博客分类


评论 (3)

正在加载...
hansu zhang
hansu zhang
2018-12-15

您好,
请问对数据规模比较大的网格,如果字符串缓冲区不足以容纳节点索引和 xyz 轴位置,要怎么解决这个问题呢,或者说如何定义字符串缓冲区的大小呢?
谢谢

多大 于
多大 于
2023-12-29

那如何在app中,直接导出stp格式的三维图呢

Lei Cao
Lei Cao
2024-01-03 COMSOL 员工

于大多, 您好!

感谢您的评论。
您可以通过 App 开发器中录制代码功能获取导出几何的 COMSOL API 语句,再关联到按钮执行命令中。

如果有进一步问题,建议您联系COMSOL的技术支持团队:
在线支持中心:cn.comsol.com/support
Email: support@comsol.com
谢谢!

浏览 COMSOL 博客