4. 基于文本模板的K文件参数化生成

在上一节中我们已经看到,LS-DYNA 的 K 文件本质上是一组按固定字段格式排列的文本卡片。只要卡片的关键字和字段位置不变,改变其中的数值并不会破坏文件的可读性。这一特性为“文本模板 + 占位符替换”的参数化生成方式提供了基础:我们不需要调用 LS-PrePost 或任何 CAE 前处理器,仅用一段脚本就能批量产出成百上千个 K 文件。

本节以一个最简单的悬臂梁结构为对象,完成一次完整的参数化建模任务。目标不是把梁建得多复杂,而是把“模板—参数—批量生成—可运行验证”这条链路走通,让后续更复杂的参数化优化有可复用的骨架。

一、为什么选择文本模板而不是脚本建模

LS-DYNA 提供了多种参数化途径:*PARAMETER 关键字、LS-OPT 参数化、Python 调用 LS-PrePost 的 API,以及直接生成文本 K 文件。它们各有适用场景。*PARAMETER 适合在单个 K 文件内部做少量参数联动,但批量生成不同文件时仍需外部驱动;LS-PrePost API 功能强,但依赖图形环境,脚本较重;而文本模板方式最轻量——模板就是一份能正常求解的 K 文件,把需要变化的数字替换成占位符即可。

文本模板方式的另一个优势是“所见即所得”。模板文件本身可以直接提交给 LS-DYNA 求解,确认无误后再做占位符化,出错时也容易定位到具体卡片。对于梁、板、简单装配体这类结构规则、卡片数量可控的模型,这种方式效率极高。

二、悬臂梁模型的模板结构

我们采用的示例对象是一根矩形截面悬臂梁:左端固支,右端自由,自由端承受一个横向集中力。梁长 L、截面宽 b、截面高 h、弹性模量 E、密度 ρ、集中力 F 都是可参数化的量。为了让模型能真正跑起来,还需要定义壳单元或实体单元、节点、部件、材料、截面、边界条件和载荷曲线。

为控制篇幅,这里采用壳单元(*SECTION_SHELL)建模,梁沿长度方向划分 nseg 段,宽度方向划分 nw 段。节点坐标由脚本按规则计算生成,而不是手工填写。模板中保留卡片关键字和字段顺序,把节点坐标、单元连接、厚度、材料参数、载荷幅值等替换为占位符。

下面给出模板文件的核心片段(节选,实际模板需包含完整的 *NODE、*ELEMENT_SHELL、*PART、*SECTION_SHELL、*MAT_ELASTIC、*BOUNDARY_SPC_SET、*LOAD_NODE_SET、*DEFINE_CURVE 和 *CONTROL 卡片):

*KEYWORD
*NODE
{{NODE_BLOCK}}
*ELEMENT_SHELL
{{ELEMENT_BLOCK}}
*PART
cantilever beam
         1         1         1
*SECTION_SHELL
         1         1        {{THICK}}
*MAT_ELASTIC
         1     {{E}}   {{NU}}   {{RHO}}
*BOUNDARY_SPC_SET
         1         0         1         1         1         1         1         1
*LOAD_NODE_SET
         2         2        {{F}}
*DEFINE_CURVE
         1         0       1.0       1.0
         0.0       0.0
         1.0       1.0
*CONTROL_TERMINATION
      {{TEND}}
*END

可以看到,模板中真正需要替换的只有少数几个占位符:{{NODE_BLOCK}}、{{ELEMENT_BLOCK}}、{{THICK}}、{{E}}、{{NU}}、{{RHO}}、{{F}}、{{TEND}}。其中节点块和单元块是“结构化文本”,由脚本按网格规则生成,其余是标量参数。

三、占位符替换脚本的实现

脚本的核心逻辑分三步:读取模板、计算参数、替换并写出。下面用 Python 给出一个最小可用实现,重点展示节点块和单元块的生成方式。

import os

def gen_nodes(L, b, nseg, nw):
    lines = []
    nid = 1
    for i in range(nseg + 1):
        x = L * i / nseg
        for j in range(nw + 1):
            y = b * j / nw
            lines.append(f"{nid:10d}{x:10.4f}{y:10.4f}{0.0:10.4f}")
            nid += 1
    return "\n".join(lines)

def gen_elements(nseg, nw):
    lines = []
    eid = 1
    for i in range(nseg):
        for j in range(nw):
            n1 = i * (nw + 1) + j + 1
            n2 = n1 + 1
            n3 = n2 + (nw + 1)
            n4 = n1 + (nw + 1)
            lines.append(f"{eid:10d}{n1:10d}{n2:10d}{n3:10d}{n4:10d}")
            eid += 1
    return "\n".join(lines)

def render(template, params):
    text = template
    for key, val in params.items():
        text = text.replace("{{" + key + "}}", str(val))
    return text

