01最小可用例
先看一个完整能跑的:在当前 mdb 的某个 model 里建一个带密度、 弹性、塑性的材料。
# -*- coding: utf-8 -*-
from abaqus import mdb
from abaqusConstants import ISOTROPIC
model = mdb.models['Model-1']
mat = model.Material(name='Steel_Q345')
mat.setValues(description='structural steel')
mat.Density(table=((7.85e-09,),), temperatureDependency=False)
mat.Elastic(type=ISOTROPIC,
table=((206000.0, 0.3),),
temperatureDependency=False)
mat.Plastic(hardening=ISOTROPIC,
table=((345.0, 0.0),
(420.0, 0.02),
(480.0, 0.06),
(520.0, 0.15)),
temperatureDependency=False)结构就这么简单:Material(name=...) 返回一个材料对象, 之后每种物理属性都是这个对象上的一个方法,各自吃一张表。 没有中间层,没有需要先创建的容器。
模型名不能想当然
mdb.models['Model-1'] 里的名字写错会直接抛异常。 要跟着「当前正在看的那个 model」走,用的是这一串:
from abaqus import mdb, session
def current_model_name():
try:
viewport = session.viewports[session.currentViewportName]
name = viewport.displayedObject.modelName
if name in mdb.models:
return name
except (AttributeError, KeyError):
pass
names = sorted(mdb.models.keys())
if not names:
raise ValueError('the current mdb has no models')
return names[0]注意最后那个兜底:取不到当前视口时退回按名字排序的第一个。 这条规则在多 model 的文件里制造过不少误会 ——Analysis-Final 会排在 Model-1 前面, 因为 A 在 M 前。相关的排查在材料建了却不生效那篇的第一个断点。
02坑一:单行表格末尾那个逗号
这是写材料脚本时最常见的一个错,而且它是纯 Python 的问题, 跟 Abaqus 一点关系都没有。
table=((206000.0, 0.3),) # 一行两列,对
table=((206000.0, 0.3)) # 括号只是分组 —— 这是两个数第二行里的外层括号没有逗号,Python 把它当成分组括号直接去掉了, 于是传进去的是 (206000.0, 0.3), 一个长度 2 的元组,而不是「一行、这一行有两个数」。
多行的时候不会出这个问题,因为行之间自然有逗号。 所以它专挑只有一行的场合下手 —— 也就是绝大多数等向弹性、密度、单点热膨胀。
rows = []
rows.append((206000.0, 0.3))
mat.Elastic(type=ISOTROPIC, table=tuple(rows))批量生成材料时本来就要循环攒行,顺手就避开了。顺带一个相关的:属性块的参数吃的是表,不是数。 密度只有一列,很容易顺手写成 table=7.85e-09, 那会得到一句和材料毫无关系的报错 ——argument of type 'float' is not iterable, 或者 'float' object has no attribute 'get'。 看到这两句里的 float,去找哪个地方本该是表却给了个数。
03坑二:常量不是字符串
ISOTROPIC、JOHNSON_COOK、DISPLACEMENT、ENERGY、LINEAR、MAXIMUM、ON、OFF —— 这些全是 abaqusConstants 里的符号常量, 不是字符串。写成 'ISOTROPIC' 不等价。
所以脚本开头这一行不能省:
from abaqusConstants import *如果你的数据来源是配置文件、CSV 或者 Excel, 那里存的一定是字符串,需要在中间转一次。 插件内部就是这么做的,做法可以直接抄:
import sys
def to_constant(name, default=None):
if name is None:
return default
key = name.strip().upper()
try:
return getattr(sys.modules['abaqusConstants'], key)
except AttributeError:
raise ValueError('unknown Abaqus constant "%s"' % key)用 getattr 而不是自己维护一张映射表, 有一个实际好处:这个 Abaqus 版本里没有的常量会在用到它的时候报一句能读懂的话,而不是在脚本 import 阶段就整个崩掉。 版本之间常量增删的时候,这个差别很值钱。
04十类属性块的调用与列顺序
这是全篇最该收藏的一张表。 列顺序写错不会报错(列数对得上就照收), 所以它是这一栏里代价最高的一类错。
| 属性 | 调用 | 每行的列顺序 |
|---|---|---|
| 密度 | mat.Density(table=, temperatureDependency=) | value |
| 等向弹性 | mat.Elastic(type=ISOTROPIC, table=, temperatureDependency=) | youngsModulus, poissonsRatio |
| 塑性(等向硬化) | mat.Plastic(hardening=ISOTROPIC, table=, temperatureDependency=) | yieldStress, plasticStrain |
| 塑性(Johnson-Cook) | mat.Plastic(hardening=JOHNSON_COOK, table=) | A, B, n, m, meltingTemperature, transitionTemperature |
| 应变率相关 | mat.plastic.RateDependent(type=, table=) | JOHNSON_COOK 型:C, epsilonDot0;其它:multiplier, exponent |
| 导热系数 | mat.Conductivity(type=, table=, temperatureDependency=) | value |
| 比热 | mat.SpecificHeat(table=, temperatureDependency=) | value |
| 热膨胀 | mat.Expansion(type=, table=, temperatureDependency=, zero=) | alpha(zero 是标量,不是列) |
| 蠕变 | mat.Creep(law=, table=, temperatureDependency=) | STRAIN 与 TIME:A, n, m;HYPERBOLIC_SINE:A, B, n, deltaH, R |
| 阻尼 | mat.Damping(alpha=, beta=, composite=, structural=) | 四个都是标量,不用表;至少给一个 |
| 韧性损伤起始 | mat.DuctileDamageInitiation(table=, temperatureDependency=) | fractureStrain, stressTriaxiality, strainRate |
| 剪切损伤起始 | mat.ShearDamageInitiation(table=, temperatureDependency=, ks=) | fractureStrain, shearStressRatio, strainRate(ks 是标量) |
| JC 失效起始 | mat.JohnsonCookDamageInitiation(table=) | d1, d2, d3, d4, d5, meltingTemperature, transitionTemperature, referenceStrainRate |
| FLD 起始 | mat.FldDamageInitiation(table=, temperatureDependency=) | minorStrain, majorStrain(次应变在前) |
| 损伤演化 | initiation.DamageEvolution(type=, table=, softening=, degradation=) | DISPLACEMENT 型:位移;ENERGY 型:断裂能 |
这几个块各自的含义和填法,批 4 那三篇讲得更细:韧性损伤、Johnson-Cook 两个块、剪切损伤与 FLD。
损伤演化是独立的第二个调用
这是结构上最容易漏的一处:损伤起始和损伤演化是两个对象, 演化挂在起始对象上,要单独再调一次。
mat.DuctileDamageInitiation(
table=((0.35, -0.33, 0.001),
(0.25, 0.0, 0.001),
(0.18, 0.33, 0.001)),
temperatureDependency=False)
mat.ductileDamageInitiation.DamageEvolution(
type=DISPLACEMENT,
table=((0.5,),),
softening=LINEAR)注意方法名是大写开头的 DuctileDamageInitiation, 创建出来的成员名是小写开头的 ductileDamageInitiation —— 这条命名规律在 Abaqus 里是通用的, 要接着往下挂东西就得用小写那个。 只写起始不写演化,导入不报错、材料编辑器里也看得见, 但单元一个都不会删。
超弹和 CDP 各有一个必踩的坑
这两个块结构比别的复杂,各有一处不看源码想不到的:
- 超弹用试验数据时,
table仍然是必填的。虽然系数由 Abaqus 自己拟合、根本没有系数可给, 但少了这个参数会得到expected 1, got 0。 办法是传一个空元组:table=(),同时testData=ON。 - CDP 的子表构造器带
Concrete前缀。是cdp.ConcreteCompressionHardening(...), 不是cdp.CompressionHardening(...)。 少了前缀会得到object has no attribute 'CompressionHardening', 而这句话里没有任何字提示你缺的是前缀。
05坑三:温度依赖标志,Abaqus 数不出来
给材料加温度相关性的做法是每行末尾多一列温度。 于是问题来了:Abaqus 只能数出这一行有几个数, 数不出多出来的那一个是温度还是别的东西。
等向弹性没事,因为它固定两列,第三列只能是温度。 出事的是各向异性 —— 各类型的常数个数是固定的, 记不住就会数错:
| 弹性类型 | 常数个数 | 带温度列时每行 |
|---|---|---|
| ISOTROPIC | 2 | 3 |
| TRACTION | 3 | 4 |
| LAMINA | 6 | 7 |
| ORTHOTROPIC | 9 | 10 |
| ENGINEERING_CONSTANTS | 9 | 10 |
| ANISOTROPIC | 21 | 22 |
G23 被读成温度 3980 度。没有任何一条报错会提到这件事, 模型算得完,刚度是错的。所以写脚本时的规矩很简单:各向异性弹性一律显式写 temperatureDependency, 不要指望能推。写了之后行宽和它对不上,Abaqus 会当场拦下来 —— 这正是你想要的:把一个静默的错变成一个响亮的错。
温度相关表的排法、同一温度怎么分组、区间外怎么取值, 在温度相关材料表那篇里。
06坑四:覆盖已有材料的正确顺序
脚本跑第二遍的时候材料已经在了。 直觉写法是删掉再建:
del model.materials['Steel_Q345'] # 别这么写
mat = model.Material(name='Steel_Q345')
mat.Elastic(...) # 这里一抛异常,材料就真没了这个写法有一个很贵的失败模式:新数据要是有问题, 旧的已经删掉了,你手工调过的那份材料换来一行报错。 正确的顺序是先建后换:
SCRATCH = 'zzMaterialImporterScratch'
def build_or_replace(model, name, build):
"""build(mat) 负责往材料上写属性;出错时不动已有的那份。"""
exists = name in model.materials
scratch = SCRATCH
counter = 0
while scratch in model.materials:
counter += 1
scratch = '%s%d' % (SCRATCH, counter)
build_name = scratch if exists else name
try:
mat = model.Material(name=build_name)
build(mat)
except Exception:
if build_name in model.materials:
del model.materials[build_name]
raise # 旧材料一个字没动
if exists:
del model.materials[name]
model.materials.changeKey(fromName=build_name, toName=name)多写这十几行,换来的是「失败不丢数据」。 插件内部就是这个模式,暂存名也是这个 —— 它刻意跟被替换的材料没有任何关系, 而不是在原名后面加后缀:Abaqus 的材料名有长度上限, 从原名派生就要截断, 而 Steel_A36_Plate 和 Steel_A36_Pipe截出来的尾巴会撞在一起 —— 那正好是撞了会没人发现的场合。
del model.materials[name] 也可能失败, 最常见的原因是还有 Section 在引用它。 这时候什么都没换、旧的还在,是安全的。 但如果失败发生在 changeKey 那一步, 新材料就是唯一剩下的那份了 ——这时千万不要为了「保持模型整洁」把它删掉, 应该留在暂存名下,让人手工改名。07怎么跑,以及怎么查 API 名字
脚本有两种跑法:
- 在 CAE 里跑。File 菜单里的 Run Script,选中脚本文件。 适合边写边试,异常会打到消息区。
- 无界面批处理。
abaqus cae noGUI=build_materials.py, 适合放进流水线。注意这种跑法里不能弹任何对话框。
abaqus.rpy 是查 API 名字最靠得住的办法
Abaqus 会把 GUI 里的操作对应的内核命令记进工作目录下的abaqus.rpy,参数原样照抄。 所以「某个属性在脚本里叫什么」这个问题有一个不用翻手册的答案:
- 在 CAE 里手工把这个属性填一遍。
- 打开工作目录下的
abaqus.rpy,翻到最后。 - 那几行就是等价的脚本,方法名和参数名都在里面。
abaqus.rpy 记录参数原样照抄,不要把口令、令牌这类东西作为参数传给内核函数 —— 它会明文躺在工作目录里。 插件把下载来的材料数据先落成临时文件再让内核去读, 读完立刻删,就是为了绕开这一点。什么时候别写脚本
脚本的代价是列顺序、常量、温度标志全归你负责, 而这三类错里有两类不报错。 所以如果你要做的事只是「把一份已经定好的材料表建进模型」, 写脚本是在给自己找活干 —— 那种情况用 JSON 文件更省事,格式清单在帮助文档的格式一节,Excel 里的表怎么变成这个文件在Excel 材料表怎么进 Abaqus。
脚本真正划得来的是材料本身要算出来的场合: 按温度插值生成一批、按系数扫描生成一批、 或者建材料只是一段更长的建模脚本里的一步。 三条复用路线怎么选、什么时候该用文件什么时候该用脚本,在材料复用到别的模型里对过一遍。插件替你挡掉的那些检查列在产品页上。