
简介面向化工、石油与制药行业的三维工厂设计场景PDMS二次开发教程以PML语言为主线帮助有一定PDMS使用基础、希望拓展软件功能的工程师与二次开发人员快速上手。资源共59个文件压缩包仅237KB涵盖h/cs/cpp源码、pmlfrm/pmlobj等PML脚本、doc/ppt教程文档以及sln/vcproj工程文件和exe/dll示例程序既有接口开发实例也有语法讲解。已有2743人学习内容包括PML语法基础、DARS接口开发、.NET集成、MFC窗口模式等并附带实用辅助工具与编辑器语法高亮配置便于读者对照实践。教程对PML的变量与数据类型、条件与循环控制、函数与子程序、对象属性访问、事件驱动交互均有涉及实例覆盖自动布置管道阀门、定制物料平衡表与管路长度统计、扩展用户菜单和工具栏、设计数据合规性检查等工作。通过这些应用读者可掌握用PML扩展PDMS核心功能的方法提升设计效率与数据校验能力。1. PDMS二次开发教程先想清楚从哪条路线切入PDMS二次开发教程这个关键词背后其实是工程师群体里非常具体的一类需求模型里有几百根管道要批量改口径图纸要按规则自动编号或者一堆模型数据要导到外部系统做校验。用脚本或插件去碰模型把这些重复劳动变成几次点击是PDMS二次开发最核心的价值。开发路线主要有三条内置的PML脚本、PMLNet桥接.NET、以及直接写C#插件。这三条路不是互相替代而是按需求深度递进。PML学习成本最低打开软件就能写适合模型数据的批量操作PMLNet让你在脚本里调用.NET现成的类库C#插件适合复杂界面和外部系统对接。这篇笔记会按“先跑通最小脚本、掌握对象遍历、判断何时上C#、避开高频坑”的顺序展开新手跟步骤走熟手可以直接跳到避坑章看边界与参数。2. PML语言基础与开发环境先跑通一个能改模型的脚本2.1 PML是什么软件内置的脚本语言不用额外装环境第一次接触PDMS的人容易误解二次开发门槛高其实第一步只是打开软件自带的命令窗口敲一行脚本。PMLProgramming Macro Language是PDMS内置的脚本语言语法接近BASIC面向工程对象。与需要编译的开发方式不同PML脚本是解释执行的你敲完一行回车模型里的对象立刻就有反应。最小脚本-- 这是注释不参与解释 message hello pdms这段命令在命令窗口执行后会弹出一个显示hello pdms的消息框。message是PML里最常用的输出函数能把括号里的内容显示到屏幕上适合初学者用来验证脚本是否被执行。注释用--开头脚本再复杂也要保持每个段落有注释后面调试就知道哪一段出了问题。进一步让它修改当前选中对象的名字!obj objhandle(PIPE) !!obj.name TANK-A01!开头的是变量objhandle是PML里取“当前选中对象”的函数PIPE表示类型限定命令窗里你最后一次选中的PIPE就会被拾取到。!!obj是对句柄的解引用把句柄转为真正的可操作对象.name是对象自带的属性。把name改成TANK-A01以后设计模型里这根管道立即改名。第一次跑通这两行一个完整的读写闭环就建立起来了。2.2 三种运行方式命令窗口、脚本文件、编译文件命令窗口适合单行验证写完了就执行不用考虑文件保存。脚本文件适合保存和复用一个批量改名脚本改几个参数就能迁移到下一个项目。编译文件适合交付给同事因为它不暴露源码加载速度也会快一些。保存好的脚本以.pml结尾加载命令$m C:\scripts\rename_pipe.pml$m是PDMS的命令行加载指令后面接脚本的绝对路径。注意路径如果包含空格或者中文部分版本对中文路径的支持不算好踩过坑的人不少我一般会让脚本直接放在C盘根目录或者PDMS安装目录的脚本文件夹下省掉权限和编码的麻烦。脚本编译之后会生成.pmlc文件。编译文件加载快、源码不暴露但从调试角度看编译后的脚本报错时看不到原始行号定位问题更费劲。日常开发用.pml原始文件调试等到交付时才编译是多数人的习惯。2.3 PML的变量、判断与循环三块基础语法一次说清PML的变量不需要声明类型直接赋值即可。整数、实数、字符串、布尔、句柄都在赋值时自动确定。字符串用双引号包起来变量名加!前缀。需要注意PML的变量作用域比大多数脚本语言宽松脚本之间容易互相污染。我习惯给变量加前缀内部临时变量叫!tmp_xxx对外参数叫!arg_xxx这样跨脚本调用时不会互相覆盖。判断语句的写法if !size 8 then message 找到8寸管道 endifif用做相等比较字符串和数字比较时先确认两边类型一致。then结束条件头endif结束整个判断。漏写endif是新手报错理由里的第一名我见过不少人排查半天就是少了一个闭合关键字。循环遍历的核心写法!items list(1, 2, 3) do !i - !items message !i looplist构造一个数组do !i - !items表示从items里依次取出每个元素放到变量!i里直到结束。循环体里不要修改items的结构否则在遍历过程中容易翻车。这套do-loop结构是下一章遍历模型对象时的主角先把这段读写通。2.4 脚本的组织方式先做环境检查再干活一旦脚本超过几十行执行前加环境检查能挡掉大部分误操作。下面这段是一个典型的脚本开头-- run_batch.pml !zone objhandle(ZONE) if !zone 0 then message 请先在设计导航栏选中一个区域 exit endif !pipes collect(all, PIPE, IN, !!zone) message 共找到 !pipes.Count 根管道这段脚本先取当前选中的区域如果没选中objhandle返回0脚本弹窗提示并退出只有选中了区域才继续collect。exit在PML里表示终止整个脚本这种“先校验后执行”的顺序能避免后续操作在错误的环境里空转。最后一行用拼接字符串这样的输出比单纯弹窗更有信息量跑批时能直接看到范围大小。3. 操控对象模型遍历设备、管道与属性的核心写法3.1 对象层级先弄懂PDMS数据库里的树PDMS里的所有模型对象构成一棵树从世界节点开始往下是站点、区域再到设备、管道、分支最后是具体的元件。这棵树不是界面上的装饰它是对象存取的路径依据。你在脚本里给collect指定起点本质上就是在这棵树上划定范围。最常见的层级WORLD SITE ZONE EQUIPMENT PIPE BRANCH 元件这个树结构要结合设计导航栏一起理解。打开导航栏看到的就是这个层级在当前模型里的实例。两个区域、三根管道每个节点在数据库里都有唯一编号脚本里的句柄实际上就是对这个编号的引用。为什么一上来就讲层级因为很多批量操作的脚本写不好问题不在语法而在范围没找准。你想给全厂管道改名结果collect的范围只覆盖了当前区域脚本不报错但结果不完整这种错最难发现。反过来想只改一个区域起点却写成世界节点就会误伤其他地方。先定位范围再写代码。3.2 collect遍历批量改属性的标准骨架批量操作的标准骨架就是collect加do-loop先在目标层级收集对象再逐个处理。下面这段脚本会遍历全厂所有管道把公称直径改成8英寸!pipes collect(all, PIPE, IN, !!world) do !pipe - !pipes !pipe.bore 8 message !pipe.name loopcollect的前四个参数各自有明确含义all表示按类型不限层级PIPE限定只收集管道对象IN表示在起点对象内部查找!!world表示从世界节点开始。do-loop把收集到的对象逐个取出变量!pipe在每次循环里代表一根管道。循环体内直接对属性赋值message用来输出每根管道名字验证循环确实走到了。把PIPE换成EQUIPMENT就是遍历所有设备换成BRANCH就是遍历分支。起点从!!world换成一个区域句柄就只改这个区域内的对象。这种改参数的方式是PML开发最常用的模式一条脚本改三个参数就能适配很多需求。3.3 有条件遍历先筛后改避免误操作批量修改最怕引错对象。给循环体加上判断条件只对满足条件的对象做修改是保护数据的第一道闸门!pipes collect(all, PIPE, IN, !!world) do !pipe - !pipes if !pipe.bore 4 then !pipe.bore 6 message 升级完成 !pipe.name endif loop这段代码只处理公称直径为4寸的管道把口径升到6寸。如果只想处理某个区域把世界起点换成指定区域的句柄即可。实战里还会叠加更多条件比如按管道等级、保温状态、所在位置筛选。PML的if支持多个条件用and/or连接。条件越严格误操作概率越低批量跑之前多花一分钟加条件比出错后花一天还原数据划算得多。3.4 创建与复制new和copy怎么用除了改属性二次开发还经常要创建和复制元素。创建一个分支的常见写法!newBranch new branch with !pipenew返回新对象句柄branch是要创建的元素类型with !pipe指定父对象也就是新元素挂到哪根管道下面。创建以后这个对象还是一个空壳需要继续设置尺寸、等级等属性才能被设计系统识别不能只建壳不填内容。复制操作适合拿已有元素做模板!copyObj copy !templateBranchcopy返回模板的一个副本新对象独立存在改它的属性不会影响模板。需要说明的是new和copy只是最常见做法中的两个入口实际项目中还要注意新对象的命名规范和父对象归属这些参数在不同设计院有自己的约定脚本里最好做成可配置项而不是写死在代码深处。3.5 属性赋值不生效有些属性是算出来的如果赋值以后属性值没变先怀疑这个属性是不是派生属性。例如口径、等级这类属性往往和管道等级库里的规则绑定直接给一个不存在的值会被系统忽略。遇到这种情况第一步去软件自带的属性帮助里查属性定义第二步看这个属性有没有专门的设置函数第三步检查赋值类型比如字符串和数字写错。!grade !pipe.grade message !grade读取属性总是安全的先读后写。我看到很多新手一上来就写批量赋值跑完发现部分对象没变却说不清为什么。养成“先读一个样本、再批量写”的习惯能过滤掉大部分类型和命名问题。4. 用PMLNet和C#做重量级功能什么时候该换技术栈4.1 先分清哪些事不适合用纯PML硬扛PML能处理很多任务但它有一个明显短板性能。解释执行加上对象模型访问的开销遍历几千个对象就已经有明显延迟如果还要做复杂字符串处理、正则匹配、对接外部数据库PML会写得非常痛苦。这时候就该考虑PMLNet和C#。判断标准有三条。第一循环量级单个操作超过几千次就值得换。第二界面复杂度需要表单、列表、多标签页这种交互界面时PML的弹出窗口完全不够用。第三外部数据要读写Excel、SQL Server或调用Web接口直接上C#比用PML去调用原生接口省事得多。这三条不满足继续用PML没必要上重武器。4.2 PMLNet最小调用从脚本里创建一个.NET对象PMLNet是PML和.NET之间的桥它让PML脚本有能力创建.NET对象并调用其方法。这段脚本演示了最简调用过程!net object pmlnet(PMLNet) !array !net.CreateObject(System.Collections.ArrayList) !array.Add(abc) !array.Add(def) message !array.Count第一行object pmlnet(PMLNet)把桥接类实例化第二行CreateObject传入.NET类型的全名PMLNet返回一个代表该.NET对象的变量之后!array.Add就是调用ArrayList的方法!array.Count是读取属性。执行后弹窗显示2说明两个元素已经成功添加。这里容易绕晕的点CreateObject返回的不是原生PML对象而是一个包装后的引用。调用它上面的方法时方法名、参数都要遵循.NET的规则Add括号里的参数是Object类型传入字符串没有问题。如果调用带重载的方法注意参数个数和类型要匹配报错信息常常不直观。4.3 用C#写插件一个不依赖PDMS API的最小实例PMLNet的另一个重要用途是加载你自己编译的.NET程序集。先写一个最普通的C#类namespace PdmsTools { public class PipeHelper { public static string Hello(string name) { return hello, name; } } }这个类不需要引用任何PDMS程序集它只是验证链路。编译时选择.NET Framework版本平台选择x86输出生成DLL。PML侧加载这个DLL并调用!net object pmlnet(PMLNet) !asm !net.LoadAssembly(C:\\Tools\\PdmsTools.dll) !helper !net.CreateObject(PdmsTools.PipeHelper) message !helper.Hello(piping)LoadAssembly接受DLL的绝对路径CreateObject按命名空间加类名创建对象最后Hello方法返回字符串并弹窗。跑通这一条说明PMLNet加载自定义程序集的链路没有问题。实际拿C#做PDMS二次开发时还需要在C#工程里引用PDMS提供的.NET程序集通过它的对象模型访问数据库。这一步依赖具体的PDMS安装路径不同机器可能不同建议把引用路径写入配置文件不要写死在代码里。项目里常见的做法是先做一个不依赖API的Hello工具验证环境再逐步引入PDMS程序集这样出问题能快速定位是环境问题还是业务代码问题。4.4 从PML传值到C#类型转换的三个注意点PML侧给C#方法传参表面上直接写变量就行实际上类型匹配容易出问题。整数在PML里按实数处理往C#的int参数传值时可能触发舍入字符串要注意空值null传入C#后处理不当会抛异常。下面是C#侧的一个常见写法public static int CountByName(string name) { if (string.IsNullOrEmpty(name)) { return 0; } return name.Length; }PML侧调用时先判断变量是否为空!name PIPE-001 if !name ! then message !helper.CountByName(!name) endif这段演示了常见防线C#侧判空PML侧也判空两道检查避免空引用。类型问题大多在集成阶段才暴露提前在两边做好防御能少一轮环境联调。5. PDMS二次开发避坑指南版本、引用与调试的典型问题5.1 collect返回空集合脚本不报错但啥也没改现象脚本执行完日志里一条消息都没有模型毫无变化。原因collect的起点对象类型不对或者起点指向的区域里根本没有对应类型的元素。有些版本对collect参数比较宽容识别不了就返回空集不报错。解决把collect结果的数量先输出出来验证范围。!pipes collect(all, PIPE, IN, !!world) message !pipes.Count如果Count为0先检查起点是不是世界对象再检查工程里确实有PIPE类型。把范围临时缩小到某个已知有管道的区域如果缩小区就有数据、扩大就没有说明起点对象的句柄拿错了。5.2 属性名写错不报错也不生效现象批量改完以后打开报表数据一点没变。原因PML把不存在的属性名当成新属性直接赋值了这个行为在不同版本里表现还不一样有的赋值失败有的悄悄忽略。这个问题特别隐蔽因为脚本本身不报错。解决写批量脚本前先单独输出目标属性当前值。message !pipe.bore看到值再动手。批量脚本里在赋值前后各加一段日志把对象名和修改前后的属性值都记录下来这样即使属性名有问题日志也能直接看出来。靠肉眼盯着屏幕确认翻车概率太高。5.3 版本升级后老脚本大面积报错现象换了一个大版本以前跑得好好的脚本开始报错错误提示指向同一类函数调用。原因PDMS不同版本对PML语法和部分属性接口做了调整老写法在新环境里不兼容尤其是界面相关的函数版本更换后改动往往较大。解决迁移前先建一个最小验证脚本覆盖属性读写、对象遍历、DLL加载这三类功能。在新版本里把最小脚本跑通再往里面填业务代码。这样版本兼容问题在第一天就暴露而不是藏在几百行业务代码后面等到最后才蹦出来。5.4 PMLNet加载DLL失败提示找不到程序集现象LoadAssembly执行时弹出错误提示找不到文件或程序集名称不匹配。原因一是DLL路径不对二是PDMS进程版本和DLL编译目标不匹配比如PDMS主程序通常跑在32位进程里你的C#工程编译成64位就会加载失败三是DLL依赖了本机没有的其他库。解决先确认LoadAssembly的路径是绝对路径再把编译平台改成x86最后检查DLL的依赖项。把DLL放到PDMS安装目录的脚本目录下加载可以减少路径权限问题。排查这类问题有一个笨办法写一个不引用任何PDMS API的独立DLL先加载它加载成功说明链路正常加载失败说明问题在环境本身。5.5 跑批操作没有先备份数据回不去现象脚本跑完发现改错了想还原数据结果找不到之前的版本。原因PDMS的数据保存在工程数据库里修改类脚本不会自动生成备份。一旦逻辑有漏洞批量改动的数据很难通过界面手动还原。解决跑批前备份MODELS目录脚本里增加计数器每处理一个对象就打印一次进度。重要修改尽量分两批跑第一批先跑小范围确认结果正确以后第二批再放开全库。这个习惯是我吃过亏以后养成的回头再看能救命的两步就是备份和分批。6. 调试效率与验证一个能省半天时间的自检习惯6.1 用日志文件代替弹出的消息框调试PML时最烦的是message弹窗循环一百次就弹一百次。改用文件输出脚本跑完直接看日志!log open(C:\\tmp\\pml_run.log, W) write !log batch started, pipe count !pipes.Count close !log变量记录、中间结果、错误信息都写在日志里不影响脚本运行也方便对比前后结果。6.2 把全库遍历改成小范围遍历把范围从!!world换成!!zone先选中一个已知有数据的区域再跑跑通后再改成全库。这一步几乎不花时间但能拦截掉一半以上的低级错误。6.3 留一个可重复执行的回归脚本回归脚本只做三件事选取一个已知对象、修改一个已知属性、把结果写入日志。每次改动功能后先跑它环境状态好不好一目了然。最后说一个教训。某次我在模拟项目X里写了一个批量改spool编号的脚本觉得逻辑简单没走小范围验证直接拿全库试。脚本没报错结果几十根管道的逻辑关系全乱了。查日志才发现条件判断里把两个字段的优先级写反了。从那以后任何脚本都要先小范围验证、留日志、备份数据。这个习惯的价值不是让你少写代码而是让你在写错的时候能用半小时而不是三天把问题找回来。希望帮到你。本文还有配套的精品资源点击获取