☰
PDMS二次开发教程:从PML脚本到.NET管道工具实战
2026/10/6 19:09:22 网站建设 项目流程

简介:面向工厂设计工程师与开发者的PDMS二次开发教程,系统讲解如何用PML扩展PDMS的设计、报告、界面及流程自动化能力,适合已掌握基础建模、希望提升定制化效率的初中级用户。压缩包共59个文件、237KB,以doc教程、cpp/cs源码、h头文件、pmlfrm窗体、pmlfnc函数及exe演示程序为主,覆盖PML语法、PDMS12 dotNet接口、dars_MFC窗口、CsvFixer等示例,并附UE语法高亮配置与培训PPT,便于对照学习。已有2739人浏览学习。通过源码与文档,可掌握变量、控制结构、对象属性访问、事件驱动等PML核心语法,结合自动化布管、定制报表、扩展菜单、数据校验等实例,快速建立PDMS二次开发的项目级落地思路。

1. pdms二次开发教程:从这条管道曲线说起

拿到一个 pdms 二次开发教程,先要搞清楚它到底能解决什么。你在 PDMS 里花三天建完的管道模型,业主突然要一个带保温厚度的材料表;或者设计院要求所有 2 寸以下管线必须用承插焊连接,你不想一条条去点属性框核对。这些事靠鼠标点击做不完,靠 PDMS 自带命令也做不干净,唯一的出路就是把 PDMS 里面的数据「接」出来,按你的规则跑一遍,再写回去。这就是 pdms 二次开发的正事:不是改软件界面,而是让 PDMS 替我干活——批量改属性、批量出图、批量校验模型。

这个方向适合三类人:设计工程师想省掉重复劳动;IT 人员要在 PDMS 和 ERP、材料系统之间打通数据;还有做模板工具的人,想把团队里的建模规范做成一键成品。本教程用一套完整链路来讲——先跑通最小的 PML 脚本,再上 .NET 做独立工具,最后给一套数据提取和管道工具的落地方案,以及我用这几年踩出来的几十个坑。看完你就知道,PDMS 二次开发没有你想的那么神秘,但也不是网上那种「复制粘贴就跑」的段子。

2. PML 脚本是 PDMS 二次开发的地基:先跑通第一个工具

2.1 为什么选 PML 而不是一上来就碰 .NET

PDMS 二次开发主要有两条路:PML(Programmable Macro Language)和 .NET API。你打开 PDMS 的命令行窗口,输入show !!pml能直接看到语法对象树,这套内嵌语言就是 PML。它和 PDMS 是同一个进程,能直接操作当前打开的模型数据库,适合写「寄生在 PDMS 里」的小工具。而 .NET API 是 PDMS 12.0 之后主推的接口,需要编译成 dll 再注册调用,适合写独立运行的批处理工具。

我的经验是:凡是设计人员自己用、需要在模型界面里交互的工具,用 PML;凡是数据量大、要定时跑、要对接外部系统的,用 .NET。新手尤其不要跳过 PML 直接啃 .NET——PDMS 二次开发的核心是理解它的数据库对象模型(Design 层、Element 层、属性 DB 的分布),PML 让你能在界面上肉眼验证每一步操作的后果,这个反馈回路比什么都重要。

2.2 最小可运行脚本:读出一个管道的等级和直径

先写一个能当场看到效果的 PML 脚本。新建一个文本文件,后缀改成.pml,放进任意目录,在 PDMS 命令窗口输入:

-- getpipeinfo.pml !pipe = $!PIPEQSE !name = !pipe.Name !spec = !pipe.Spec !dia = !pipe.Diameter !!pml = $? !name + ' | ' + !spec + ' | ' + tostring(!dia)

这段代码先说逻辑:$!PIPEQSE是 PDMS 内置的查询命令,运行后光标变成拾取状态,你在模型里点一条管道,!pipe就拿到了这条管道的对象引用。.Name取管道名,.Spec取等级,.Diameter取公称直径。最后一行!!pml = $?是把结果写到 PDMS 命令行,加tostring()是因为直径在 PML 里是实数型,直接拼字符串会报类型不匹配。

