版本声明本篇基线为AVEVA E3D Design 3.1涉及前代AVEVA PDMS处显式标注。PML Add-in 机制的小节标题Defining a PML Add-in、PML Application Control Object、Create aPLOBJControl File、Create a UIC File、How to Add a PML Add-in to the PowerWheel™与真实 Addin 名Aveva.Core.PMLPseudos取自官方 PMLCG 章节页PMLCG24.27.01、PMLCG28.17.01。.uic的内部元素结构本次调研未取得可引用示例本篇不臆造。未找到的事实如实交代本次调研未取得官方公开的Draft/Isodraft 批量化出图 PML API章节 URL也未取得官方关于以无人工交互方式启动宏或命令的公开章节 URL。因此本篇不给出出图与报表任务体的具体 API 调用任务体一律标为占位同时不臆造任何命令行参数或静默启动开关。本篇脚本按通用 PML 语法与纯文本处理构造标注需在本地 E3D Design 环境验证日志行数、任务数量、时间戳一律为示意真实值须在本机跑出。一句话结论无人值守能不能成立不取决于脚本有多聪明而取决于三件事有没有做硬——任务清单是否唯一、重复执行是否幂等、失败是否被隔离并留下可续跑的证据。〇 认知问题PML Add-in 和 .NET Addin 是同一套机制吗两者的配置文件是同几个文件吗.plobj控制文件、.uic文件、PowerWheel 这三者谁是开关、谁是外观、谁是载体官方为什么明确说不建议从标签页按钮直接调用 PML 宏照做会付出什么代价怎样让一批任务重跑一次也不出错幂等无人值守跑到一半崩了我凭什么知道崩在哪、并且只补跑失败的那几项一 机制解析1.1 三类批量任务一个共性工程项目里的批量作业基本落在三类里类别输入产物风险点批量出图模型 出图规则图纸文件产物体积大、数量多、耗时长批量报表模型 报表定义表格/Excel/CSV字段口径易漂移批量校验模型 规则集校验清单/命中清单判据必须离散、可复跑三者的共性只有一条但决定了所有架构它们都是对一批对象做同一件事所以一切问题都会以数量对不上的形式暴露出来。于是批量作业的全部工程学就围绕数量的可解释性展开清单里有多少项、成功多少、失败多少、跳过多少、重跑之后这四个数是不是还一样。1.2 官方 PML Add-in 机制的五个步骤官方 PMLCG 的 PML Add-ins 章节给出的路径是五步顺序与名称照录Defining a PML Add-in定义 PML Add-inPML Application Control ObjectPML 应用控制对象Create aPLOBJControl File创建 PLOBJ 控制文件Create a UIC File创建 UIC 文件How to Add a PML Add-in to the PowerWheel™把 PML Add-in 挂到 PowerWheel 上章节里还出现了一个真实的 Addin 名Aveva.Core.PMLPseudos。从这五步可以读出三条机制事实PML Add-in 不是 .NET Addin。它是 PML 侧的机制核心配置/控制文件是PLOBJ控制文件而 .NET Addin 走的是模块名Addins.xml例如 MODEL 对应DesignAddins.xml与CAF_ADDINS_PATH。配置文件名与控制文件完全不同这是两套机制这正是第 1 个认知问题的答案。它有一个应用控制对象。也就是说PML Add-in 的运行是被一个控制对象管着的而不是文件一放就跑。它的界面落点是 PowerWheel™。挂载方式在官方目录里单独成节说明官方把它当作可挂到界面上的定制单元来看。1.3.plobj与.uic的分工第 2 个认知问题的答案用一句话就能记住PLOBJ是开关UIC是外观PowerWheel™ 是载体。PLOBJ控制文件PML Add-in 的控制文件决定这个 Add-in 怎么被控制PML 侧机制的入口文件。UIC文件UICUser Interface Customisation文件是 XML用来定义菜单与工具栏并通过命令对象的Key与逻辑关联。官方机制口径是命令可以实现为PML command object 或 core command object然后通过.uic文件把工具与命令对象关联起来。所以.uic决定长什么样、点了之后调到谁。PowerWheel™挂载点。位置差异在环境变量上看得见——CAF_ADDINS_PATH管 Addin 位置CAF_UIC_PATH管界面定义文件位置它以模块名Customisation.xml为入口查找并列出.uicAVEVA_CAF_TRACING用于开发期跟踪。模块级配置里有一条很实用的机制模块名Customisation.xml中的$1会被当前工程名替换。这在多项目共用一套定制时特别有用——同一份界面配置可以在不同工程下加载不同的.uic。注意.uic的内部元素结构本次调研未取得可直接引用的官方示例。稳妥做法是在界面上用工具栏右键 → Customize交互式编辑出一个最小配置把生成的文件当作模板再手工扩展。这比照着想象写 XML 靠谱得多。1.4 为什么官方不建议从标签页按钮直接调宏官方在 Commands 机制处的口径是明确的不建议从标签页按钮直接调用 PML 宏命令应当实现为命令对象并通过.uic关联。这个不建议背后有三层实际理由第 3 个认知问题的答案兼容性旧式的事件/工具栏方式仍受支持但不推荐原因是与 command ribbon 不兼容。直接调宏的按钮在旧式工具栏时代能凑合到了 command ribbon 体系里就会出问题。解耦.uic通过命令Key关联逻辑界面与实现分离直接调宏则把按钮与文件路径焊死在一起宏一改名按钮就废。状态命令机制有CommandState可按位组合与Checked/Enabled/Visible等状态属性能表达什么时候该灰掉裸调宏没有这层表达能力。所以对批量作业来说正确的界面挂载姿势是命令对象 .uic而宏或函数本身作为被命令调用的实现体存在。1.5 无人值守的四个支柱第 4、5 个认知问题要靠这四根柱子回答支柱要解决的问题实现要点唯一清单到底该处理哪些项清单由脚本从模型/表生成生成即存档快照不允许边跑边改幂等重跑一次会不会翻倍每项执行前先判断是否已完成或由外层按上一轮日志跳过已成功项日志崩了怎么知道崩在哪每项一行结构化记录TASK;标识;状态;原因逐项输出失败隔离一项失败能否拖垮全局单次失败不终止循环汇总行给出成功/失败计数失败项单列成重跑清单其中幂等是最容易做错的一环。做法有三档按可靠性排序标记法每项完成后在数据里留一个标记需要可写属性风险最高状态表法每项状态记在外部表/日志里重跑时先查表跳过成功项推荐重导法每次全量重建产物带时间戳、旧产物归档最笨但最稳。本篇代码采用第 2 档状态表法因为它对模型零写入风险最低而且天然满足可回退系列铁律 8。1.6 关于无人值守的触发方式必须如实说明的边界“无人值守听起来像设个定时任务就行”但本次调研未取得官方关于以无人工交互方式启动宏或命令的公开章节 URL也未取得官方 Batch 模式的命令行参数说明。因此本篇只讲脚本内部要做到什么清单、幂等、日志、失败隔离不讲也没法讲如何静默启动。这是刻意保留的空白而不是疏漏在触发方式上给出没有出处的开关名比不写更糟。二 完整代码与逐行剖析2.1 任务清单表结构清单表就是这四个支柱里的第一根。字段如下字段说明示例示意任务标识全流程唯一通常直接用对象名P-1201/B1任务类型DRAW/RPT/CHKCHK作用域该任务的处理范围ZONEAREA-01产物标识预期产物的名字/路径模板占位按模块实际能力填写状态TODO/OK/FAIL/SKIPTODO原因失败/跳过的原因空时间戳状态最后一次变化的时间占位关键纪律清单表在生成时存档一份快照。之后任何重跑都以这份快照为准避免边跑边改清单导致的数量不可解释。2.2 调度骨架逐项执行 结构化日志下面这段脚本以批量校验为任务体它是三类任务里唯一能对模型零写入、纯靠读数据与字符串判断完成的一类并严格遵守单次失败不终止循环-- -- 文件建议名batchCheck.pmlfnc -- 用途对当前 ZONE 下的全部 BRAN 批量执行命名归属校验 -- 输出每项一行 TASK;标识;状态;原因 -- 末尾一行 TASK-SUMMARY;OK数;FAIL数 -- 说明按通用 PML 语法构造需在本地 E3D Design 环境验证。 -- 本脚本只读不写任务体为校验非出图/报表。 -- 出图与报表的任务体需按其所属模块的实际可用接口实现 -- 本次调研未取得官方批量出图 PML 章节故不在此臆造。 -- define function !!batchCheck(!pipeNo is STRING) is REAL !ok 0 -- 成功计数 !fail 0 -- 失败计数 -- 作用域当前 ZONE 下的全部 BRAN清单的生成来源 var !b coll all bran for zone do !x value !b $!x -- 把该 BRAN 置为 CE !id !!ce.name -- 任务标识直接用对象名 -- ---- 任务体命名归属校验 ---- !parts !id.split(/) -- 按斜杠切段社区宏中用过 split !np !parts.size() -- 段数 !st OK !why if (!np ne 2) then -- 期望形如 管线号/分支段段数不等于 2 即不合规 !st FAIL !why segments !np else if (!parts[1] ne !pipeNo) then !st FAIL !why pipe !parts[1] endif endif -- ---- 结构化日志一行一项绝不合并 ---- !ln TASK; !id ; !st ; !why P $!ln -- ---- 失败隔离计数但不中断循环 ---- if (!st eq OK) then !ok !ok 1 else !fail !fail 1 endif enddo -- 汇总行外层流程据此判断本轮是否可以接受 !ln TASK-SUMMARY; !ok ; !fail P $!ln return !ok endfunction2.3 逐行剖析define function !!batchCheck(!pipeNo is STRING) is REAL函数定义形式取自官方培训代码define function !!area(!len is REAL, !wid is REAL) is REAL函数名以!!开头、参数用is声明类型、返回类型用is声明。!ok/!fail两个计数器无人值守的第一条自证材料就是这两个数。没有计数的批量作业等于没有日志。var !b coll all bran for zone清单的来源。用集合在运行期生成清单好处是清单即数据代价是每次跑都重新求值所以清单快照必须另行存档见 2.1 的纪律。$!x单独成行的命令把该分支设为当前元素。社区真实宏里$!x就是这么用的。!id !!ce.name任务标识直接用对象名是一个重要的工程选择它让日志直接和模型对上号不需要再做一层 ID 映射。代价是对象改名后日志会失联——所以模型一旦进入批量流程就应冻结取名。!parts !id.split(/)与!parts.size()字符串方法与.size()。社区宏中出现过!!ce.name.replace(copy-of-,).split(-)与!b.size()的组合是本篇这两行的直接依据。段索引是1 基社区宏用!c[1]、!c[2]。if (!np ne 2) … else … if (!parts[1] ne !pipeNo) … endif … endif嵌套 IF 与ne比较官方控制逻辑示例用了IF (!Type eq BRAN) THEN。刻意不用复合布尔表达式单条件嵌套在版本间行为更可预期。!ln TASK; !id ; !st ; !why结构化日志行。字段顺序固定、分隔符固定是后续能被脚本解析的前提。字符串拼接用社区宏!c[1] - !c[2]同款。P $!ln变量注入打印形式来自官方培训代码中的$!Type注入。失败不中断循环体内没有任何提前跳出的分支。批量作业里一项失败就整体退出是最常见的自伤设计。2.4 失败重跑清单把日志变成补跑输入日志只有变成下一步的输入才有价值。下面这段纯文件处理脚本把本轮日志中失败、以及根本未出现的项汇总成重跑清单# build-retry.ps1 —— 由日志与清单快照生成重跑清单幂等的关键一环param([Parameter(Mandatory $true)][string]$LogFile,# TASK;... 日志[Parameter(Mandatory $true)][string]$Snapshot,# 清单快照每行一个任务标识[Parameter(Mandatory $true)][string]$RetryOut# 输出重跑清单)# 1) 收集上一轮已成功的任务标识$done {}Get-Content-LiteralPath$LogFile|Where-Object{$_-match^TASK;}|ForEach-Object{$f$_-split;if($f[2]-eqOK){$done[$f[1]]$true}}# 2) 清单快照里的全部任务$allGet-Content-LiteralPath$Snapshot|Where-Object{$_.Trim()-ne}# 3) 差集未成功者即为重跑对象已成功者被跳过 —— 这就是幂等$retry$all|Where-Object{-not$done.ContainsKey($_)}# 4) 落盘并给出可核对的数字$retry|Set-Content-LiteralPath$RetryOut-Encoding UTF8清单总数 :$($all.Count)上一轮已成功 :$($done.Count)本轮需重跑 :$($retry.Count)if($all.Count-ne($done.Count$retry.Count)){警告数字不闭合检查清单快照与日志是否来自同一次运行}第 4 步的数字闭合检查清单总数 已成功 需重跑是整套机制的自检器。无人值守最怕的不是失败而是数量对不上却没人发现。只要这一行报警就说明清单快照与日志不是同一次运行的产物——这在交接班、多人协作时几乎必然发生。2.5 上线前的检查清单#检查项判据1清单快照已存档快照文件存在且有生成时间2任务体对模型零写入代码审查确认无写操作3单项失败不中断人工注入一个必失败项确认其余项照常完成4日志格式可解析用脚本解析一次字段数与预期一致5数字闭合总数 成功 失败 跳过6重跑两次结果一致连续跑两次成功集合完全相同7已成功的项被正确跳过重跑清单中不含上一轮成功项8界面挂载走命令机制通过.uic关联命令而非裸调宏第 6 项连跑两次结果一致是幂等性的最终判据也是唯一值得反复做的一项测试。三 常见报错与排查现象 1PML Add-in 的配置文件都放好了PowerWheel 上却没有出现对应的工具。原因候选一.uic文件没被模块级模块名Customisation.xml列入该文件按顺序列出要加载的 UIC且其中的$1会被当前工程名替换写错了就找不到原因候选二文件放的位置不在CAF_UIC_PATH指向的路径下原因候选三.uic里的关联键与命令对象的Key不一致.uic就是靠Key关联的。处置打开AVEVA_CAF_TRACING做开发期跟踪再按上面三条逐一比对路径、文件名、键值。现象 2从标签页按钮直接调用宏行为不稳定甚至点了没反应。原因候选这正是官方明确不建议的做法——旧式事件/工具栏方式仍受支持但与 command ribbon 不兼容。处置改为命令对象PML command object 或 core command object.uic关联让界面与逻辑解耦并获得命令状态的表达能力CommandState等。现象 3重跑一次产物翻倍。原因候选任务不幂等——脚本没有先判断是否已完成的步骤直接又做了一遍。处置按 2.4 的做法以日志中已成功项构成已完成集合重跑时做差集。不要靠我记得跑过了来解决。现象 4一项失败后面所有项都没做。原因候选循环体里有提前跳出BREAK/SKIP的适用范围用错或错误没有隔离导致流程整体中断。处置保证循环体内无提前跳出每个任务体包在自己的最小作用域内用计数 继续代替报错 停止。另外要注意 PML 的错误机制错误不会立即报错是否输出取决于下一行输入——所以别指望报错即中断能当失败隔离用。现象 5日志里全是输出但看不出哪一项失败。原因候选日志没有结构化把多个字段拼成一句自然语言或失败原因字段留空。处置统一为TASK;标识;状态;原因四字段原因字段禁止留空——没有原因的失败记录等于没有记录。现象 6数字对不上但每一行看起来都正常。原因候选清单快照与日志来自不同次运行或中途有人改了清单或某项被跳过但没记SKIP。处置先把跳过也变成一条显式日志记录状态SKIP 原因再跑 2.4 的数字闭合检查。跳过是最容易被隐形的状态。现象 7想做出图或报表的批量任务却找不到对应的调用方式。原因候选本系列调研未取得官方公开的 Draft/Isodraft 批量化出图 PML API 章节也未取得 Reporting 侧批量接口的官方 API 章节。处置不要在脚本里猜 API 名。稳妥做法是把任务体做成可替换的实现槽先接你已经在本机验证过的操作例如按官方界面路径完成一次操作、记录为作业模板等本机确认可用接口后再填入。先跑通流程再补任务体。四 动手练习练习 1跑通调度骨架判定标准出现汇总行在 Command Window 中载入 2.2 的函数并对一个 ZONE 运行。判定标准输出末行形如TASK-SUMMARY;OK;FAIL且OK FAIL等于该 ZONE 下分支数。该脚本需在本地 E3D Design 环境验证。练习 2日志转清单判定标准三个数字自洽把 2.2 的输出保存为文本配合清单快照跑 2.4 的脚本。判定标准脚本打印的三个数字满足总数 已成功 需重跑且未触发警告行。练习 3幂等验证判定标准连跑两次成功集合相同连续运行两次用同一份快照生成两份重跑清单。判定标准两次的需重跑数量与具体条目完全一致。练习 4失败隔离验证判定标准注入失败后其余项照常完成人为构造一个必失败项例如把某个分支改名成不合规形式重跑。判定标准该分支记为FAIL且原因字段非空其余分支照常输出OK汇总行计数正确。练习 5界面挂载自查判定标准能说清三个文件的分工为你手上的一个批量任务设计界面挂载方案写出三个要点PLOBJ控制文件负责什么、.uic关联靠什么键、挂到 PowerWheel 后由谁触发。判定标准三个要点各一句且不含从标签页按钮直接调宏的方案。五 小结与下一篇预告本篇三句话PML Add-in 与 .NET Addin 是两套机制前者靠PLOBJ控制文件与 UIC后者靠模块名Addins.xmlMODEL 对应DesignAddins.xml与CAF_ADDINS_PATH。界面挂载走命令对象 .uic这是官方推荐姿势因为旧式标签页按钮直接调宏方式与 command ribbon 不兼容。无人值守靠四根柱子唯一清单、幂等、结构化日志、失败隔离其中数字闭合检查是最便宜的自检器。到这里你已经能写、能挂、能批量跑了。但还有一个所有二次开发人员迟早要面对的硬问题这套代码当初是为 PDMS 写的现在要搬到 E3D Design 上到底哪些能直接用、哪些必须重写第 19 篇《PDMS 到 E3D 的定制迁移》就讲这件事迁移的政策背景、要迁移的三类资产、命名空间与程序集的代际差异以及一个必须如实交代的关键事实——官方并没有给出一份命名空间迁移对照表网上流传的对照关系只能当线索不是结论。文末 FAQFAQ 1PML Add-in 和 .NET Addin 是同一套机制吗不是。两者都是把定制装入产品的方式但配置与控制文件完全不同.NET Addin 走每个 CAF 应用的模块名Addins.xml例如 MODEL 对应DesignAddins.xml位置可用CAF_ADDINS_PATH覆盖装载后由 CAF 反射找出实现了IAddin的类并调用其Start。PML Add-in 是 PML 侧机制官方章节路径是定义 PML Add-in → PML 应用控制对象 → 创建PLOBJ控制文件 → 创建 UIC 文件 → 挂到 PowerWheel™其标志性文件是PLOBJ控制文件。所以排错时第一件事就是先确认自己在查哪一套机制的配置。FAQ 2.plobj、.uic与 PowerWheel 三者的关系是什么一句记法PLOBJ是开关UIC是外观PowerWheel™ 是载体。PLOBJ控制文件控制 PML Add-in 的装载与行为UICUser Interface Customisation文件是 XML定义菜单与工具栏并通过命令对象的Key与逻辑关联PowerWheel™ 是它们挂上去的界面载体。另外模块级配置模块名Customisation.xml会按顺序列出要加载的 UIC 文件其中的$1会被当前工程名替换而CAF_UIC_PATH决定从哪里找这些界面定义文件。FAQ 3为什么官方不建议从标签页按钮直接调用 PML 宏因为那种做法属于旧式事件/工具栏方式官方口径是仍受支持但不推荐与 command ribbon 不兼容——在旧式工具栏上能凑合到 ribbon 体系里就会出问题。除此之外还有两点实际代价界面与实现被焊死宏一改名按钮就废以及失去命令状态的表达能力CommandState、Checked/Enabled/Visible这些什么时候该灰掉的信息。正确做法是把命令实现为命令对象用.uic通过Key关联。FAQ 4怎样让批量任务重跑一次也不出错幂等核心是执行前先判断是否已完成。三种做法按可靠性排序一是标记法在数据里留标记需要可写属性、风险最高二是状态表法把每项状态记在外部表或结构化日志里重跑时先查表跳过已成功项对模型零写入、推荐使用三是重导法每次全量重建、产物带时间戳、旧产物归档最笨但最稳。无论用哪种最终判据都是一条连续跑两次成功集合完全相同。注意跳过也必须写成显式记录否则数量永远对不上。FAQ 5无人值守崩了怎么定位并只补跑失败项靠结构化日志 数字闭合。日志格式统一为TASK;标识;状态;原因一项一行、原因字段禁止留空末尾给一行TASK-SUMMARY;成功数;失败数。定位时先看最后一行成功记录它后面的第一项就是崩溃点。补跑时用清单快照减去日志中状态为 OK 的集合得到重跑清单——这样已成功的项会被自动跳过。如果清单总数 ≠ 已成功 需重跑说明快照与日志不是同一次运行的产物必须先修这个不一致再重跑否则补跑范围不可信。