☰
Abaqus 二次开发教程(1):脚本运行方式与执行边界——同一份脚本在三种执行模型里行为不同
2026/11/3 2:48:51 网站建设 项目流程

Abaqus 二次开发教程(1):脚本运行方式与执行边界——同一份脚本在三种执行模型里行为不同

版本声明块

  • 工具/软件:Abaqus 2026(2026 FD01),内嵌 Python 3.10.5(2024 GA 起;此前为 Python 2.7.15,旧版本)
  • 语言/环境:Python(Abaqus 内嵌解释器)+ Abaqus Scripting Interface(ASI);命令行执行入口为abaqus cae与abaqus viewer
  • 本文目标:读完之后,你能判断一段脚本"能在哪里跑、会在哪里炸",并能写出一个不依赖任何图形对象的批处理脚本

一句话结论:abaqus cae script=xxx.py会把图形界面拉起来、可以调用session.viewports;abaqus cae noGUI=xxx.py不启动图形界面、跑完即退出,脚本里引用任何显示对象(viewports、display group、caeModules.visualization)都会失败——判定边界用session.attachedToGui,判定回放状态用session.replayInProgress。

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

  1. 同一个.py文件,用script=、noGUI=、replay=三种方式跑,差别到底在解释器、在进程,还是在可访问的对象集合?
  2. replay文件(.rpy)与journal文件(.jnl)是不是同一样东西?为什么保存.cae时才生成.jnl?
  3. 为什么官方讲义把"录制脚本"与"可复用脚本"分成两件事?录制出来的代码缺了什么?
  4. 无图形界面时脚本真的完全不能碰显示相关对象吗?哪些对象在 noGUI 模式下依然可用?
  5. 提交作业(Job)这件事在三种执行模型里行为一致吗?为什么批处理里submit()之后还需要waitForCompletion()?

一、机制解析

1.1 三层结构:解释器、内核、图形前端

先把最容易被忽略的事实摆出来:ASI 不是"Python 调 Abaqus",而是"Python 解释器被嵌进 Abaqus/CAE 的进程里,ASI 命令是 Python 的语法扩展"。官方讲义《Introduction to Abaqus Scripting》把这层关系讲得很直白——ASI 是 Python 语言的扩展,使用 Python 语法,但它操作的是 Abaqus/CAE 内核(kernel)里的对象。这就决定了脚本的执行边界由"内核在不在、图形前端在不在"两件事共同决定。

┌──────────────────────────────────────────────┐ │ abaqus cae 执行过程 │ ├──────────────────────────────────────────────┤ │ ① 解释器层:内嵌 Python 3.10.5 │ │ import abaqus / abaqusConstants / caeModules │ │ │ │ │ ▼ │ │ ② 内核层(kernel):对象与状态 │ │ mdb(模型数据库)、session(会话)、 │ │ 各模块对象(caeModules) │ │ │ │ │ ▼ │ │ ③ 图形前端层(可选):viewports、 │ │ display groups、XY plots、动画 │ └──────────────────────────────────────────────┘ ② 恒在;③ 只在有 GUI 时在

为什么这对你重要:绝大多数"脚本在 GUI 里能跑、放到服务器批处理里就报错"的问题,都出在第③层被删掉了。理解了分层,报错就不再是玄学——它只是"脚本访问了本不该存在的层"。

1.2 三种执行模型对照表

官方abaqus cae执行过程的命令行选项(按官方文档站的命令参考整理)为:

abaqus cae [database=database-file] [replay=replay-file] [recover=journal-file] [startup=startup-file] [script=script-file] [noGUI=noGUI-file]
执行模型触发方式图形前端可访问对象结束行为典型用途
交互会话abaqus cae有mdb、session、viewports、caeModules 全部手动退出,动作被写进.rpy探索、调试、录制
GUI + 脚本abaqus cae script=xxx.py有全部脚本跑完仍在会话中需要截图、需要边跑边看
批处理abaqus cae noGUI=xxx.py无mdb、session 的基础成员、odbAccess命令跑完即退出参数扫描、批量后处理、CI 流水线
回放replay=xxx.rpy视命令而定与所用模式一致逐条重放命令恢复一个会话的建模过程
从日志恢复recover=xxx.jnl视命令而定同上重放命令以重建已保存的模型数据库事故恢复、把日志转成脚本