参数说明里有个关键点:.Diameter返回的是模型的物理内径,不是公称直径。如果设计用的是 2 寸 SCH40 管线,模型里存的可能是 49.25mm 这种实际值,你要显示 DN50 就得查等级库的 SKEY 再映射。很多新手第一次看到输出数字和自己预期不符,直接在微信群里问为什么数据错了,其实只是没搞清楚这个对象属性是物理值还是工程值。判断的方法很简单:在 PDMS 里点开管道属性,看「General」页显示的数值单位是 mm 还是英寸,如果带单位,说明底层是物理存储,输出前要做单位转换。

2.3 把脚本接上命令栏和自定义菜单

脚本写好了不能每次都在命令窗口敲路径。PDMS 二次开发的标准做法是把工具挂到菜单。在 PDMS 安装目录的App文件夹下找pml子目录(不同版本路径略有差异,比如C:\AVEVA\PDMS\12.0\App\pml),里面有一个pml.sys初始化文件,追加一行:

do file '!mypml/getpipeinfo.pml'

注意这里!mypml是 PML 的环境变量路径,不一定指向你的脚本目录。我的做法是先用!!sys查看当前环境变量,再把自己的脚本目录加进去:

!!mydir = 'C:\pdms_dev\scripts' $? newlayer !mydir do file '!mydir/getpipeinfo.pml'

这段代码的逻辑是:newlayer命令把!mydir登记到 PDMS 的文件搜索路径,之后do file就能用环境变量名引用脚本。如果用绝对路径也行,但项目之间迁移容易断路径,用环境变量管理是血泪经验换来的规范。

菜单挂载选择在pml.sys里、还要写menus子文件,不同版本的挂法不太一样。PDMS 12.0 常见的是在pml.sys里加入自定义菜单初始化,然后重开模块。如果你用的是较老的 11.6 或较新的 E3D,初始化文件位置和菜单注册方式会有差别,但do file和newlayer这两条命令逻辑是通用的。跑完脚本后,去命令窗口敲getpipeinfo,看能不能直接弹出提示。

2.4 遍历模型:对当前区域的所有管道统一改名

每次点选一条管道效率太低,真正干活的时候你得遍历。下面这段脚本展示了 PDMS 二次开发里最常用的模型遍历方法——从当前位置往下找所有 Pipe 元素,逐个改名:

-- renamepipes.pml !sels = $1 !ele = !!new from !sels !pipe = !ele.First( 'Pipe' ) while !pipe <> !null do !oldname = !pipe.Name !newname = 'NEW_' + !oldname !pipe.Name = !newname !pipe = !pipe.Next( 'Pipe' ) endwhile

这里$1是当前选中元素集合,!!new from创建一个对象指针,.First('Pipe')找第一个 Pipe 子元素,.Next('Pipe')继续找下一个。循环里直接给.Name赋值,就是最简单的批量改属性。注意这个循环只遍历指定类型,不会碰下面的设备、支架分支。写法上 PML 的集合遍历不能像 C# 那样安全地边改边删,所以要在循环里避开删除操作,删元素必须另开一轮——这是 PML 对象模型的边界。

3. .NET 接口开发:把 PDMS 工具变成独立程序

3.1 PML 的尽头是 .NET:什么时候必须迁移

PML 再强大也有绕不过去的墙:处理 10 万级元素时脚本慢得让人想砸电脑;没有正常的异常处理机制,一个报错弹窗出来你都没法定位是哪一行;想调用 Windows 的 WebService、读数据库、生成 Excel 报表,PML 写起来像在泥地里骑自行车。这些场景就是 PDMS 二次开发里 .NET 接口的舞台。PDMS 从 12.0 开始提供了完整的 .NET API 程序集,你可以在 Visual Studio 里引用来访问模型数据。我自己判断迁移的时机就三条:数据量超过几万条、要做后台批处理、要和外部系统交换数据,满足任何一条就果断转 .NET。

