通过 Python 的 win32com 动态派发驱动 Aspen Custom Modeler,实现连接挂载、点号路径变量读写、稳态与动态仿真运行及 Task 激活,并附 10 个真机验证案例与进程清理、看门狗等稳定性配方。
✓已通过基础校验
名称、分类、版本与作者信息完整,安装包与文档齐备。
✓可直接使用
提供 Markdown 文档与配置示例,下载后按文档说明使用即可。
v1.2.0(2026-10-04):在 v1.1.0 全链路真机验证基础上,用 10 个真实模型案例全部复现,固化出 4 条新铁律(反斜杠路径、单实例孤儿清理、模式感知 Run、看门狗守护 Run),并修正了 v1.1.0 对 HeatedCube/FiveTank/NonIsothermalReactor "可 Run" 的误判——它们经此 COM 路径会挂起(已用看门狗安全收尾,属已知限制)。本文件是索引 + 铁律;详细配方见
references/v11_com_recipes.md;10 个可运行范本见examples/case01..case10*.py(共用examples/_acm_helper.py)。
本机实测证据链(关键调用 + 真实返回值):
# 连接(win32com 动态派发,非 comtypes)
app = win32com.client.dynamic.Dispatch(pythoncom.GetActiveObject(CLSID)
.QueryInterface(pythoncom.IID_IDispatch))
app.Version -> '37.0' # V11.0 内部版本号
app.Name -> 'Aspen Custom Modeler'
app.ActiveDocument -> 已打开的 .acmf
doc.Flowsheet.BOIL1.Q.Value -> 4.0 # 读变量(点号路径)
doc.Flowsheet.BOIL1.Q.Spec="fixed"; .Value=5.0 -> 回读 5.0 # 写变量
doc.Flowsheet.BOIL1.Q.Spec -> 'Fixed'
app.Simulation.Run(True) -> None; State='Paused' # 稳态求解(收敛后暂停=成功)
app.Simulation.Tasks (via fs.Tasks) -> Count/Name/Active # 任务
app.Quit() + taskkill -> 干净退出,无残留
注册表实测:ProgID ACM Application(CLSID {FDE14674-8E3D-0370-878A-2E97C127D967}),
LocalServer32 = D:\Aspen V11\AMSystem V11.0\Bin\AspenModeler.exe /acm /Automation。
win32com.client.dynamic(动态派发),不是 comtypes、也不是 win32com.genache/EnsureDispatch。comtypes:能连接、能读属性,但 Run 这种长调用会卡死/崩溃服务器——comtypes 不泵送 Windows 消息,ACM 在 Run 期间的事件回调无法投递 → 远程过程调用失败/RPC 服务器不可用,且会污染后续连接。不可用。win32com.client.gencache.EnsureDispatch("ACM Application"):直接报 This COM object can not automate the makepy process(ACM 类型库无标准 typelib,无法早绑定)。不可用。win32com.client.dynamic.Dispatch(obj) + 先 QueryInterface(pythoncom.IID_IDispatch)(因为 GetActiveObject 返回的是 PyIUnknown):全链路可用,已逐节点验证。 这是本 skill 的唯一正确路线。comtypes 并自行处理了消息泵/线程;对单脚本自动化,win32com.dynamic 最简单且本机验证可靠。.acmf 启动 + GetActiveObject 挂载」。GetActiveObject 需轮询 + 等文档真正就绪:启动后约 8 秒 COM 服务器才就绪,循环 sleep 1s 最多 ~40 次;连上后还要轮询 ActiveDocument.Name 直到等于文件名,否则可能挂到空的 'Untitled' 文档上。val = doc.Flowsheet.B1.Tank.h.Value(等价 doc.Flowsheet.Blocks("B1").Tank.h.Value)doc.Flowsheet.B1.Q.Spec = "fixed" 然后 doc.Flowsheet.B1.Q.Value = 5.0GetVariable/SetVariable 方法! 变量是 Flowsheet 上按「块名 → 变量名」动态解析的属性(GetIDsOfNames)。doc.Variables/sim.Variables 只是整数计数,不是集合。B1.Tank.h)。app.Simulation(不在 doc 上,也不在 doc.Simulation)。本机验证:app.Simulation.Run(True) 稳态、app.Simulation.Run(False) 动态app.Simulation.Reset() 复位app.Simulation.State('Ready'/'Paused'/'Running'/...)、app.Simulation.RunMode('Steady State'/'Dynamic'/'Optimization'/'Estimation')doc.Run 也存在,但一律用 app.Simulation.Run 更稳。os.path.normpath)。Run(True):None,State='Paused' → 求解收敛后暂停,视为成功。STS_F_Failure(COM 错误 189)但 State 仍到达 'Paused' —— 这是模型自身收敛状态(示例文件未必完全收敛),不是自动化故障。判定成功应以 State=='Paused' 为准,异常可容错捕获。Run(True) → Paused(成功;可容忍 STS_F_Failure)。Run(True)→Paused 锁初值,再 Run(False)→Running 进入动态。Run(True) 与 Run(False) 经此 COM 路径都会挂起不返回(bash timeout 杀掉,exit=124)。Run(True) 锁初值 → Run(False) 到极小时间(锁定状态变量)→ 再 set_variable(写变量)施加阶跃 → 继续 Run(False) 逐点读变量。Run(False) 之前 set_variable——否则 ACM 重初始化状态变量,看不到瞬态。doc.Flowsheet.Tasks.Count、doc.Flowsheet.Tasks.Item(i).Name、.Active(bool,激活:task.Active = True)。Run 会永久挂起(铁律 10),若在主线程直接调用会卡死整个脚本。_acm_helper.run_sim):把 Run 放进独立工作线程,线程内重新 GetActiveObject 挂载同一个 ACM 实例(COM 对象属于该线程,避开跨线程调用 <unknown>.Run 的坑),再用 t.join(timeout) 做看门狗:正常模型秒回,挂起模型到时后外部 taskkill 干净收尾,脚本绝不卡死。timeout 包一层(如 timeout 120 python caseXX.py),否则可能一直挂着。app.Quit() + taskkill /F /IM AspenModeler.exe(同时清 Modeler.exe);动态运行未停时 Quit 会 RPC 失败,需先 app.Simulation.Reset() 或 taskkill 兜底。残留进程会污染后续连接(连上但 ActiveDocument 指向旧/坏会话)。| # | 脚本 | 模型 | 类型 | 块数 | Tasks | 变量样本(实测) | Run 行为 | 自动化结论 |
|---|---|---|---|---|---|---|---|---|
| 1 | case01 | BTXCDI | Steady | 23 | 0 | BOIL1.Q=4.0, P=2.13, H=35000, Temp=173.6 | Run(True)→Paused(+STS_F_Failure) | ✅ 连接+读写 |
| 2 | case02 | SSMeth | Steady | 5 | 0 | B3.Temp=533.15, Pressure=120 | Run(True)→Paused | ✅ 稳态运行 |
| 3 | case03 | VAMReactor | Steady | 1 | 0 | REACTOR.L=10, D=0.037, alpha=1, Duty=-11.18 | Run(True)→Paused(+STS_F_Failure) | ✅ 稳态容错 |
| 4 | case04 | Absorber | Steady | 1 | 1(FeedDisturbance) | {} | Run(True)→Paused(+STS_F_Failure) | ✅ 稳态+Task枚举 |
| 5 | case05 | Crystallizer | Dynamic | 1 | 0 | V=0.05,Temp=25,rho=1000,v=0.05,w=100,C=0 | Run(True)→Paused, Run(False)→Running | ✅ 动态可跑 |
| 6 | case06 | JacketedReactor | Dynamic | 1 | 0 | u=9.6,S=0.2394,s=0.2394 | Run(True)→Paused, Run(False)→Running | ✅ 动态可跑 |
| 7 | case07 | EUROKIN1 | Estimation | 1 | 0 | T=300,R=8.314,Tau=1,tau=1 | Run(True)→Paused, Run(False)→Running | ✅ 估算模式 |
| 8 | case08 | HeatedCube | Dynamic | 1 | 6(WarmSurface1-3,WarmEdge1-3) | B1.D=1.2 | Run(True/False) 均挂起 | ⚠ 已知限制(看门狗收尾) |
| 9 | case09 | FiveTank | Dynamic | 5 | 3(SubTask1/2,TestTask) | Tank1.H=2.25,Vol=6.75,K=2 | Run(True/False) 均挂起 | ⚠ 已知限制(看门狗收尾) |
| 10 | case10 | NonIsothermalReactor | Optimization | 1 | 0 | H=1,V=10,Temp=350,v=10,h=1,ca=1 | Run(True/False) 均挂起 | ⚠ 已知限制(看门狗收尾) |
规律:稳态全部可 Run(容错 STS_F_Failure);可跑通动态/估算走
Run(True)→Paused再Run(False)→Running;HeatedCube/FiveTank/NonIsothermalReactor 三类(纯动态含多 Task、多块动态、优化)经此 COM 路径会挂起,属已知限制。
⏱ 注意:Crystallizer / JacketedReactor 的稳态求解较慢(实测Run(True)约 60s+),看门狗超时须给到 ≥150s,否则会被误判为挂起(已在该两个案例脚本中设timeout=150)。
pip install pywin32 # 唯一需要的包(用 win32com.client.dynamic,不是 comtypes)
D:\Aspen V11\Aspen Custom Modeler V11.0;exe =D:\Aspen V11\Aspen Custom Modeler V11.0\Examples\推荐直接用 _acm_helper.launch_and_attach()(已封装铁律 2/4/7/8:normpath + 杀残留 + 等就绪 + 等文档加载):
import os, sys
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
from _acm_helper import launch_and_attach, safe_quit
EXAMPLE = r"D:\Aspen V11\Aspen Custom Modeler V11.0\Examples\BTXCDI\btxcdi.acmf"
app = launch_and_attach(EXAMPLE)
if app is None:
raise SystemExit("连接失败")
doc = app.ActiveDocument
sim = app.Simulation
完整可运行范本:
examples/case01_connect_readwrite_btxcdi.py(本机真机通过:启动→挂载→读 23 块→读/写 BOIL1.Q 4.0→5.0→还原)。
```python
val = doc.Flowsheet.BOIL1.Q.Value # 读 -> 4.0
doc.Flowsheet.BOIL1.Q.Spec = "fixed" # 固定
doc.Flows
c428b164cf7568593b676ad331e64bda653eeed689d1321b16c6ff79791492dd