过渡句:表里"可访问对象"这一列是本篇真正的干货,但要读透它,必须先搞清.rpy和.jnl这两个文件到底谁是谁——很多人在这里混淆,导致"回放后模型对不上"。

1.3.rpy与.jnl:一个是"动作流",一个是"结果重建"

按官方文档站对会话生成文件的说明:

  • abaqus.rpy(replay 文件):记录一次会话中几乎每一个建模操作对应的 ASI 命令。它是"动作流",工作目录里从会话开始就在写。
  • model_database_name.jnl(journal 文件):只有在你执行File > Save保存模型数据库时才生成,内容是"能重建这份已保存模型数据库"的命令。它偏"结果快照的重建指令"。
  • model_database_name.rec(recover 文件):记录"自上次保存以来"改动模型数据库的命令,用于恢复未保存的会话。

三者的差别不在格式,而在覆盖范围与生成时机。经验法则:想复刻"你点了哪些鼠标",用.rpy;想复刻"保存时的模型数据库",用.jnl。这两个文件里几何选择如何被记录,由session.journalOptions控制——默认记录的是内部 ID/名称,如果希望用坐标记录(便于换机器、换模型仍能对上),要显式设置replayGeometry与recoverGeometry(例如设为COORDINATE)。这是【最佳实践】:把录制结果当作半成品,不要直接当生产脚本。

1.4 noGUI 的执行边界,用两个标志判定

noGUI 模式下图形前端层不存在。【铁律】第 8 条:noGUI 批处理脚本不得引用任何可视化对象——session.viewports、display group、XY plot 绘图对象、动画与图像输出相关对象在批处理里不可用。能用的仍然是内核层的对象:mdb(模型数据库)、session的基础成员、各模块对象、odbAccess打开的 ODB。

判定与防御写法:

fromabaqusimportmdb,sessionimportsysifsession.attachedToGui:# 交互会话:图形前端存在,viewports 可用print('GUI 会话:可以显示、可以截图')else:# noGUI / 非交互执行:图形前端不存在print('批处理模式:禁止任何显示对象调用')sys.exit(0)

session.attachedToGui是官方 Session 对象的成员,含义是"当前是否运行在交互式会话中";session.replayInProgress表示"当前是否正在执行 replay 文件"。这两个标志之所以重要:同一个函数在两种状态下应该走两条分支(GUI 里显示结果、批处理里写 CSV),否则脚本无法同时服务于交互与批处理。

1.5 提交作业:submit()是异步的

【铁律】之外的一条【最佳实践】:批处理脚本里submit()之后必须waitForCompletion(),否则脚本会继续往下跑,而.odb还没写完——后处理读到的是半成品,甚至直接抛异常。官方参考里waitForCompletion()的语义是"中断脚本执行直到分析结束";如果此时status既不是 SUBMITTED 也不是 RUNNING,它会立刻返回。这也是为什么批处理管线里"提交→等待→判定 status→再开 ODB"的顺序不能随意打乱。

1.6 进程视角:noGUI 到底省下了什么

同一个脚本在 GUI 与 noGUI 下运行,差别不只是"有没有窗口"。GUI 会话要初始化图形前端:创建默认视口、加载显示选项、维护 display group 与标注层,并把每次几何选择翻译成可视化状态。这些动作在脚本里表现为对session.viewports['viewportNumber:1']、session.graphicsOptions、session.defaultOdbDisplay一类对象的调用。noGUI 把这些全部跳过,于是:

  • 启动更快:没有图形前端初始化,也没有字体与绘图缓存的加载;
  • 内存更省:显示相关仓库(viewports、images、movies、paths)不会被创建;
  • 输出形态变了:GUI 里"看到结果"是产出,批处理里"文件是产出"——CSV、JSON、日志才是可被下游消费的东西。