if __name__ == "__main__":
    with open("beam_template.k", "r", encoding="utf-8") as f:
        tpl = f.read()

    cases = [
        {"L": 200.0, "b": 20.0, "nseg": 20, "nw": 2,
         "THICK": 2.0, "E": 210000.0, "NU": 0.3,
         "RHO": 7.85e-9, "F": 50.0, "TEND": 0.01},
        {"L": 300.0, "b": 20.0, "nseg": 30, "nw": 2,
         "THICK": 2.0, "E": 210000.0, "NU": 0.3,
         "RHO": 7.85e-9, "F": 50.0, "TEND": 0.01},
    ]

    os.makedirs("cases", exist_ok=True)
    for idx, p in enumerate(cases, start=1):
        p["NODE_BLOCK"] = gen_nodes(p["L"], p["b"], p["nseg"], p["nw"])
        p["ELEMENT_BLOCK"] = gen_elements(p["nseg"], p["nw"])
        out = render(tpl, p)
        with open(f"cases/beam_{idx:03d}.k", "w", encoding="utf-8") as f:
            f.write(out)
        print(f"generated cases/beam_{idx:03d}.k")

这段脚本没有使用任何第三方库,替换逻辑就是字符串替换。节点编号从 1 开始,按“先沿长度、后沿宽度”的顺序排列;单元连接按四边形壳单元的逆时针顺序给出。只要模板中的 *ELEMENT_SHELL 卡片字段顺序与生成顺序一致,模型就能被 LS-DYNA 正确读取。

四、参数化生成的数据流与验证闭环

整个流程可以概括为:参数表 → 模板渲染 → 批量 K 文件 → 提交求解 → 结果提取。下面这张图展示了这一数据流,以及每个环节中需要检查的关键点。

悬臂梁 K 文件参数化生成数据流 参数表 L, b, nseg, nw E, ν, ρ, F, TEND 文本模板 beam_template.k {{占位符}} 渲染脚本 节点/单元块生成 字符串替换 批量 K 文件 beam_001.k beam_002.k ... LS-DYNA 求解 lsdyna i=beam_xxx.k 结果提取 d3plot / nodout 检查点 节点编号连续 单元法向一致 参数调整 / 重新生成

图中左侧的“检查点”是实际工作中最容易出问题的地方。节点编号必须连续且与单元连接一致,否则 LS-DYNA 会报“node not defined”;壳单元的法向必须统一,否则在 *BOUNDARY_SPC_SET 和载荷施加时会出现方向混乱。建议在生成后先用 LS-PrePost 打开一个样本文件,检查网格和边界条件是否正确,再批量提交。

五、一个可复用的参数表

为了让批量生成可管理,建议把参数集中写在一张表里,而不是散落在脚本中。下面给出本节示例的参数表,字段和取值都属于教学示例,实际使用时按需修改。

参数名 含义 示例值 单位 备注
L梁长度200 / 300mm沿 x 方向
b截面宽度20mm沿 y 方向
nseg长度方向分段数20 / 30—决定单元数量
nw宽度方向分段数2—至少为 1
THICK壳厚度2.0mm对应 *SECTION_SHELL
E弹性模量210000MPa钢的典型值
NU泊松比0.3——
RHO密度7.85e-9t/mm³与 mm-MPa 单位制匹配
F自由端集中力50N沿 z 方向
TEND终止时间0.01s—

单位制必须统一。上表采用 mm-MPa-t 体系:长度 mm、应力 MPa、密度 t/mm³、力 N、时间 s。如果混用单位,求解结果会出现数量级错误,而且往往不会报错,只是位移或应力明显不合理。这是参数化生成中最隐蔽的坑之一。

六、生成后的检查与常见问题

批量生成完成后,不要直接全部提交。先做三项检查:第一,用文本编辑器打开一个生成文件,确认占位符已全部替换,没有残留的 {{ }};第二,检查 *NODE 和 *ELEMENT_SHELL 的条目数是否与 nseg、nw 计算一致,节点数应为 (nseg+1)×(nw+1),单元数应为 nseg×nw;第三,用 LS-PrePost 打开样本文件,查看网格形状和边界条件位置。

常见问题包括:节点编号重复导致单元连接错误;壳单元法向不一致导致载荷方向异常;*DEFINE_CURVE 的曲线编号与 *LOAD_NODE_SET 引用的编号不匹配;*CONTROL_TERMINATION 的 TEND 过小导致求解未完成。这些问题在模板阶段就可以通过固定卡片顺序和编号规则来规避。

模板文件本身应当是一份“能跑通”的 K 文件。建议先用一组固定参数手工生成一个可求解的模型,确认无误后再把数值替换成占位符。这样模板的正确性有保障,后续批量生成的风险会小很多。