另外 .NET 接口通过 COM 互操作访问 PDMS 的对象模型,所以核心概念和 PML 一致——都是 Element、DB、Attribute 这套体系。学会了 PML 的遍历逻辑,转到 .NET 只是换一层语法壳。

3.2 用 C# 读取 Design 元素列表的完整示例

新建一个 C# 控制台项目,添加Aveva.Pdms.Database、Aveva.Pdms.Utilities、Aveva.ApplicationFramework等引用(版本不同命名空间略有区别,从 12.0 到 E3D 都有)。下面是从当前 Design 模块的指定位置读取所有 Pipe 名称和规格的代码:

using System; using System.Collections.Generic; using Aveva.Pdms.Database; using Aveva.Pdms.Utilities; namespace PdmsDev { class Program { static void Main(string[] args) { DbElement current = DbElement.CurrentElement; DbElement[] pipes = current.GetElements(new DbType[] { DbType.Pipe }); List<string> output = new List<string>(); foreach (DbElement pipe in pipes) { string name = pipe.GetString(DbAttributeInstance.NAME); string spec = pipe.GetString(DbAttributeInstance.ASPEC); output.Add($"{name}|{spec}"); } System.IO.File.WriteAllLines(@"C:\pdms_dev\output.txt", output); Console.WriteLine("Done: " + output.Count); } } }

代码逻辑说清楚:DbElement.CurrentElement拿当前模块的位置对象,GetElements(DbType.Pipe)一次性取到该位置下属性的所有 Pipe 元素,注意GetElements 是深层遍历,会把子级所有层级的管道都带上来。GetString(DbAttributeInstance.NAME)按属性名取字符串值。管道等级属性在 PDMS 内部叫ASPEC,不是SPEC——这是坑,后面避坑章节还会专门讲。

参数说明:括号里的new DbType\[\]是类型过滤器,只返回 Pipe;如果不加,返回的是全部子元素,数据量大时内存吃紧。这个程序不依赖 PDMS 界面,在命令行跑就行,但要先启动 PDMS 并打开一个 Design 模块,因为 .NET API 要连上数据库进程。

3.3 搭建一个不依赖用户交互的批处理框架

如果要写一个每周五晚上自动跑的材料统计工具,.NET 最实用的架构是:命令行入参 + 配置文件 + 日志输出。我不写入参而是推荐配置文件,因为设计人员不熟悉命令行。下面是一个最小框架的示意:

// Program.cs 入口逻辑 static void Main(string[] args) { string configPath = args.Length > 0 ? args[0] : @"C:\pdms_dev\config.ini"; var cfg = LoadConfig(configPath); string modelPath = cfg["ModelPath"]; string outputFile = cfg["OutputFile"]; string[] pipeTypes = cfg["PipeTypes"].Split('|'); try { var session = PdmsSession.Connect(modelPath); var report = GenerateReport(session, pipeTypes); System.IO.File.WriteAllText(outputFile, report); WriteLog($"OK|{DateTime.Now}|{outputFile}"); } catch (Exception ex) { WriteLog($"FAIL|{DateTime.Now}|{ex.Message}"); throw; } }

这个框架的价值在于:PDMS 二次开发里最容易翻车的就是「今天能用、明天不能跑」,因为模块的打开状态、用户的登录权限、数据库连接对象没释放都可能导致随机失败。配置文件把连接路径、类型列表、输出位置和日志等级全抽出来,排错的时候看一眼日志就知道卡在哪一步。异常处理里WriteLog写的是结构化文本——时间戳、状态、位置三字段,这是我自己常用的平替日志方案,不用额外引 NLog 之类的库。

