01先分清起始和演化:为什么填了不断
Abaqus 的延性金属损伤是分两步走的。第一步叫损伤起始(damage initiation),它只回答 一个问题:什么时候开始坏。第二步叫损伤演化(damage evolution),它回答 另一个问题:开始坏之后怎么坏下去。两者在 Abaqus 内部是两个独立的关键字, 插件的 JSON 也就跟着分成两层。
"damage": {
"ductile": {
"table": [
[0.35, -0.33, 0.001],
[0.25, 0.00, 0.001],
[0.18, 0.33, 0.001]
],
"evolution": {
"type": "DISPLACEMENT",
"value": 0.5,
"softening": "LINEAR"
}
}
}外层的 table 是起始准则,内层的 evolution 是演化规律。只写外层不写内层,导入依然成功,材料里也确实多了一条 Ductile Damage, 但 Abaqus 拿到的只是一个"何时开始"的判据,没有"之后怎么退化"的规律,于是 SDEG 这个损伤变量会一路涨到 1 附近然后停住,单元刚度不降、不删除。你在结果里看不到断裂, 只能在场变量里看到一片损伤区。
evolution。顺带说一句:损伤是叠在塑性之上的,材料必须已经有 plastic 块。 塑性表本身的坑(应变不从 0 开始、应力不单调、名义应力当真应力用)在塑性表那六种报错和 真应力真应变换算两篇里已经拆过,这里不重复。
02ductile 的三列:字段名、顺序、以及各自的含义
插件读 damage.ductile 时要求的字段是固定的三个,顺序也是固定的。 源码里这一行写得很直白:
_rows(sub, ('fractureStrain', 'stressTriaxiality', 'strainRate'), 'damage.ductile')| 列 | 字段名 | 含义 | 取值范围 |
|---|---|---|---|
| 第 1 列 | fractureStrain | 该应力状态下的等效塑性断裂应变 | ≥ 0(0 合法) |
| 第 2 列 | stressTriaxiality | 应力三轴度,静水压力除以 Mises 应力再取负号的那个量 | 可正可负 |
| 第 3 列 | strainRate | 等效塑性应变率 | ≥ 0 |
| 第 4 列 | (温度) | 只在你多写一列时才存在 | 按你的温标 |
第 4 列是隐式的:插件并不要求你显式声明温度相关,而是按表宽推断。 源码里这一句是关键:
tempDep = spec.get('temperatureDependency', width > len(columns))意思是,如果你没写 temperatureDependency,而每行给了 4 个数(比声明的 3 列多), 插件就认为第 4 列是温度。这个设计很方便,但也意味着多打一个数就会被当成温度, 而不是报错。温度列的完整规则在温度相关表怎么排那篇里。
关于三轴度:它是 Abaqus 里用来区分"拉、剪、压"三种应力状态的标量。纯剪切附近它在 0 左右,单轴拉伸大约 1/3,缺口和三向拉应力区会更高。同一种材料在三向拉应力下能承受的 塑性应变要小得多,所以这张表的物理趋势应该是三轴度越高、断裂应变越小。
03拿示例库里那组演示值当参照
插件自带的 materials_sample.json 里有一组韧性损伤的完整写法, 挂在 Steel_Q345 上。下面这些数值是示例库里的演示值, 用来说明格式和趋势,不是标准值、也不是某炉钢的实测值 —— 真做项目要用自己的试验数据。
| fractureStrain | stressTriaxiality | strainRate | 大致对应的应力状态 |
|---|---|---|---|
| 0.35 | -0.33 | 0.001 | 偏压 |
| 0.25 | 0.00 | 0.001 | 纯剪附近 |
| 0.18 | 0.33 | 0.001 | 单轴拉伸 |
值得学的不是这三个数,而是它们的排布方式:三轴度从 -0.33 单调升到 0.33, 断裂应变从 0.35 单调降到 0.18,应变率一列保持同一个值。这就是一张最小可用的韧性损伤表 —— 三轴度覆盖了压、剪、拉三个点,趋势符合物理,应变率维度先不展开。
同一个材料的 evolution 写的是 type 为 DISPLACEMENT、value 为 0.5、softening 为 LINEAR。 这个 0.5 的单位是长度,在毫米制里就是 0.5 mm,含义是"从开始损伤到完全失效, 等效塑性位移走 0.5 mm"。它的单位问题下面单独讲。
这个材料在示例库里的塑性表是 345 / 420 / 480 / 520 配 0 / 0.02 / 0.06 / 0.15, 也是演示值。想核对整份 JSON 的字段层级, 对着 JSON 字段说明看一遍最快。
04evolution 的两种写法,以及那个不允许为 0 的校验
演化子块有两种类型,插件按 type 决定去读哪个字段名。源码里是一行三元表达式:
etype = _constant(spec.get('type'), DISPLACEMENT)
column = 'fractureEnergy' if etype == ENERGY else 'displacementAtFailure'| type | 插件要读的字段 | 量纲 | 对网格的敏感度 |
|---|---|---|---|
| DISPLACEMENT(默认) | displacementAtFailure | 长度 | 高 |
| ENERGY | fractureEnergy | 能量 / 面积 | 低 |
两种写法都还接受一个通用的 value 作为兜底字段名 —— 但顺序有讲究: 插件先找 table 或者当前 type 对应的正式字段名,两者都没有时才回头去找 value。所以"value": 0.5 和 "displacementAtFailure": 0.5效果一样(示例库用的是前者),但如果你 type 写了 ENERGY、字段名却填成displacementAtFailure,插件会抱怨缺 value —— 一条指向兜底字段的报错,真正的病根是字段名和 type 不配套。
type 不写就是 DISPLACEMENT,这个默认值是插件给的 (源码里无条件调用了一次常量解析)。但 softening 和degradation 不一样:源码里是写了这个键才传, 不写就整个参数不传给 Abaqus,用的是 Abaqus 自己那一端的默认值。 想明确控制这两项,就显式写出来,别依赖"应该是默认那个吧"。
fractureStrain 允许为 0, 但 evolution 里的位移或能量必须严格大于 0。 源码里这两处调用的是同一个校验函数,区别只在一个参数: 起始表那次带了 allowZero=True,evolution 那次没带。# 起始表:允许 0
_checkColumn(table, 0, ..., 'fractureStrain', allowZero=True)
# 演化:不允许 0
_checkColumn(table, 0, ..., column)所以你如果把演化值写成 0(比如想表达"一到就断"),会拿到这样一条报错:
damage.ductile.evolution: displacementAtFailure must be positive, got 0而在起始表里把断裂应变写成 0,是合法的 —— 那表示这个应力状态下材料一进入塑性就开始损伤。 如果你写了负数,报错串里的形容词会换成另一个:
damage.ductile: fractureStrain must be non-negative, got -0.2这两条串的差别(positive 和 non-negative)就是那个allowZero 参数的直接产物。看到哪一个,就知道插件在校验哪一层。
05演化值的单位:为什么位移型换个网格结果就变
displacementAtFailure 的量纲是长度。这件事有两个后果, 都很容易吃亏。
- 它跟着你的单位制走。整套模型按毫米—吨—秒建的,这个 0.5 就是 0.5 mm; 按米—千克—秒建的,0.5 就是 0.5 m,差了一千倍。单位制怎么自洽,单位制那篇里有完整的换算表。
- 它和网格尺寸耦合。等效塑性位移等于损伤区的等效塑性应变乘以单元特征长度, 所以同一个 0.5 mm,在 1 mm 网格上和在 0.2 mm 网格上代表的应变量完全不同。 网格一细,结构就"变脆"。
这就是 ENERGY 型存在的理由。fractureEnergy 是单位面积的断裂能, Abaqus 内部会用它除以单元特征长度反算出位移,于是网格细化时结果收敛, 而不是一路变脆。代价是你得有断裂能这个量 —— 它通常来自断裂力学试验, 比试验测个断后位移麻烦。
ENERGY; 只在一个固定网格上标定曲线、不打算改网格的,用 DISPLACEMENT 更省事。 示例库里 Steel_Q345 用位移、Al_6061_T6 用能量, 正好把两种都演示了一遍,后者在Johnson-Cook 那篇里讲。06不报错但结果错的五种情况
上面那些报错串好办 —— 插件会拦。真正麻烦的是插件拦不住、Abaqus 也不吭声的这五种。
| 情况 | 插件会拦吗 | 实际后果 |
|---|---|---|
| 漏了 evolution | 不会 | 只累积损伤变量,材料不软化、单元不删 |
| 表只有一行 | 不会 | 三轴度插值退化成常数,拉压剪都用同一个断裂应变 |
| 三轴度那列没排序 | 不会 | 插值按你给的顺序连线,可能连出反向段 |
| 三列写反了顺序 | 不会 | 数都是合法数,但被当成完全不同的物理量 |
| 多打一个数 | 不会 | 被当成温度列,静默变成温度相关表 |
第二条值得展开。只给一行表,语法上完全合法,Abaqus 也照跑, 但你实际上是在说"这个材料在任何应力状态下的断裂应变都一样"。 对韧性金属来说这几乎总是错的 —— 缺口根部和光滑试棒的断裂应变能差好几倍。 要么老实给三个以上三轴度点,要么心里清楚自己在用一个各向应力状态无关的粗模型。
第三条的表现很隐蔽:假设你给的三轴度顺序是 0.33 / -0.33 / 0,插件不会重排, Abaqus 就按这个顺序理解分段线性关系,插出来的曲线会在中间折回去。提交前按第 2 列升序排一遍,这是一分钟的事。
第四条容易发生在从别人的 inp 里抄数的时候 —— 不同前处理器导出的列顺序不一致。 判断方法很简单:断裂应变那一列应该都是正的小数(0.05 到 2 之间居多), 三轴度那一列应该有负数或跨零。如果第 1 列出现了负数,插件会直接报错, 这反倒帮你抓住了写反的情况。
07提交前照着过一遍
- 材料里已经有
plastic块,且塑性表第一行应变是 0、应力单调不降。 damage.ductile.table每行三个数,顺序是断裂应变、三轴度、应变率。- 第 1 列没有负数;第 2 列已按升序排好;不多不少正好三个数(除非你确实要温度列)。
- 三轴度至少覆盖三个点,把压、剪、拉都带上,断裂应变随三轴度单调下降。
evolution子块存在,且里面的位移或能量大于 0。- 演化值的单位和整套模型的单位制一致;如果后面要改网格,优先用
ENERGY。 - 导入后在材料编辑器里确认 Ductile Damage 下面挂着 Damage Evolution, 而不是只有一条起始准则。要是材料整个没生效,先看材料没生效那篇。