01为什么不能直接把 Excel 存成 CSV 就导
材料数据在 Excel 里是二维的:一行一种材料, 一列一个参数。但 Abaqus 的材料不是二维的 —— 塑性是一张多行的曲线表,温度相关的每个属性又各自是一张表, 损伤起始下面还挂着演化。一张扁平的表格装不下这个结构, 所以中间必然要有一次结构转换。
目标格式是 JSON。它的顶层可以是两种写法,插件都收:
{"materials": [ ... ]} 带外壳
[ ... ] 直接一个数组第二种就是插件导出材料时写出来的形状 —— 所以「先导出一份看看格式」是最省事的起点, 尤其是当你已经在 CAE 里手工建好了一个材料当样板。 这条路和它的边界在材料复用到别的模型那篇里讲了。
一个最小的目标文件
下面是从 Excel 里那一行「密度、模量、泊松比 + 一条硬化曲线」 转出来之后该有的样子:
{
"materials": [
{
"name": "Steel_Q345",
"description": "structural steel",
"density": 7.85e-09,
"elastic": { "youngsModulus": 206000.0, "poissonsRatio": 0.3 },
"plastic": {
"table": [
[345.0, 0.0],
[420.0, 0.02],
[480.0, 0.06],
[520.0, 0.15]
]
}
}
]
}注意 density 那一行:只有一列的属性块 可以直接写一个数,不用包成对象。 这一点在从 Excel 转的时候很实用 —— 密度、比热、导热系数、热膨胀系数都是单列,一格对一个值, 省掉一层嵌套。字段的完整清单在帮助文档的格式一节。
02坑一:另存编码 —— 报错和编码没关系
这是从 Excel 出发时的头号坑, 因为它的报错一个字都没提编码:
materials.json 不是合法的 JSON:...文件明明是从一个能打开的 JSON 编辑器里存出来的, 括号也数对了,怎么就不合法? 原因是插件读文件时按 UTF-8 解, 而在中文 Windows 上 Excel 和记事本的默认编码不是 UTF-8。 只要文件里有一个中文字符(最常见的就是 description 里那句说明), 解码就会失败,而失败被归到了「JSON 不合法」这一类里。
换个角度:如果整个文件一个中文都没有, 那么 GBK 和 UTF-8 存出来的字节是一样的,怎么存都能读。 所以这个坑只在你写了中文的材料名或说明时才会踩到。 也就是说,它专挑「认真写了注释的人」下手。
03坑二:单元格里到底什么算数字
Excel 单元格里看着是数字的东西,转成 JSON 之后不一定是数。 这一栏的报错有三句,都很短:
elastic: values must be numbers
plastic: row [345.0, '0.02 '] is not a list of numbers
elastic: missing key(s) poissonsRatio但哪些真的会挂、哪些其实没事,跟直觉不太一样。 先说不用管的两种:
| 单元格里的东西 | 转成 JSON 后 | 结果 |
|---|---|---|
| 206000(正常数字) | 206000.0 | 正常 |
| 206000(Excel 存成了文本,左上角有绿三角) | "206000" | 能过 —— 数字字符串会被转成数 |
| 前后带空格的 206000 | " 206000 " | 能过 —— 空白被吃掉 |
| 206,000(千分位) | "206,000" | 挂:values must be numbers |
| 206000 MPa(带单位) | "206000 MPa" | 挂:values must be numbers |
| 空单元格 | null 或 "" | 挂:values must be numbers |
| #N/A、#DIV/0!(公式没算出来) | "#N/A" | 挂:values must be numbers |
| 2.06E+05 | 206000.0 | 正常 —— 科学计数法本来就合法 |
所以在 Excel 侧要做的清理其实只有三件:
- 关掉千分位。单元格格式里的「使用千位分隔符」会在导出时把逗号带出去。 数值格式统一设成常规或科学计数。
- 单位不要写在数字旁边。单位写在表头里,别写在单元格里。 「206000 MPa」这种写法在 Excel 里看着清楚,转出去就是一个字符串。
- 把公式错误和空格清干净。整列筛一遍有没有
#N/A、#REF!、 以及看不出来的空单元格。
另外一句报错值得单独说:missing key(s) poissonsRatio这类是整个键没写,而不是值不对。 从 Excel 转的时候,某一列在源表里根本不存在(比如泊松比那一列被同事删了) 就会得到这一句,它会明确点名缺哪个键。
04坑三:排序按钮,和塑性表的两条硬规则
这一坑最隐蔽,因为它不是打字错误,是一次无害的操作造成的。
塑性表有两条硬规则:每条硬化曲线的第一行塑性应变必须正好是 0, 并且塑性应变必须沿表严格递增。 在 Excel 里,只要你按屈服应力那一列点了一下排序, 第二条通常就断了:
plastic: plasticStrain must increase down the table, got 0.02 after 0.06
plastic: the first plastic strain must be 0, got 0.02报错会把两个数都写出来,所以定位不难 —— 难的是意识到「我什么时候排过序」。 对策很土但有效:塑性曲线那几列单独放一张工作表, 并且永远按塑性应变升序存放, 排序只在这一列上做。
这两条规则各自为什么必须成立、以及第一行为什么是 0 而不是一个很小的数, 在塑性表三类报错里逐条讲了;从拉伸曲线换算到能填的表在真应力真应变换算。
还有一条:所有行的列数必须一样
plastic: all rows must have the same lengthExcel 侧的成因几乎总是同一个:温度那一列只填了一部分行。 前面几行有温度,后面几行忘了填, 转出来就是有的行三个数、有的行两个数。
05单位换算就在 Excel 里做完
手册和供应商给的数据基本都是 SI:密度 kg/m³、模量 Pa 或 GPa、 比热 J/(kg·K)。Abaqus 里更常用的是 tonne-mm-s: 密度那个量级变成 1e-09、模量用 MPa、比热要乘 1e6。
这一步在 Excel 里做,不要在脑子里做,也不要在转换脚本里做。理由很实际:Excel 里多加一列公式,换算结果和原始值并排放着, 肉眼扫一遍就知道量级对不对; 埋进脚本里就看不见了,而这一栏错了的代价是 「静力结果看着正常,一做模态差几十倍」。
| 量 | SI 里常见的样子 | mm 制里该是什么 | 在 Excel 里乘什么 |
|---|---|---|---|
| 密度 | 7850 kg/m³ | 7.85e-09 | 乘 1e-12 |
| 弹性模量 | 206 GPa | 206000(MPa) | GPa 乘 1000 |
| 比热 | 460 J/(kg·K) | 4.6e+08 | 乘 1e6 |
| 导热系数 | 45 W/(m·K) | 45 | 数值不变 |
| 热膨胀系数 | 1.2e-05 /K | 1.2e-05 | 数值不变 |
十项换算的完整对照和每一项的推导在Abaqus 单位制怎么选,比热那个 1e6 是怎么来的在导热系数与比热,帮助文档的单位一节有同一张表可以直接对。
06一个能跑的 CSV 转 JSON 脚本
公式拼 JSON 那条路(用 TEXTJOIN 之类在单元格里拼字符串) 在只有几种材料、只填密度和弹性的时候够用, 一旦要带塑性曲线就会变成一团没法维护的公式。 超过这个规模就该用脚本。
下面这个脚本只用 Python 标准库,跑在普通 Python 里,不需要 Abaqus —— 所以可以在自己机器上反复试,不占许可证。 它吃两个 CSV:一个一行一种材料,一个装塑性曲线。
materials.csv
name,description,density,E,nu
Steel_Q345,structural steel,7.85e-09,206000,0.3
plastic.csv
name,yieldStress,plasticStrain
Steel_Q345,345,0
Steel_Q345,420,0.02
Steel_Q345,480,0.06
Steel_Q345,520,0.15# -*- coding: utf-8 -*-
"""csv_to_json.py —— 把两张 CSV 转成材料库 JSON。普通 Python 就能跑。"""
import csv
import json
from collections import OrderedDict
def read_csv(path):
# utf-8-sig:Excel 存出来的 BOM 在这里就吃掉了
with open(path, newline='', encoding='utf-8-sig') as f:
return list(csv.DictReader(f))
def number(text, where):
"""一格转一个数。这里挂掉,比导入时挂掉便宜得多。"""
cleaned = (text or '').strip()
if not cleaned:
raise ValueError('%s 是空的' % where)
try:
return float(cleaned)
except ValueError:
raise ValueError('%s 不是数字:%r' % (where, text))
def build(materials_path, plastic_path):
curves = OrderedDict()
for i, row in enumerate(read_csv(plastic_path), start=2):
name = row['name'].strip()
curves.setdefault(name, []).append([
number(row['yieldStress'], 'plastic.csv 第 %d 行 yieldStress' % i),
number(row['plasticStrain'], 'plastic.csv 第 %d 行 plasticStrain' % i),
])
out = []
for i, row in enumerate(read_csv(materials_path), start=2):
name = row['name'].strip()
spec = OrderedDict()
spec['name'] = name
if row.get('description', '').strip():
spec['description'] = row['description'].strip()
spec['density'] = number(row['density'], '第 %d 行 density' % i)
spec['elastic'] = OrderedDict([
('youngsModulus', number(row['E'], '第 %d 行 E' % i)),
('poissonsRatio', number(row['nu'], '第 %d 行 nu' % i)),
])
rows = curves.get(name)
if rows:
# 按塑性应变升序 —— Excel 里排过序也不怕
rows = sorted(rows, key=lambda r: r[1])
if rows[0][1] != 0.0:
raise ValueError('%s 的第一行塑性应变不是 0' % name)
spec['plastic'] = {'table': rows}
out.append(spec)
return out
if __name__ == '__main__':
specs = build('materials.csv', 'plastic.csv')
with open('materials.json', 'w', encoding='utf-8') as f:
json.dump({'materials': specs}, f, indent=2, ensure_ascii=False)
f.write('\n')
print('wrote %d material(s)' % len(specs))几个刻意的选择,都是为了让错误尽早暴露:
encoding='utf-8-sig'读、encoding='utf-8'写, 加上ensure_ascii=False—— 中文说明按原字符写出去,第 02 节那个坑一次解决。- 每一格转数字都带上「哪个文件第几行哪一列」。 CSV 的行号从 2 起算,因为第 1 行是表头 —— 这样报出来的行号和 Excel 里看到的行号是同一个数。
- 塑性曲线在脚本里重新按塑性应变排一次序, 并且当场检查第一行是不是 0。 这两条本来是导入时才查的,提前到这里查, 是因为这时候你还开着 Excel。
spec 里加键就行: 热的三样放进 thermal,损伤放进 damage。 属性块的键名和每一列的顺序在帮助文档的格式一节,如果你想跳过文件直接建材料, 对应的 Abaqus 接口调用在用 Python 批量建材料那篇的属性块对照表里。07报错对照:这句话对应 Excel 里的哪一格
文件转好了导进去,还是可能被拦。 下面这张表把导入时会看到的原话映射回 Excel 侧的成因, 按「先看文件层、再看材料层、最后看数值层」的顺序排。
| 导入时看到的 | Excel 侧的成因 |
|---|---|
| 找不到材料库文件 | 路径写错,或者文件还开在 Excel 里没保存出来 |
| 不是合法的 JSON | 两种:一是编码不是 UTF-8 且文件里有中文(最常见);二是拼字符串时漏了逗号或多了尾逗号 |
| 材料库的顶层必须是一个对象或数组。 | 写成了单个材料对象,外面忘了套数组 |
| 里没有任何材料。 | 数组是空的 —— 通常是源表最后一行是空行,被跳过之后什么都没剩 |
| 第 N 个材料不是一个对象。 | 数组里混进了字符串或数字,常见于手工拼字符串时多了一个逗号 |
| 第 N 个材料缺少 "name" 字段。 | 源表里那一行的名称列是空的;N 是从 1 数的第几个材料 |
| 材料名重复 | 拖公式或复制粘贴导致两行同名。插件在读文件阶段就拒,不会先导一个再覆盖 |
| values must be numbers | 单元格里混了非数字字符:千分位逗号、单位后缀、公式错误值、空格 |
| is not a list of numbers | 同上,但发生在表格类属性的某一行里,报错会把这一行原样打出来 |
| all rows must have the same length | 温度列只填了一部分行 |
| "table" is empty | 曲线那张工作表里这个材料一行都没有 |
| missing key(s) ... | 整列在源表里不存在,报错会点名缺哪个键 |
| must be positive, got ... | 数值是 0 或负数。密度允许 0,模量、比热、导热系数不允许 |
| poissonsRatio must be between -1 and 0.5 (exclusive) | 泊松比填了 0.5 —— 这是这一栏唯一一处会当场拦下的物理检查 |
| the first plastic strain must be 0 | 曲线表排过序,或者第一行填了一个很小的数当起点 |
| plasticStrain must increase down the table | 曲线表按屈服应力排过序 |
泊松比那一条为什么是硬边界、0.4999 为什么比 0.5 更麻烦,在弹性模量与泊松比里推过。
还有一条路:不经过 Excel
如果你要的材料本来就是常见牌号, 那么从站上的在线材料库直接取,比自己维护一张 Excel 省事 —— 数值带出处,单位制也已经是统一的。 要说清楚的是能力边界:在线材料库需要 Abaqus 2023 或更新的版本; 2022 及更早的版本没有激活途径,只能走本地 JSON 文件这条路, 也就是这篇讲的做法。两条来源的差别列在产品页的数据来源一节。