Abaqus 二次开发教程(6):几何与网格——Part 类型与单元选择的隐藏代价

版本声明块

  • 工具/软件:Abaqus 2026(2026 FD01),内嵌 Python 3.10.5(2024 GA 起;此前为 Python 2.7.15,旧版本)
  • 语言/环境:Python + Abaqus Scripting Interface(ASI);mesh 模块提供 ElemType,网格方法挂在 Part 与 PartInstance 上;脚本可运行于 Abaqus/CAE 或 abaqus cae noGUI=(第 1 篇)
  • 本文目标:把单元选择从“默认优先级给的类型”变成“脚本里显式写出、并用可数数字判定的决策”

一句话结论:同一个 100 mm×10 mm×10 mm 悬臂梁、每条边 4 段结构化六面体网格,C3D8(完全积分,2×2×2 共 8 个积分点)给出 64 单元、144 自由度且弯曲主导时偏刚,C3D8R(减缩积分,每单元 1 个积分点加沙漏控制)给出同样 64 单元、192 自由度但弯曲结果更接近欧拉-伯努利理论解;代价是减缩积分允许零能沙漏模态——【经验法则】沙漏能量超过内能的 1% 即应重新划分网格或换单元类型。

〇、本篇要解决的认知问题

  1. Abaqus/CAE 里创建 Part 时的 type(DEFORMABLE_BODY、DISCRETE_RIGID_BODY、ANALYTICAL_RIGID_BODY)在求解链路上到底改变了什么?为什么刚体 Part 不能靠它获得材料刚度?
  2. C3D8 与 C3D8R 的代价差异具体落在哪:积分点数量、单元自由度、沙漏控制,还是收敛行为?
  3. 为什么减缩积分在弯曲主导的问题里通常更准,却会引入零能模态?体积锁定与剪切锁定又分别在什么条件下出现?
  4. seedPart、setMeshControls、setElementType 为什么是三个不同对象而不是同一步操作?顺序颠倒时脚本会在哪一步失败?
  5. 脚本里按元素标签(elemLabels)建 Set 为什么比按几何实体建 Set 更容易失效?重划分网格后哪些引用会静默改变含义?

一、机制解析

1.1 Part 的三种 type:它决定的是“拓扑从哪来、刚度从哪来”

Part 是定义(第 5 篇的对象树),Instance 是装配里被摆放的那一份。type 不是界面标签,它决定三件事:

                      几何/网格来源            材料刚度来源          典型用途
DEFORMABLE_BODY   ──── 草图+特征,可划分网格 ──── 截面 + 材料(必须) ── 结构件主体
DISCRETE_RIGID_BODY ── 草图+特征,可划分网格 ──── 无材料:质量+参考点 ── 冲头、夹具
ANALYTICAL_RIGID_BODY ─ 由方程定义(不划分网格)── 无材料 ───────────── 刚体接触面
type能否划分网格是否参与刚度矩阵质量从哪来常见误用
DEFORMABLE_BODY是是(需 SectionAssignment + 材料)密度 × 体积忘记截面指派 → 提交时缺定义
DISCRETE_RIGID_BODY是否(刚体,只有质量矩阵)ReferencePoint + 刚体质量/转动惯量以为能算变形
ANALYTICAL_RIGID_BODY否否通常不参与需要复杂网格控制时不可用

为什么这对你重要:二次开发脚本里最容易混淆的是“刚体 Part 也能 generateMesh()”。网格在刚体上只用于接触与显示,不产生单元刚度;如果脚本给刚体 Part 指派了截面,求解器不会把它当柔性体。把 type 当参数显式写出(而不是依赖界面默认),是脚本可复现的第一条件。

过渡句:type 决定“这块几何有没有刚度”,单元类型决定“这块刚度以什么代价算出来”。下面这笔经济账是全篇的机制核心。

1.2 单元选择的经济账:积分点 × 自由度

同一套几何、同一套种子,换单元类型不改单元数,但改计算量与误差结构:

单元(六面体族)节点数积分点每单元自由度特征主要风险
C3D8(完全积分)82×2×2 = 824弯曲主导时偏刚,易锁定剪切锁定、体积锁定
C3D8R(减缩积分)81(单点)24更柔、弯曲更准,靠沙漏控制零能沙漏模态
C3D20(二次,20 节点)202×2×2 = 860畸变容忍度好,DOF 高自由度多、接触面定义更复杂
C3D4(四面体,线性)4112自由网格易生成严重偏刚(常需中间节点或 C3D10)

自由度对 64 个单元的实际影响:C3D8 与 C3D8R 都是每单元 24 个节点自由度,因此 64 单元 → 144 自由度(按第 3 篇的 125 节点×3 扣除固定面约束后为 144);换成 C3D20 同样 64 单元则自由度显著增加。真正的差异不在 DOF,而在每个单元参与积分的应变采样点数:8 个采样点意味着更完整的应变场(代价是弯曲主导时偏刚),1 个采样点意味着更柔的响应(代价是可能出现零能模态)。这是减缩积分“更准但更不稳定”的全部机制。

【经验法则】判定沙漏是否可接受的常用阈值是:沙漏能量(ODB 中历史变量 ALLHLL)小于总内能(ALLIE)的 1%;超过 5% 时应视为结果不可信。该阈值是行业经验判断,具体判定口径以官方文档与求解器指南为准。

过渡句:沙漏是减缩积分的代价,锁定是完全积分的代价。两者不能靠“加密网格”统一解决,因为它们的成因不同。

1.3 三种失效模式对照表

失效模式出现在哪种单元物理成因症状可脚本判定的信号
沙漏(hourglass)减缩积分(C3D8R、S4R)单积分点无法约束棋盘形零能变形网格呈波浪状、节点位移锯齿ALLHLL / ALLIE 比值
体积锁定(volumetric locking)完全积分 + 近不可压缩材料超弹性/塑性不可压缩条件在每点被强制结果偏刚、网格加密不收敛加密后误差不上降(第 3 篇的单调下降判据)
剪切锁定(shear locking)完全积分线性单元在弯曲常应变假设在纯弯曲下产生虚假剪切应变挠度偏小、随网格加密缓慢改善与理论解相对误差
畸变敏感线性四面体与高长宽比六面体雅可比退化,积分精度丢失个别单元应力异常高最小长宽比、负雅可比单元数

【最佳实践】弯曲主导 + 近不可压缩(超弹性)时,优先选减缩积分或专用单元族;接触与冲击(显式)优先六面体减缩积分,但必须核查沙漏。具体可用单元清单以官方文档为准——并非所有单元类型都适用于 Abaqus/Explicit(例如部分单元族不支持显式分析)。

过渡句:机制上分清“积分点”与“自由度”之后,脚本层的三件事就各自归位:种子决定数量、网格控制决定生成技术、单元类型决定积分行为。

1.4 三件事不能合并:种子、网格控制、单元类型

① seedPart(size=2.5)        → 决定每条边切几段:只影响数量
② setMeshControls(technique=STRUCTURED, elemShape=HEX) → 决定生成技术:影响可达性
③ setElementType(regions=part.cells, elemTypes=(elemType,))  → 决定积分行为:影响精度与稳定性
④ generateMesh()            → 真正把网格写进内核(此时才可按名引用)

第 5 篇的规则在这里仍然成立:网格属于内容层(实体靠索引或标签),可复现的引用属于引用层(Set)。setMeshControls 与 setElementType 必须作用于同一个 regions 对象,否则类型设置在生成时不被采用。【铁律】第 4 条同样适用:网格控制属性与单元类型属性的修改要走方法(setValues/对应的编辑方法),直接赋值在只读成员上抛异常;录制脚本与官方参考一律用 setValues。

【经验法则】结构化网格(technique=STRUCTURED)要求区域拓扑可扫掠,不满足时生成失败或退化;对复杂几何应当从一开始就用 FREE 并在脚本里预留分支。

过渡句:下面三段脚本把这笔经济账写成可数数字——第一段生成网格并读回实际单元类型,第二段用纯 Python 审计网格质量与引用风险,第三段把单元选择写成规则表。

二、完整代码与逐行剖析