但 noGUI 不是万能加速。真正耗时的大头是网格划分、作业求解与 ODB 读取,这些跟图形前端无关。【经验法则】如果一条批量任务里"建模 + 后处理"占 10 分钟、图形初始化占 30 秒,那么从 GUI 切到 noGUI 的收益不到 5%;反过来说,当你把同一个脚本在 CI 里跑 500 次,30 秒 × 500 就是 4 小时的纯浪费——这才是 noGUI 的价值所在。为什么这对你重要:它决定你"要不要把脚本改成批处理"这个工程决策的成本收益,而不是凭感觉优化。

1.7 执行边界的三条判据

把边界写成可执行的判断,比背规则更可靠:

  1. 对象是否存在:脚本引用的对象若只在图形前端层存在(视口、显示组、XY 绘图、动画),批处理里必失败。判据是session.attachedToGui。
  2. 是否需要人:官方对 noGUI 的说明明确——由于没有界面,脚本不能包含任何用户交互。凡是依赖"等用户点确认"的逻辑都要改成参数或配置文件。
  3. 产出落在哪里:批处理里print的输出会进入控制台或日志,而文件写入才有可追溯的产出。判据是"跑完后目录里有没有可校验的产物文件"。
判据GUI 会话noGUI 批处理常见踩坑
显示对象可用不可用用截图当作"验证通过",批处理里直接抛异常
用户交互允许禁止File > Run里弹窗确认,批处理里卡死或失败
产出形态屏幕 + 文件仅文件只打印不写文件,流水线无法判定成败
退出方式手动跑完自动退出异常发生在脚本末尾,退出码丢失,CI 误判成功

1.8 双分支输出:把"显示"与"落盘"分开写

成熟脚本的写法是:核心逻辑只操作内核对象,最后按执行模型选择出口。这样同一份脚本既能给工程师在 GUI 里看,也能给流水线在批处理里跑。判据仍然是session.attachedToGui——它是官方 Session 成员,不依赖任何第三方包。

# dual_exit.py —— 同一份脚本,两种出口(这是 1.8 的示意片段,完整可运行)fromabaqusimportmdb,sessionfromabaqusConstantsimport*importjson result={'model_count':len(mdb.models),'job_count':len(mdb.jobs)}# 上面这一段只碰内核对象:两种执行模型都能跑ifsession.attachedToGui:# 图形前端存在:给工程师看view=session.views['Iso']# session.views 是显示相关仓库session.viewports['Viewport: 1'].view.setValues(view)print('GUI:已切到等轴测视图,结果同时写入 result.json')withopen('result.json','w')asf:# 文件是两种模型共同的产出:批处理的"成功"只能靠产物与退出码判定json.dump(result,f,indent=2)print('模型数=%d,作业数=%d'%(result['model_count'],result['job_count']))

这段代码的关键在顺序:先做内核操作,再按执行模型分支。如果反过来把显示操作放在最前,noGUI 会在第一行就失败,连产物都不会生成——这正是许多"批处理跑不出文件"的真实根因。

过渡句:机制讲完,接下来把这套边界落成代码。下面三段脚本分别对应"批处理骨架"“录制文件体检”“启动钩子”,每段都带逐行注释与核心决策点。

二、完整代码与逐行剖析

2.1 一个可复制即运行的 noGUI 脚本骨架

保存为pipeline.py,执行:

abaqus cae noGUI=pipeline.py
# pipeline.py —— 批处理骨架:无图形界面,只做内核对象操作与文件输出fromabaqusimportmdb,session# mdb=模型数据库,session=会话;两者都是内核对象fromabaqusConstantsimport*# 【铁律】第 3 条:符号常量不可省略,ON/OFF 等大小写敏感importodbAccess# ODB 读写入口(第 9 篇详述)importmesh# 单元类型对象 ElemType 定义在 mesh 模块里importosimportsysdefbuild_minimal_model(model_name,length_mm,section_mm,load_n):"""只用到内核对象,不碰任何显示对象——这样脚本才真正能跑在 noGUI 下。"""model=mdb.Model(name=model_name)# 新建一个模型,返回 Model 对象sketch=model.ConstrainedSketch(name='profile',sheetSize=200.0)# 在草图里画一个矩形截面(草图是 model 的成员,属于内核,不属于图形前端)sketch.rectangle(point1=(0.0,0.0),point2=(section_mm,section_mm))part=model.Part(name='Beam',dimensionality=TWO_D_PLANAR,type=DEFORMABLE_BODY)part.BaseShell(sketch=sketch)# 由草图生成 part 主体model.Material(name='Steel')# 材料定义model.materials['Steel'].Elastic(table=((21000.0,0.3),))# 单位:mm–N–s(【铁律】第 1 条)。21000 即 N/mm^2(MPa),不再换算一次model.HomogeneousSolidSection(name='Section',material='Steel',thickness=1.0)assembly=model.rootAssembly# 每个 Model 自带 rootAssembly 成员assembly.DatumCsysByDefault(CARTESIAN)inst=assembly.Instance(name='BeamInst',part=part,dependent=ON)model.StaticStep(name='LoadStep',previous='Initial')fixed_edge=part.edges.findAt(((0.0,section_mm/2.0,0.0),))part.Set(name='FixedEdge',edges=fixed_edge)# 边界条件挂在 instance 的 set 上:instance 是"装配里的那一份",与 part 不是同一对象model.DisplacementBC(name='FixedBC',createStepName='LoadStep',region=inst.sets['FixedEdge'],u1=0.0,u2=0.0)load_edge=part.edges.findAt(((length_mm,section_mm/2.0,0.0),))part.Surface(name='LoadSurf',side1Edges=load_edge)model.Pressure(name='EndLoad',createStepName='LoadStep',region=inst.surfaces['LoadSurf'],magnitude=load_n/(section_mm*section_mm))# 压力 = 力 / 面积;这里把"100 N 集中力"化成压强,单位必须与前面一致part.seedPart(size=1.0,deviationFactor=0.1)# 全局种子:每条边按 1 mm 划分part.setMeshControls(regions=part.faces,technique=STRUCTURED)elem_type=mesh.ElemType(elemCode=CPS4R,elemLibrary=STANDARD)# 减缩积分:需要显式指定(第 6 篇)part.setElementType(regions=part.faces,elemTypes=(elem_type,))part.generateMesh()returnmodeldefsubmit_and_wait(job_name,model_name):mdb.Job(name=job_name,model=model_name)# Job 挂在 mdb.jobs 仓库里job=mdb.jobs[job_name]job.submit()# 异步:提交动作本身不阻塞job.waitForCompletion()# 【最佳实践】等结束再读结果,否则读到半成品ifjob.status!=COMPLETED:print('作业未正常完成,status=%s'%job.status)returnFalsereturnTruedefreport_csv(job_name,model_name,out_path):odb=odbAccess.openOdb(path=job_name+'.odb',readOnly=True)# 只读打开:避免 .lck 锁文件冲突(分析正在写 ODB 时会写入锁文件)try:frame=odb.steps['LoadStep'].frames[-1]# 最后一个输出帧u=frame.fieldOutputs['U']rows=['Node,U1,U2']# 表头,便于下游 pandas 直接读forvinu.values:rows.append('%d,%.6f,%.6f'%(v.nodeLabel,v.data[0],v.data[1]))withopen(out_path,'w')asf:f.write('\n'.join(rows))print('CSV 行数(含表头):%d'%len(rows))finally:odb.close()# 【铁律】第 9 条:显式 close,否则句柄泄漏returnTrueif__name__=='__main__':# 【铁律】第 8 条的防御写法:在批处理里绝不走显示分支ifsession.attachedToGui:print('本脚本设计用于 noGUI,检测到 GUI 会话仍可运行,但显示相关代码已省略')ok=build_minimal_model('Model-1',100.0,10.0,100.0)isnotNoneifokandsubmit_and_wait('Job-1','Model-1'):report_csv('Job-1','Model-1','result.csv')sys.exit(0)

逐行看几个真正影响成败的决策点:

  • from abaqusConstants import *缺了它,TWO_D_PLANAR、CARTESIAN、STRUCTURED、COMPLETED全是NameError。【铁律】第 3 条。
  • model.Part、model.Material、part.Set这类"工厂方法"都挂在所属对象上,而不是全局函数;这是 ASI 与常规 Python 最大的手感差异,第 2 篇会系统展开。
  • job.status是 SymbolicConstant,用!=比较没问题,但绝不要拿它当字符串比较——COMPLETED不是字符串'COMPLETED'(虽然它打印出来像字符串)。
  • finally: odb.close()而不是直接close():批处理在循环里跑几十个 ODB,异常路径没关文件会累积句柄,这正是【铁律】第 9 条存在的理由。