PdmsSession.Connect之类的方法名是示意性的,不同版本 API 的启动方法不一样,PDMS 12.0 通常要引Aveva.ApplicationFramework,先启动框架服务再连数据库。不要在程序里直接 new 一个Database,要通过框架的IDatabase服务来连——这是 .NET 接口稳定性的关键。

3.4 对外提供的接口选择:COM 与 .NET API 的取舍

还有一个经常被问到的点:「我家 PDMS 是 11.6 的老系统,可以用 .NET 吗?」答案是:11.6 没有完整的 .NET API,只能走 COM 接口,把 PDMS 当 COM 服务器用,C# 里通过Marshal.GetActiveObject("PDMS.Application")拿到进程对象再反射调用方法。这种写法功能上能做,但速度慢、不稳定,而且微软已经逐步收紧GetActiveObject的权限。我的建议很直接:老系统优先用 PML 做界面内的工具,真要做外部程序,花点钱升级到 12.0 SP6 以上或者直接用 E3D,省下后面排错的成本比什么都值。

4. PDMS 二次开发避坑指南:九个血泪经验

4.1 属性名弄错:SPEC 还是 ASPEC

现象:用 C# 的pipe.GetString(DbAttributeInstance.SPEC)去取等级,运行不报错,但返回的值一直是空字符串。

原因:PDMS 的数据库里,管道等级有两个相关属性。SPEC是逻辑名,不是所有元素都有,很多时候你需要的实际存储属性叫ASPEC(A 开头是 Attribute 的缩写),两者的取值逻辑不完全一样。用错属性名是 PDMS 二次开发里最低级也最浪费时间的错。

解决:调出 PDMS 的 Command Line 工具,选中元素,用Q ASPEC和Q SPEC各查一次,看哪个返回了你想要的值,再去代码里改属性名。任何时候不确定属性,先用这个命令验证,不要对着文档猜。

4.2 遍历里的类型判断:为什么把设备也算进去了

现象:用!ele.First('Pipe')遍历,发现有些设备箱体也被改名了,逻辑明明只处理了 Pipe 类型。

原因:PDMS 的Pipe类型是Equipment、Structure之外的管路元素,但分支箱体在数据库中可能是PipeBranch,而PipeBranch继承自Pipe的部分语义,导致部分类型判断为真。PML 脚本里的First('Pipe')不会精确匹配,它对类型是「兼容即算」的。

解决:遍历时加双重判断。!ele.TypeName == 'PIPE'这种用DbAttributeInstance.TYPE或者.TypeName比较,不要只依赖First('Pipe')。在 .NET 里就直接用DbType.Pipe做显式过滤,不写模糊匹配。

4.3 属性改完不生效:别忘记$P刷新

现象:脚本循环跑完,屏幕上的模型属性没有任何变化,但脚本再跑一遍,读到的值又是新值。

原因:PDMS 的图形界面和数据库之间有缓存,脚本直接改属性不一定立刻触发图形刷新,尤其当改动涉及管道路径、等级这类参与计算的性质时,刷新滞后是很隐蔽的。

解决:在批量操作结束后,调用一次$P或者!!pml = $P强制刷新当前视窗。要注意的是$P刷的是整个模块的视图,数据量大的时候会很慢,所以不要每个循环都调,放在循环结束之后调一次。这个「改完不刷新」的坑在材料统计工具里最常见,因为你看不到模型变化,就以为代码写错了,其实只是没触发刷新。

4.4 脚本运行到一半弹窗卡死:消息框阻塞

现象:脚本在循环里调用了一个弹窗函数,然后 PDMS 界面失去响应,连取消按钮都点不动。

原因:PML 的!!弹窗或者 .NET 里的MessageBox.Show在 PDMS 主线程上运行,而循环还在执行,界面消息泵被阻塞。PDMS 二次开发里有个约定——不要在遍历模型时弹窗。这是新手最容易犯的错,因为调试的时候你想看每一步的值,就把弹窗塞进了循环。