2.1 显式单元类型:生成、读回、判定

# explicit_element_type.py —— 显式写单元类型,并把实际类型读回(mm-N-s,第 7 篇)
from abaqus import mdb, session                 # 顶层对象;noGUI 下 session 不可用(【铁律】第 8 条)
from abaqusConstants import *                    # 【铁律】第 3 条:符号常量不可省略
import mesh                                      # ElemType 定义在 mesh 模块
import regionToolset
import sys

MODEL = 'Cantilever'
L, H, W = 100.0, 10.0, 10.0
seed = 2.5                                       # 每条边 4 段 → 64 单元 / 125 节点(第 3 篇的硬校验)

model = mdb.Model(name=MODEL)
sketch = model.ConstrainedSketch(name='profile', sheetSize=200.0)
sketch.rectangle(point1=(0.0, 0.0), point2=(W, H))

# type 参数显式写出:默认是 DEFORMABLE_BODY,但脚本可复现的前提是“不依赖默认”
part = model.Part(name='Beam', dimensionality=THREE_D, type=DEFORMABLE_BODY)
part.BaseExtrusion(sketch=sketch, depth=L)
part.regenerate()                                # isOutOfDate=1 时网格操作不可靠(第 5 篇)

model.Material(name='Steel')
model.materials['Steel'].Elastic(table=((21000.0, 0.3), ))
model.materials['Steel'].Density(name='Density', table=((7.85e-9, ), ))   # 【铁律】第 1 条
model.HomogeneousSolidSection(name='Section', material='Steel', thickness=1.0)
part.Set(name='AllCells', cells=part.cells)      # 先建命名引用,再指派(第 5 篇引用层)
part.SectionAssignment(region=regionToolset.Region(cells=part.cells), sectionName='Section')

part.seedPart(size=seed, deviationFactor=0.1)
part.setMeshControls(regions=part.cells, elemShape=HEX, technique=STRUCTURED)

# 关键决策点:减缩积分单元必须显式写全 elemCode 与 elemLibrary;
# 不写时 Abaqus 按默认优先级挑类型,可能给出与预期不同的单元(现象以官方文档为准)
hourless = mesh.ElemType(elemCode=C3D8R, elemLibrary=STANDARD)
part.setElementType(regions=part.cells, elemTypes=(hourless, ))
part.generateMesh()

print('可数判定:单元数=%d 节点数=%d' % (len(part.elements), len(part.nodes)))
print('期望:64 / 125;每单元自由度=%d,总节点自由度=%d'
      % (24, 3 * len(part.nodes)))

# 读回当前单元类型:参数名与返回形态以官方文档为准;用 try 包住避免脚本崩掉
try:
    actual = part.getElementType(region=part.cells, elemShape=HEX)
    print('实际单元类型:%s' % actual)
except Exception as e:
    print('读回失败(%s):%s' % (type(e).__name__), str(e))

# 按标签建 Set 的风险:标签在重新划分网格时会重排(2.3 节讨论);参数名以官方文档为准
part.Set(name='MidElements', elemLabels=(16, 17, 32, 33))
print('按标签的 Set 实体数=%d(重网格后可能指向别的单元)' % len(part.sets['MidElements'].elements))
sys.exit(0)

三个决策点:

  • part.regenerate() 必须在网格操作前:特征改完没重新生成时 isOutOfDate 为 1,此时种子与类型设置的生效顺序不可靠(第 5 篇)。
  • elemLibrary=STANDARD 显式写出:单元族与求解器的匹配是显式的(Abaqus/Standard 与 Abaqus/Explicit 的可用单元清单不同),依赖默认值会让脚本在换求解器时行为改变。
  • part.Set(name='MidElements', elemLabels=(16, 17)):按标签引用属于内容层,重网格后同一标签可能是另一个单元。可复现的写法是按几何实体建 Set(cells=/faces=),或把标签作为脚本参数从外部文件读入(第 12 篇)。

过渡句:第一段脚本解决“类型写全”,第二段解决“网格质量与引用风险能否被脚本自证”。

2.2 网格质量审计:用纯 Python 把三类失效写成可数指标