2.2 录制文件转脚本:把.jnl变成可维护的入口

# journal_to_script.py —— 演示录制语义如何影响脚本可移植性fromabaqusimportmdb,sessionfromabaqusConstantsimport*importsysimportos# 让 replay 与 journal 里的几何选择按坐标记录(官方 JournalOptions 的语义)# 这样换一个模型、换一台机器时,录制出来的行更可能仍然指向同一个几何实体session.journalOptions.setValues(replayGeometry=COORDINATE,recoverGeometry=COORDINATE)work_dir=os.getcwd()rpy=os.path.join(work_dir,'abaqus.rpy')jnl=os.path.join(work_dir,'model_database_name.jnl')print('replay 文件是否存在:%s'%os.path.exists(rpy))print('journal 文件是否存在:%s'%os.path.exists(jnl))print('正在回放中:%s'%session.replayInProgress)# 把录制文件当"数据"读,而不是当"程序"跑:先统计行数与是否含显示相关行defscan(path):ifnotos.path.exists(path):return0,0total=0gui_hits=0withopen(path,'r')asf:forlineinf:total+=1# 这些前缀出现在图形前端相关命令上:批处理里放到 noGUI 会失败if'Viewport'inlineor'displayGroup'inlineor'xyPlot'inline:gui_hits+=1returntotal,gui_hitsfornamein(rpy,jnl):total,gui_hits=scan(name)print('%s:行数 %d,其中显示相关行 %d'%(name,total,gui_hits))# 判断标准:gui_hits > 0 说明这份录制不能直接搬进 noGUI 流水线ifscan(rpy)[1]>0:print('需要人工剔除显示相关行,或改为双分支(GUI 显示 / 批处理写文件)')sys.exit(1)sys.exit(0)

这段脚本的核心思想:录制的产物是"证据",不是"架构"。第 19 篇会讨论 journal 膨胀与回放不可幂等的工程后果——同一次录制跑两遍,结果未必一致,因为回放会按顺序重放所有操作,包括你手动撤销的那几步。

2.3 startup 文件与插件的最小执行入口

# my_startup.py —— 每次 Abaqus/CAE 启动都会执行的入口(GUI 模式)fromabaqusimportsessionfromcaeModulesimport*# 官方模块入口:assembly、mesh、part、property、job 等模块对象fromdriverUtilsimportexecuteOnCaeStartupimportsys executeOnCaeStartup()# 官方 driverUtils 入口,插件与 startup 脚本的标准写法ifnotsession.attachedToGui:# 批处理里没有图形前端,下面这段永远不会执行——这就是执行边界sys.exit(0)viewport=session.viewports['Viewport: 1']# 只在 GUI 里存在viewport.maximize()print('startup 已执行,当前视口:%s'%viewport.name)print('执行环境:Python %s'%sys.version)

为什么 startup 值得单独讲:它是"每次会话都生效"的全局钩子,插件通常从这里挂菜单。也正因为它每次会话都跑,把它写成"只在 GUI 里才动显示对象"是【最佳实践】——否则同一个 startup 文件在noGUI下会拖垮整条批处理线。

