Abaqus 二次开发教程(6):几何与网格——Part 类型与单元选择的隐藏代价
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% 即应重新划分网格或换单元类型。
〇、本篇要解决的认知问题
- Abaqus/CAE 里创建 Part 时的
type(DEFORMABLE_BODY、DISCRETE_RIGID_BODY、ANALYTICAL_RIGID_BODY)在求解链路上到底改变了什么?为什么刚体 Part 不能靠它获得材料刚度? - C3D8 与 C3D8R 的代价差异具体落在哪:积分点数量、单元自由度、沙漏控制,还是收敛行为?
- 为什么减缩积分在弯曲主导的问题里通常更准,却会引入零能模态?体积锁定与剪切锁定又分别在什么条件下出现?
seedPart、setMeshControls、setElementType为什么是三个不同对象而不是同一步操作?顺序颠倒时脚本会在哪一步失败?- 脚本里按元素标签(
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(完全积分) | 8 | 2×2×2 = 8 | 24 | 弯曲主导时偏刚,易锁定 | 剪切锁定、体积锁定 |
| C3D8R(减缩积分) | 8 | 1(单点) | 24 | 更柔、弯曲更准,靠沙漏控制 | 零能沙漏模态 |
| C3D20(二次,20 节点) | 20 | 2×2×2 = 8 | 60 | 畸变容忍度好,DOF 高 | 自由度多、接触面定义更复杂 |
| C3D4(四面体,线性) | 4 | 1 | 12 | 自由网格易生成 | 严重偏刚(常需中间节点或 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 参数——这是本系列“真实性第一”的写法约定:脚本里只出现已确认的名字,不确定的一律标注“以官方文档为准”。
三、常见报错与排查
-
现象:
generateMesh()后单元类型与脚本里写的不一致(例如期望 C3D8R,实际得到默认类型)。
根因:setMeshControls与setElementType的regions对象不是同一个;或减缩积分单元未显式写全,落入默认优先级。
解法:两处使用同一个part.cells/同一个命名 Set;显式写mesh.ElemType(elemCode=C3D8R, elemLibrary=STANDARD);用part.getElementType(region=part.cells, elemShape=HEX)读回(参数名与返回形态以官方文档为准),把读回结果打印进判定标准。 -
现象:结构化网格生成失败或数量偏离理论值。
根因:区域拓扑不可扫掠,technique=STRUCTURED不适用;或种子被单条边的显式种子覆盖。
解法:脚本里对不可扫掠区域改用technique=FREE;把种子值与段数作为参数打印出来(本例 4 段 → 64 单元),用len(part.elements)与理论值对照。 -
现象:显式分析提交时报某单元类型不支持。
根因:单元族与求解器的匹配是显式的,Abaqus/Explicit 的可用单元清单与 Abaqus/Standard 不同。
解法:不要从 Standard 脚本直接切到 Explicit 复用同一ElemType;可用单元清单以官方文档为准,脚本里把elemLibrary与求解器类型作为参数显式传入。 -
现象:弯曲挠度偏小,加密网格后改善很慢(或近不可压缩材料下完全不改善)。
根因:完全积分线性单元的剪切锁定,或近不可压缩条件下的体积锁定。
解法:改用减缩积分单元族;判定用“误差随网格加密单调下降”(第 3 篇的判据),而不是用绝对误差。同时检查沙漏能量占比是否 < 1%(【经验法则】)。 -
现象:脚本第二次运行时按元素标签的 Set 指向了别的单元,结果与第一次不同。
根因:元素标签在重新划分网格时重排,按标签引用属于内容层(第 5 篇)。
解法:把 Set 建在几何实体上(cells=/faces=),或把标签从外部参数文件读入并在每次运行前打印;CI 判定用“空 Set 数 = 0”。
四、动手练习
-
经济账对照:在同一个模型上分别跑 C3D8 与 C3D8R(种子 2.5 mm),记录单元数、节点数与端部挠度。
判定标准:两次单元数必须都是 64、节点数都是 125;完全积分与减缩积分的总节点自由度都是 144(扣除固定约束后);弯曲挠度必须满足“减缩积分结果更接近 1.90476 mm 的理论解”,且沙漏能量占比打印值 < 1%。三项任一不符即为脚本或网格控制未生效。 -
网格质量审计:把 2.2 的脚本跑通,然后人为把某条边的种子改成 10 mm(造成一根长条单元)。
判定标准:第一次运行退出码必须为 0 且最大长宽比 ≤ 20;第二次必须打印“长宽比>20 的单元 ≥ 1”并退出码为 1。若第二次仍通过,说明审计脚本没有覆盖该单元(例如单元族不是 8 节点六面体)。 -
引用层失效验证(思考题,附验证要点):解释为什么按
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 才跨运行稳定。
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐


所有评论(0)