# mesh_audit.py —— 不依赖 Abaqus 版本差异的几何审计(可接入 CI,退出码即判定)
from abaqus import mdb
from abaqusConstants import *
import math
import sys

MODEL = 'Cantilever'
model = mdb.models[MODEL]
part = model.parts['Beam']

def aspect_ratio_hex(nodes, elem):
    """线性六面体最小/最大边长比:>20 视为畸变(【经验法则】)。"""
    coords = [nodes[i].coordinates for i in elem.nodeLabels]
    def dist(a, b):
        return math.sqrt(sum((a[k] - b[k]) ** 2 for k in range(3)))
    lengths = [dist(coords[i], coords[(i + 1) % 4]) for i in range(4)]          # 底面四条边
    lengths.append(dist(coords[0], coords[4]))                                  # 高
    lengths.append(dist(coords[1], coords[5]))
    if min(lengths) <= 0.0:
        return float('inf')
    return max(lengths) / min(lengths)

def jacobian_sign(coords):
    """用底面法向与高的夹角判断雅可比符号:退化单元(负体积)必须单独计数。"""
    def sub(a, b):
        return [a[k] - b[k] for k in range(3)]
    def cross(u, v):
        return [u[1] * v[2] - u[2] * v[1], u[2] * v[0] - u[0] * v[2], u[0] * v[1] - u[1] * v[0]]
    u = sub(coords[1], coords[0])
    v = sub(coords[3], coords[0])
    n = cross(u, v)
    h = sub(coords[4], coords[0])
    return sum(n[k] * h[k] for k in range(3))

rows = []
for elem in part.elements:
    if len(elem.nodeLabels) != 8:
        continue
    coords = [part.nodes[i].coordinates for i in elem.nodeLabels]
    rows.append((aspect_ratio_hex(coords, elem), jacobian_sign(coords)))

if rows:
    worst = max(r[0] for r in rows)
    neg_j = sum(1 for r in rows if r[1] <= 0.0)
    over_20 = sum(1 for r in rows if r[0] > 20.0)
    print('审计:单元数=%d 最大长宽比=%.2f 长宽比>20 的单元=%d 负/零雅可比单元=%d'
          % (len(rows), worst, over_20, neg_j))
else:
    print('审计:网格为空——先 generateMesh() 再跑本脚本')

# 引用层完整度(第 5 篇规则 4):按标签的 Set 是否仍然非空
empty_sets = [k for k, v in sorted(part.sets.items())
              if sum(len(getattr(v, a, ()) or ()) for a in ('elements', 'cells', 'faces')) == 0]
print('空 Set:%s' % sorted(empty_sets))

# 判定标准:最大长宽比 <= 20、负雅可比 = 0、空 Set = 0
ok = bool(rows) and max(r[0] for r in rows) <= 20.0 and all(r[1] > 0.0 for r in rows) and not empty_sets
print('判定:%s' % ('通过' if ok else '需要重新划分网格'))
sys.exit(0 if ok else 1)

要点:

  • 这段刻意不用任何“版本敏感”的查询接口,只做纯几何计算:它在 Abaqus 2026 与旧版本上行为一致,因而可作为 CI 门禁。【最佳实践】把可复现的判定写成退出码(sys.exit(0/1)),而不是写在文档里。
  • 长宽比 > 20 与“负雅可比必须为 0”是【经验法则】级阈值;它们判的是网格质量,不判物理正确性。物理正确性要靠与独立算法对照(第 3 篇的欧拉-伯努利 1.90476 mm)。
  • 按标签的 Set 与按实体的 Set 分开统计:前者是内容层引用(不可复现),后者是引用层(可复现)。两者的数量差就是“有多少引用会在重网格后失效”。

过渡句:前两段是“生成 + 审计”,第三段把选择本身写成规则——这样脚本在批量扫描时能自动决定单元族,而不是靠人工每次点鼠标。

2.3 决策表脚本:从问题特征推出单元与种子

# element_advisor.py —— 把单元选择写成可复用的规则(输入参数、输出建议,可被第 12 篇调用)
from abaqus import mdb
from abaqusConstants import *
import sys