三、常见报错与排查

  1. 现象:脚本在File > Run里正常,改成abaqus cae noGUI=xxx.py立刻失败,报错指向 viewports 或显示相关对象。
    根因:图形前端层没有启动,显示对象不属于内核层。【铁律】第 8 条被违反。
    解法:把显示相关代码收进if session.attachedToGui:分支;批处理路径只写文件(CSV/JSON),不要依赖绘图对象。

  2. 现象:NameError: name 'ON' is not defined(或类似符号未定义)。
    根因:漏了from abaqusConstants import *,或把常量大小写写错。【铁律】第 3 条。
    解法:在脚本头部显式导入abaqusConstants;常量一律按官方大小写写(ON/OFF、MODEL)。

  3. 现象:session.journalOptions设置的几何记录方式在回放时对不上几何实体。
    根因:默认按内部 ID/名称记录,换模型或换会话后 ID 变了。
    解法:用setValues(replayGeometry=COORDINATE, recoverGeometry=COORDINATE)按坐标记录;这是【最佳实践】,代价是录制文件变长。

  4. 现象:批处理里submit()之后立刻打开 ODB,读到空数据或报错。
    根因:submit()是异步的;.odb还在被分析进程写。分析正在写时 ODB 会有.lck锁文件。
    解法:job.waitForCompletion(),再判断job.status是否为COMPLETED,最后只读打开。

  5. 现象:Python 2 时代写的脚本在 Abaqus 2024+ 上报语法错误(例如print "hello")。
    根因:2024 GA 起内嵌 Python 为 3.10.5(官方 Release Notes),print语句不再合法。
    解法:按第 4 篇的断点清单迁移;先用python -m py_compile 脚本.py做语法体检(用系统 Python 检查语法,不依赖 Abaqus 运行时)。

四、动手练习

  1. 执行边界自检:把 2.1 的脚本分别用abaqus cae script=pipeline.py和abaqus cae noGUI=pipeline.py跑一次。
    判定标准:前者打印GUI 会话,后者打印批处理模式;两者都应生成result.csv,且 CSV 含表头行Node,U1,U2。

  2. 录制文件体检:在 GUI 里手动建模 5 分钟(含一次改变视图、一次 XY 绘图),然后运行 2.2 的脚本。
    判定标准:abaqus.rpy的"显示相关行数" > 0;把这段代码复制到 noGUI 环境运行必然失败。记录该数字,作为你团队"录制脚本不可直接投产"的量化证据。

  3. 异步提交验证:把job.waitForCompletion()去掉,比较两次result.csv的行数。
    判定标准:去掉等待时,CSV 行数 < 有等待时的行数(或 ODB 打开失败);有等待时 CSV 至少 1 行表头加若干数据行,且job.status打印为COMPLETED。

五、小结与下一篇预告

本篇把"跑脚本"拆成三层:解释器、内核、图形前端;三种执行模型的差别本质上就是"图形前端在不在"。.rpy是动作流、.jnl是保存时快照的重建指令、.rec是未保存部分的增量——三者的覆盖范围不同,不能互相替代。批处理的正确姿势是"只用内核对象 + 显式等待 + 显式关闭"。下一篇(第 2 篇)把内核层展开成对象树:mdb、session与caeModules的层级关系,以及为什么 ASI 里几乎一切修改都必须走setValues(【铁律】第 4 条)。第 3 篇会把本篇的骨架跑成一个完整闭环,第 4 篇处理 Python 2 → 3.10 的迁移断点。


本篇认知问题回显(FAQ)

Q1:abaqus cae script=与abaqus cae noGUI=跑同一个 Abaqus Python 脚本,区别在解释器还是在可访问对象?

A:同一个内嵌 Python 3.10.5 与同一套内核对象;区别是 noGUI 不启动图形前端,session.viewports不可用且跑完即退出。

Q2:Abaqus/CAE 的 replay 文件(.rpy)与 journal 文件(.jnl)分别记录了什么?

A:.rpy记录会话中几乎每个建模操作的 ASI 命令;.jnl在File > Save时生成,用于重建已保存的模型数据库。

Q3:为什么 Abaqus 录制出来的.rpy脚本不能直接放进 noGUI 批处理流水线?

A:录制含视口、显示组、XY 绘图等图形前端命令,这些对象在 noGUI 下不存在,必须剔除或收进session.attachedToGui分支。

Q4:判断当前 Abaqus Python 脚本是否运行在交互式 GUI 会话里,应该用哪个成员?

A:用session.attachedToGui判断是否交互式会话;session.replayInProgress表示是否正在执行 replay。

Q5:在abaqus cae noGUI批处理里提交作业后为什么必须调用waitForCompletion()?

A:job.submit()是异步的;waitForCompletion()才把脚本停在分析结束处,之后才能安全打开.odb。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询