解决:调试时把输出写到文件而不是弹窗。在 .NET 里写一个DebugLog方法往文本追加,在 PML 里用write命令写日志文件。等确认逻辑正确再删除日志,不影响性能。如果一定要手动交互,把信息收集到列表里,循环结束后最后统一弹窗。

4.5 开发环境中文路径的玄学问题

现象:代码放到D:\管道工具\材料统计\目录下,一运行到读取模型文件就报文件不存在。

原因:PDMS 底层很多组件用的是旧编码体系,对中文路径的兼容性就那样,报错时可能不会明确说路径问题,而是说数据库连接失败之类。很多翻车都是从这个细节开始的。

解决:整个开发工作根目录全部用英文,不要有空格和中文。模型文件名如果是设计院发来的中文名,先复制一份出来改名再操作。这个话题我不想多展开,但这条我写进了团队规范,救了很多次场。

4.6 模型大、遍历慢:要区分深层和浅层

现象:一个只有 50 根管道的区域,遍历竟然跑了十几秒。

原因:GetElements没有加深度限制,当当前元素是 Zone 时,深层遍历会把它下面的分支、管嘴、支架、附件全部提出来,体量瞬间膨胀。PDMS 的层级结构很深,Zone → Pipe → Branch → Hanger 一层套一层,浅层遍历能解决问题就别深层拉。

解决:.GetElements(DbType.Pipe)已经带了类型过滤,但如果还慢,可以先拿到子元素列表,用.Subtype(DbType.Pipe)浅层拿一次,再逐层往下。判断数据量级的方法很简单:先.Count看看有多少条,数量在一万以内直接拉,超过一万就要按层拆。

5. 数据提取与管道工具实战:从模型里抓出你要的每一根管线

5.1 核心建模理念:PDMS 二次开发的数据本质是 DB

做 PDMS 二次开发最容易被忽略的认知是:PDMS 不是三维软件,是一个带三维界面的数据库。你在屏幕上看到的每一条管道,在数据库里是一个 Element 记录;它的等级、保温厚度、连接方式,全部是记录上的属性字段。所以一切工具的底层都是——定位元素、读属性、写属性这三个动作。

PDMS 的元素定位方式有两种:一种从界面选择进去拿对象(如前面写的$!PIPEQSE),另一种用 DBKEY 直接定位元素——cvxrootid(rootname, &idroot, &type)就是. NET 里从根 ID 拿元素的方式,idroot是根节点标识,type用来判断这是什么类型的对象。这个函数在材料统计、跨模块查找时非常有价值,因为 DBKEY 是元素最稳定的身份证,名字可以改,DBKEY 不会变。

5.2 最小可用的数据导出脚本:把管道清单写成 CSV

下面这段 PML 脚本解决的问题很实际:把当前区域所有管道的名称、等级、公称直径、保温厚度导出一个 CSV,给材料工程师用。

-- export_pipe_list.pml !sels = $1 !ele = !!new from !sels !pipe = !ele.First( 'Pipe' ) !!file = 'C:\pdms_dev\pipelist.csv' !!writefile = newfile !!file !!writefile.WriteLine( 'Name,Spec,DN,Insulation' ) while !pipe <> !null do !spec = !pipe.Spec !dia = !pipe.Diameter !insu = !pipe.GetProp( 'INSULATION' ) !!writefile.WriteLine( !pipe.Name + ',' + !spec + ',' + tostring(!dia) + ',' + tostring(!insu) ) !pipe = !pipe.Next( 'Pipe' ) endwhile !!writefile.Close()

逻辑说明:newfile是 PML 里创建文件对象的命令,WriteLine逐行写入。管道等级从.Spec取,直径需要转成字符串,保温厚度用GetProp('INSULATION')而不是.Insulation,原因是 PML 没有预定义的 Insulation 属性对象,必须用属性名去查,这点和 .NET 的GetString是同一个套路。循环结束后务必Close()释放文件句柄。