RULES = [
    # (问题特征, 建议单元, 种子建议, 必查项)
    ('bending-dominant',   'C3D8R', '<=2 段/厚度方向', '与理论解对照,误差应单调下降'),
    ('near-incompressible', 'C3D8R', '>=3 段/厚度方向', '体积锁定:加密后误差必须下降'),
    ('contact-impact',     'C3D8R', '六面体优先',     '沙漏能量 ALLHLL / ALLIE < 1%(【经验法则】)'),
    ('shell-thin',         'S4R',   '>=4 段/厚度',    '厚度方向至少 3 层单元(实体壳)或壳单元'),
    ('complex-geometry',   'C3D10', '自由网格',        '线性四面体 C3D4 偏刚,慎用'),
]

def advise(problem_type, t_min, target_error):
    """t_min:最薄特征尺寸(mm);target_error:可接受相对误差(小数)。"""
    rows = [r for r in RULES if r[0] == problem_type]
    if not rows:
        return ['问题特征不在规则表内']
    code, seed, check = rows[0][1], rows[0][2], rows[0][3]
    # 关键决策点:弯曲主导时厚度方向段数决定误差量级——段数 < 3 时减缩积分也可能偏刚
    n_through = max(1, int(round(t_min / 2.0)))
    return ['单元建议:%s' % code, '种子建议:%s(本几何约 %d 段/厚度)' % (seed, n_through),
            '必查项:%s' % check, '可接受误差:< %.1f%%' % (target_error * 100.0)]

if __name__ == '__main__':
    for problem, t in (('bending-dominant', 10.0), ('near-incompressible', 6.0), ('contact-impact', 4.0)):
        print('== %s ==' % problem)
        for line in advise(problem, t, 0.15):
            print('   ' + line)
    sys.exit(0)

这段没有调用任何 Abaqus 特有的网格方法,因此它可以在普通 Python 3.10 下独立运行(python element_advisor.py),适合做批量扫描前的预检查。规则表的判据全部来自本篇机制段与【经验法则】阈值,不引入未验证的具体 API 参数——这是本系列“真实性第一”的写法约定:脚本里只出现已确认的名字,不确定的一律标注“以官方文档为准”。

三、常见报错与排查

  1. 现象:generateMesh() 后单元类型与脚本里写的不一致(例如期望 C3D8R,实际得到默认类型)。
    根因:setMeshControls 与 setElementType 的 regions 对象不是同一个;或减缩积分单元未显式写全,落入默认优先级。
    解法:两处使用同一个 part.cells/同一个命名 Set;显式写 mesh.ElemType(elemCode=C3D8R, elemLibrary=STANDARD);用 part.getElementType(region=part.cells, elemShape=HEX) 读回(参数名与返回形态以官方文档为准),把读回结果打印进判定标准。

  2. 现象:结构化网格生成失败或数量偏离理论值。
    根因:区域拓扑不可扫掠,technique=STRUCTURED 不适用;或种子被单条边的显式种子覆盖。
    解法:脚本里对不可扫掠区域改用 technique=FREE;把种子值与段数作为参数打印出来(本例 4 段 → 64 单元),用 len(part.elements) 与理论值对照。

  3. 现象:显式分析提交时报某单元类型不支持。
    根因:单元族与求解器的匹配是显式的,Abaqus/Explicit 的可用单元清单与 Abaqus/Standard 不同。
    解法:不要从 Standard 脚本直接切到 Explicit 复用同一 ElemType;可用单元清单以官方文档为准,脚本里把 elemLibrary 与求解器类型作为参数显式传入。

  4. 现象:弯曲挠度偏小,加密网格后改善很慢(或近不可压缩材料下完全不改善)。
    根因:完全积分线性单元的剪切锁定,或近不可压缩条件下的体积锁定。
    解法:改用减缩积分单元族;判定用“误差随网格加密单调下降”(第 3 篇的判据),而不是用绝对误差。同时检查沙漏能量占比是否 < 1%(【经验法则】)。

  5. 现象:脚本第二次运行时按元素标签的 Set 指向了别的单元,结果与第一次不同。
    根因:元素标签在重新划分网格时重排,按标签引用属于内容层(第 5 篇)。
    解法:把 Set 建在几何实体上(cells=/faces=),或把标签从外部参数文件读入并在每次运行前打印;CI 判定用“空 Set 数 = 0”。