参数说明:这个脚本里我故意没做单位转换。输出的直径是模型内径毫米值,材料工程师拿这个数据做表格时会再处理。如果你想直接输出英制或公称直径,就得自己写单位换算函数,PDMS 的Convert命令可以做单位转换,但要注意Convert会改变当前环境设置,用完要恢复,不然影响后续会话。

5.3 管道工具的应用案例:按连接方式批量修改支管等级

网上热词里经常看到「pdms二次开发应用案例 pipelinetool」,这类工具做的核心功能我拆开看,就是「按规则批量改模型」。举一个最常见的:设计规范要求所有承插焊连接的支管,等级必须从 SCH40 降到 SCH10S。手工改几百个分支头会改到怀疑人生,PML 脚本用管道对象模型能直接解决:

-- change_branch_spec.pml !branch = !pipe.GetChildren( 'PipeBranch' ) while !branch <> !null do !joint = !branch.GetProp( 'JOINT' ) if !joint == 'SW' then !branch.Spec = 'SCH10S' endif !branch = !branch.Next( 'PipeBranch' ) endwhile

这段代码的关键是JOINT属性存的是连接方式编码——SW代表 Socket Weld(承插焊),不同项目设置的编码可能不同,需要先查项目标准。改属性后同样记得$P刷新,前端同事常吐槽后端把模型改了但界面没变化,就是这个原因。

这个工具的价值不在于代码长短,而在于它展示了 PDMS 二次开发里最常见的一类模式——不是从零建模,而是对既有模型做规则化修补。设计规范永远在变,每次变更都靠人肉去点属性框,不仅慢而且漏。写成脚本后,一个「规范变更」从一周变成半小时,这就是为什么设计院愿意投入二次开发的根本原因。

6. 验证开发成果与进阶方向:怎样确认你的工具是可靠的

工具写完之后要经过一套验证流程才能交付。我自己的做法是三步:第一步用已知模型做基准对比——打开一个已经统计过的区域,跑你的脚本,把输出和之前人工做的表格逐行比对,完全一致才算过第一关;第二步是边界测试——故意创建一个只有一根管道、空模型、或者包含非法等级数据的模型,看程序会不会崩溃、会不会输出脏数据;第三步是把工具放到真实项目里,和一个资深的管道工程师一起「背靠背」(他手工统计,你机器跑批),对比两边的差异。

敏感场景里最常用的验证方法是「故意造一个坏数据」。PDMS 模型里经常有建立了一半的管道,等级字段是空的,直径是零,跑循环的时候不会报错但结果很差。我一般在提取数据前先加一层合法性判断:

-- 过滤无效管道 if !pipe.IsValid() and !pipe.Diameter > 0 then -- 进入正式处理流程 endif

IsValid()检查元素对象是否还有效——PDMS 里删除的元素在后来的遍历里可能还会被引用,这是 .NET 接口和 PML 都有的一个坑,不加判断就会出现NullReferenceException的玄学崩溃。加了这层守卫之后,工具交付后问题少了八成。

进阶方向上,我建议你从三个点继续深挖:一是把数据提取做成定时任务,对接材料管理系统,让 PDMS 模型自动生成采购清单;二是做模型规则校验,在建模阶段实时拦截错误(比如支管焊了不该焊的等级);三是做 PML 和 .NET 的混合架构——界面交互用 PML,重型计算走 .NET,兼顾开发效率和运行速度。这些方向都有现成的第三方库可以借鉴,但 PDMS 的版本差异太大,没有一套代码通吃所有版本,升级前一定要先跑回归测试。

最后说一个我自己的习惯:每次开发完一个工具,我会把「它解决了什么问题、坑在哪里、给后来者的备注」写成一页纸的说明,放在脚本同一目录下。这个习惯让我在半年后接到维护需求时不用重新考古自己的代码。PDMS 二次开发的技术边界其实很清晰,但项目里的坑只有自己踩过才记得牢——希望我的这些经验能帮你在做管道工具和材料统计的这条路上少走一段弯路。

本文还有配套的精品资源,点击获取

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

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

立即咨询