四、动手练习

  1. 经济账对照:在同一个模型上分别跑 C3D8 与 C3D8R(种子 2.5 mm),记录单元数、节点数与端部挠度。
    判定标准:两次单元数必须都是 64、节点数都是 125;完全积分与减缩积分的总节点自由度都是 144(扣除固定约束后);弯曲挠度必须满足“减缩积分结果更接近 1.90476 mm 的理论解”,且沙漏能量占比打印值 < 1%。三项任一不符即为脚本或网格控制未生效。

  2. 网格质量审计:把 2.2 的脚本跑通,然后人为把某条边的种子改成 10 mm(造成一根长条单元)。
    判定标准:第一次运行退出码必须为 0 且最大长宽比 ≤ 20;第二次必须打印“长宽比>20 的单元 ≥ 1”并退出码为 1。若第二次仍通过,说明审计脚本没有覆盖该单元(例如单元族不是 8 节点六面体)。

  3. 引用层失效验证(思考题,附验证要点):解释为什么按 elemLabels 建的 Set 在重网格后不可复现。
    验证要点清单:① 打印 part.sets['MidElements'].elements 的标签清单并保存为文本;② 把种子从 2.5 mm 改成 1.25 mm 重新 generateMesh(),再打印同一 Set 的标签清单,两次清单必须不同;③ 把 Set 改建在 cells= 上,重网格前后两次输出的实体数必须相同(都是 1)。三条都要有输出记录。

五、小结与下一篇预告

本篇的核心是“代价的归属”:type 决定这块几何有没有刚度;单元类型决定以多少积分点、多少自由度换取精度;减缩积分换来更柔的弯曲响应,代价是零能沙漏模态(【经验法则】< 1%);完全积分换来完整应变场,代价是剪切与体积锁定。三件事必须分开写——种子、网格控制、单元类型——并且用可数数字自证(64 单元、125 节点、最大长宽比 ≤ 20、负雅可比 = 0、空 Set = 0)。第 7 篇把全链最隐蔽的变量写成脚本:单位制与密度漏写的后果,以及一份单位一致性校验脚本;第 8 篇讨论分析步与输出请求如何决定这些数字能否被读回。第 5 篇的内容层/引用层划分在本篇落地为一条具体规则:按标签的引用不可复现,按命名实体的引用可复现。


本篇认知问题回显(FAQ)

Q1:Abaqus/CAE 创建 Part 时 DEFORMABLE_BODY、DISCRETE_RIGID_BODY、ANALYTICAL_RIGID_BODY 三种 type 在求解上有什么区别?

A:DEFORMABLE_BODY 参与刚度矩阵且需材料截面;DISCRETE_RIGID_BODY 只有质量与参考点;ANALYTICAL_RIGID_BODY 不划分网格。

Q2:C3D8 与 C3D8R 的代价差异具体体现在哪些数值上?

A:C3D8 是 2×2×2 共 8 个积分点,C3D8R 是单积分点加沙漏控制;同样 64 单元下前者弯曲偏刚、后者更柔但可能出现零能模态。

Q3:减缩积分单元(C3D8R)的沙漏能量用什么阈值判定结果可信?

A:【经验法则】是沙漏能量 ALLHLL 小于总内能 ALLIE 的 1%,超过 5% 视为不可信;具体判定口径以官方文档为准。

Q4:为什么 seedPart、setMeshControls、setElementType 不能合并成一步操作?

A:三者分别决定数量、生成技术与积分行为,且后两者必须作用于同一个 regions 对象。

Q5:用 part.Set(name='MidElements', elemLabels=(16, 17)) 按元素标签建集合为什么在重划分网格后失效?

A:元素标签在重新划分网格时会重排,按标签引用属于内容层;按几何实体(cells=/faces=)建的命名 Set 才跨运行稳定。

Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