ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

PDMS二次开发实战:PML脚本编写与批量导出管道清单

PDMS二次开发实战:PML脚本编写与批量导出管道清单

简介:PDMS二次开发教程是一份面向PDMS软件使用者、设计管理人员及二次开发初学者的学习资料,着重介绍如何用PML脚本语言扩展三维工厂设计软件功能,涵盖自动布置管道阀门、定制物料统计报告、用户界面扩展、设计合规校验和工作流自动化等典型业务场景。资源压缩包共59个文件,大小仅237KB,主体包括PML语法讲解文档、pmlfrm/pmlfnc等PML窗体与函数源文件、C/C++及C#工程源码、Visual Studio解决方案和MFC窗口示例,并附带dll、exe演示程序、语法高亮配置及相关文档。已有2755人学习。通过这套教程可系统掌握PML变量、控制结构、函数及对象属性访问等语法要点,同时参考dotNet接口开发、Dars二次开发和UE语法高亮配置等实例,辅助快速搭建开发环境、理解PDMS对象模型与API调用方式,为实际工程定制高效工具提供具体范本。

1. 这不是黑匣子:一个脚本把两周的工作压缩到十分钟

某设计院交付前一周,3000多根管道的保温等级属性全是空的。手动在PDMS里一根根查、一根根改,保守估计要两周,而有人用PML写了一个遍历脚本,十分钟跑完。PDMS二次开发的本质不复杂:用PML语言去操作它的对象模型,把重复点击变成批量逻辑。难的是最开始没人告诉你对象树怎么爬、属性怎么读写、脚本怎么挂到菜单。这篇教程不绕弯子,直接拆解PML语法、对象层级、属性读写,再给一个完整可跑的批量导出案例,外加五条高频踩坑记录。适合拿到底层开发环境还找不到下手点的初学者,也适合想把自己的脚本整理成可维护工具库的工程师。

2. 核心模块拆解:PML语法与PDMS对象模型

PML一共经历过两个阶段:延续命令行风格的PML1,以及接近结构化语言的PML2。现在做二次开发基本都围绕PML2展开,语法类似BASIC,变量名统一带$!前缀,函数用FUNCTION定义。不用把全部命令背下来,先把对象模型、变量、流程控制和属性读写四块骨架搭出来,大部分功能都能拼得出来。

2.1 对象模型:从World到Element的树状结构

PDMS的工程数据库不是一张张表,而是一棵巨大的对象树。顶部是World,代表一个独立工程库;World下按Site划分场地,Site下继续分Zone区域,Zone下面才是Equipment、Pipe等具体对象。PML脚本的一切访问都是从这棵树上取节点。

读取当前光标所在的元素,是每个脚本最常用的起点。在图形界面点中一个设备后,当前元素就是它:

$!db = DB() $!cur = $!db.CurrentElement() $!name = $!cur.Name() $!path = $!cur.Path() MESSAGE $!path & " / " & $!name

逻辑说明:第一行拿到数据库句柄,第二行拿到当前元素,第三、四行分别读取短名称和完整路径。MESSAGE是PML2的弹窗输出,执行后会弹出消息框。注意字符串拼接用的是&,不是其它脚本语言里的+,数值拼接时会自动转成字符串。

如果你处在一个多数据库并发环境,DB()默认返回第一个被激活的库,脚本里最好显式指定库名:

$!db = DB("PROJECT_STEEL")

参数说明:DB后面跟的字符串是PDMS数据库的Name,实际值要看工程配置。命令行里不带参数执行DB()返回的是当前激活库,脚本里写死库名时必须保证它真实存在,否则后续操作全部失效。

树上的元素用Element对象表示。这个对象比较特殊,它不是一份静态数据副本,而是指向树中某个位置的引用。同一个变量在不同上下文里可能指向不同内容,尤其是切换数据库之后,句柄会失效。这一点后面避坑章会重点展开。

2.2 语法骨架:变量、函数、流程控制

PML2的变量不需要声明类型,直接赋值即可。字符串和数字都能放进同一个变量,但类型区别仍然存在,字符串不能参与算术运算。

$!pipeName = "P-001" $!diameter = 150 $!desc = $!pipeName & " 管径为 " & $!diameter & "mm" MESSAGE $!desc

逻辑说明:第一行是字符串变量,第二行是数字变量,第三行用&拼接成一句话。如果字符串里已有空格就不需要额外处理,直接写在引号内。

函数定义从FUNCTION开始,ENDFUNCTION结束,用户自定义函数名通常以@开头。调用时通过@函数名(参数)完成:

FUNCTION @CalcArea($!diameter) $!radius = $!diameter / 2 $!area = 3.14159 * $!radius * $!radius EXIT FUNCTION $!area ENDFUNCTION $!pipeArea = @CalcArea(200) MESSAGE $!pipeArea

逻辑说明:定义了一个计算圆面积的函数@CalcArea,入参是直径$!diameter,函数体内部先算半径再算面积,用EXIT FUNCTION返回结果。调用时传入200,输出直径200mm对应的截面积。除运算用/,乘法用*,跟多数脚本语言一致。

流程控制分IF和循环两种。IF用于条件分流,循环最常用的是DO...LOOP:

$!count = 0 DO $!i = 1 TO 10 $!count = $!count + $!i LOOP MESSAGE $!count

逻辑说明:DO从1循环到10,每次把$!i累加到$!count,最终输出55。循环变量$!i不需要额外声明,每次迭代自动更新,循环体内可以随时读取。

2.3 属性读写:BORE、RATING、THK都没有神秘感

PML里最核心的操作是读写Element的属性。属性名在不同工程规范里可能不同,但读写套路是统一的:读取用元素->属性名,写入用元素->属性名 = 值。

$!pipe = DB().CurrentElement() $!bore = $!pipe->BORE $!pipe->BORE = 150 $!pipe.Update()

逻辑说明:第二行把BORE属性(管道孔径)读出来,第三行把BORE改成150,第四行调用Update提交修改。只赋值不Update,等于白改,重启后数据会回滚,这是新手最常见的翻车点。

->箭头左边是Element对象,右边是属性名。PML本身大小写不敏感,但属性名在数据字典里是唯一标识,建议统一按数据字典原始定义写大写。

如果不确定目标元素有哪些可用属性,可以用$$Attribute.ListAttributes($!pipe)列出全部属性和当前值,再从中挑需要的。这是排查“属性名写错”最直接的一条命令。

2.4 脚本文件的组织方式

PML支持交互式和文件式两种执行方式。交互式是在Command Window里一行行输入,适合验证单条语法;文件式是把代码存成.pml文件,用$M命令加载:

$M export_pipes.pml

逻辑说明:$M是执行PML宏的命令,默认搜索PML根目录。脚本必须放在该目录下,或者用子目录相对路径,比如$M tool/export_pipes.pml。执行时脚本不会自动打印中间结果,需要在代码里用MESSAGE弹窗或写文件输出。

团队里流传的PML脚本质量参差,常见问题包括没有异常处理、变量名随意、不写注释。作为要给别人用的脚本,至少要保证一个月后再打开还能看懂。我一般会在头部写用途和输入条件,函数上方标注参数含义,难懂的逻辑加一行注释。

3. 跑通一个脚本:从命令行到自定义菜单

PDMS二次开发的第一步不是直接堆复杂功能,而是先能跑通一个最小的PML脚本。用最小代价验证目录结构、加载规则和报错反馈,后续加功能才有底。

3.1 找到PML根目录:目录结构与加载规则

PDMS安装后会有一个PML根目录,Windows环境下常见位置是安装盘下的PML文件夹。目录内一般包含这几类子文件夹:

PML/ primitives/ -- 底层基础函数 functions/ -- 按模块组织的业务宏 forms/ -- 自定义窗体定义 app/ -- 应用程序级脚本 menus/ -- 菜单配置文件

不是每个版本都有完全相同的子目录,但基本按这个套路安排。PML解释器加载脚本时会按目录优先级搜索,所有脚本都必须放在这个根目录范围内。如果把.pml文件放在桌面直接执行$M hello.pml,是找不到的。

文件名命名有几个硬性规则:不能带空格,不能带中文,不能用数字开头。batch export.pml这种名字在命令行里会被当成两条命令,大概率加载失败。我统一用export_pipes.pml这种下划线命名,跨版本最稳。

3.2 第一个脚本:读取当前元素路径

创建一个hello.pml文件,内容如下:

-- 输出当前元素路径 $!cur = DB().CurrentElement() $!path = $!cur.Path() MESSAGE $!path

逻辑说明:--是PML注释。第一行注明脚本用途;第二行拿到当前光标指向的元素;第三行取路径;第四行弹窗显示。在PDMS图形界面先点中一个设备,打开Command Window输入$M hello.pml,会弹出消息框,内容就是该设备的完整路径。

运行没反应时先看两处:一是Command Window有没有报错,二是PML目录下文件是否存在。提示文件未找到,多半是路径或文件名写错,跟脚本本身的业务逻辑无关。

3.3 遍历当前区域的管道对象

能读路径后,下一步做集合遍历。PDMS凡是批量操作几乎都要用COLLECT,它类似SQL查询,只不过返回的是Element对象数组:

$!zone = DB().CurrentElement() $!pipes = COLLECT ALL ELEMENTS FROM $!zone WHERE TYPE EQ PIPE DO $!p IN $!pipes MESSAGE $!p.Name() LOOP

逻辑说明:第一行取当前元素作为查询起点,第二行用COLLECT收集当前区域下所有管道类型对象,第三到第五行用DO循环遍历输出管道短名。TYPE EQ PIPE表示只收管道,不包含设备、支架等其它类型。

COLLECT条件可以叠加,比如只收集公称直径大于等于100的管道:

$!pipes = COLLECT ALL ELEMENTS FROM $!zone WHERE TYPE EQ PIPE AND BORE GE 100

参数说明:GE表示大于等于,LT小于,GT大于,EQ等于,NE不等于。写在条件里的属性名必须是目标类型真实存在的,比如BORE;如果写了一个不存在的属性,查询会在编译期报错,而不是返回空集合。

用DO循环遍历时要注意性能:如果集合里有一万个对象,不要在循环里做弹窗,界面会卡到像死机。一般先读取需要的数据到本地数组,最后统一输出。

3.4 把脚本挂到自定义菜单

命令行执行适合自己调试,交付给不写脚本的同事用,还是要挂菜单。菜单配置写在menus目录下的.men文件里,往里追加一段:

BTTN "导出管道清单" ACTION $M export_pipes.pml END

逻辑说明:BTTN定义了一个按钮,文本显示“导出管道清单”,点击后执行后面的ACTION,也就是运行export_pipes.pml脚本。END结束定义。

改完菜单文件后,需要重新加载菜单才能生效。不同版本重载方式不一样,常见做法是在命令行敲$M reload,或者从工具菜单里选重新加载;找不到的情况下直接重启PDMS最省事。

菜单挂好了不代表脚本一定能跑,点击后没反应时,回到Command Window手动执行$M export_pipes.pml,先确认脚本本身无报错,再回头排查菜单配置。

3.5 调试小技巧:把输出写到文件再排查

PML脚本报错时,Command Window会打印错误行号和描述,但这个窗口信息在部分环境里被日志刷过去,一闪而过。我一般会给脚本加一个可开关的日志输出,用一个普通的FileW文件对象就能实现:

$!log = NEW FileW() $!log.Open("C:\\export\\script_log.txt", "w") $!log.WriteLine("开始遍历,集合大小: " & $!pipes.Size()) DO $!p IN $!pipes $!log.WriteLine($!p.Name()) LOOP $!log.Close()

逻辑说明:Size()返回集合的元素个数,先写一行日志就能确认COLLECT返回了多少对象。循环里每处理一条写一行,运行完直接翻文件,比盯着屏幕等结果稳得多。路径里的\\是PML字符串转义后的写法,Windows路径必须这样写。

4. 工程实例:批量导出管道清单到CSV

这一章实现一个能直接用的工具:把某个Zone下所有管道的基本属性导出成CSV,供后续应力分析或材料统计使用。整个过程拆成三步:收集对象、读取属性、写文件。

4.1 需求拆解

场景很常见:某项目需要对一个区域的管道做批量抽查,导出一张表,包含管道名称、公称直径、压力等级、保温厚度四列,并且只统计直径不小于100mm的管道。

对应到PML就是四个属性:Name、BORE、RATING、INSUL_THK。属性名在不同项目里可能有差异,写脚本前先在PDMS里选中一根管道,手动验证属性确实叫这个名,再开始编码。

4.2 收集目标对象集合

在PDMS图形界面或命令行里,把当前元素定位到目标Zone后,用以下命令收集管道集合:

$!zone = DB().CurrentElement() -- 收集直径大于等于100的管道 $!pipes = COLLECT ALL ELEMENTS FROM $!zone WHERE TYPE EQ PIPE AND BORE GE 100

逻辑说明:第二行是整个导出任务的基础。COLLECT返回数组,数组里每个元素都是管道对象的Element引用。BORE GE 100是“BORE属性大于等于100”的意思,数值属性比较时在属性名后面直接写运算符和数值。

为什么不直接对$!zone做全量遍历?因为一个Zone下还可能有支管、伴热管等类型,用TYPE EQ PIPE限定后结果更干净。COLLECT会递归收集子节点下所有管道,不需要自己写嵌套循环,这个行为在常用版本里是稳定的。

4.3 循环读取属性并写入CSV

集合拿到手后,循环读取每一根管道的属性,拼成一行,写到CSV文件:

$!file = NEW FileW() $!file.Open("C:\\export\\pipe_list.csv", "w") $!file.WriteLine("Name,BORE,RATING,INSUL_THK") DO $!p IN $!pipes $!name = $!p.Name() $!bore = $!p.BORE $!rating = $!p.RATING $!thk = $!p.INSUL_THK $!row = $!name & "," & $!bore & "," & $!rating & "," & $!thk $!file.WriteLine($!row) LOOP $!file.Close()

逻辑说明:FileW是PML2的文本写文件类,第一行实例化,第二行以写模式打开CSV,第三行写表头。DO循环里每次读取四列属性,拼成一行写入文件,最后关闭文件流。

参数说明:"w"表示覆盖写入,文件已存在会清空重写。路径C:\\export\\pipe_list.csv用了双反斜杠,因为PML字符串里反斜杠是转义字符。目标目录不存在时Open会失败,运行前先手动创建目录。

注意:读取属性我用的是$!p.BORE点号写法,而不是->箭头。PML2里两种写法都支持,个别版本对点号访问更稳定。建议在自己环境里先各试一次,选能通过的那一种。

4.4 完整脚本拼装与运行

把上面三段组合起来,加一个当前元素类型判断,防止误选中非Zone对象时报错:

-- 批量导出管道清单到CSV $!zone = DB().CurrentElement() IF $!zone.Type() EQ 'ZONE' THEN $!pipes = COLLECT ALL ELEMENTS FROM $!zone WHERE TYPE EQ PIPE AND BORE GE 100 $!file = NEW FileW() $!file.Open("C:\\export\\pipe_list.csv", "w") $!file.WriteLine("Name,BORE,RATING,INSUL_THK") DO $!p IN $!pipes $!name = $!p.Name() $!bore = $!p.BORE $!rating = $!p.RATING $!thk = $!p.INSUL_THK $!row = $!name & "," & $!bore & "," & $!rating & "," & $!thk $!file.WriteLine($!row) LOOP $!file.Close() MESSAGE "导出完成" ELSE MESSAGE "请先选中一个Zone" ENDIF

逻辑说明:开头判断当前元素类型,不是Zone就不执行主体,避免在错误上下文里收集到空集合。主体逻辑与前面一致,最后弹窗提示导出完成。

在命令行里选中目标Zone,执行$M export_pipes.pml,结束后打开CSV验证:第一行是表头,数据行数应该和COLLECT查询到的管道数量一致,数值列没有异常字符。

如果某个单元格为空,先不要急着改脚本,在PDMS里手动检查这根管道是否确实没有填对应属性。PML读不到空属性时返回null,拼出的字符串里会出现空字段,这属于数据问题而不是脚本问题。

5. 避坑指南:PML二次开发常见的五个坑

PML脚本写起来很快,但跑起来处处有惊喜。下面五个问题是我在项目里实际踩过的,按“现象、原因、解决”三行写清楚,对照排查能省大量时间。

5.1 Loop卡死又退出不了

现象:脚本运行后长时间不响应,Command Window没有任何输出,Ctrl+C中断不了,只能重启进程。

原因:最常见的是循环体内触发了一次新的COLLECT或对象更新,导致正在遍历的集合被重新计算,迭代器等于一边遍历一边扩展。另一种情况是DO循环数据量太大又没有任何进度输出,看起来像卡死,其实只是跑得慢。

解决:把需要遍历的集合先赋值给一个普通数组,后续只针对数组内的原始句柄操作:

$!pipes = COLLECT ALL ELEMENTS FROM $!zone WHERE TYPE EQ PIPE $!list = $!pipes DO $!p IN $!list $!p.Update() LOOP

另外,遍历大数据集时每100条写一行日志到文件,进度可见,就不慌。

5.2 属性写入了,保存后却回滚

现象:脚本执行完没有报错,但重新打开PDMS,修改的属性全部变回原来的值。

原因:对Element的属性做赋值只是改内存中的值,没有触发对象在数据库里的持久化。必须调用Update()或执行事务提交,数据才会真正落库。

解决:每次修改属性后,对该Element调用Update方法:

$!p->RATING = "XX" $!p.Update()

如果是批量修改,建议用数据库事务包裹起来,避免中途失败留下半截数据。

5.3 保存了元素句柄,切库后就失效

现象:脚本里先存了一个Element变量,后半段切换到别的数据库,再回来用这个变量,直接报element not found。

原因:Element对象的句柄绑定在当前数据库的上下文里,数据库切换或关闭后,句柄不再有效。

解决:不要存Element句柄,改为保存它的Path字符串,用的时候重新定位:

$!path = $!p.Path() $!target = DB().GetElement($!path)

参数说明:GetElement的参数是元素完整路径。只要路径正确,任何时间都能重新拿到对象,这也是一种更稳妥的跨函数传参方式。

5.4 中文乱码和路径分隔符

现象:脚本源码里写的中文注释出现乱码,导出的CSV用表格软件打开也是乱码。

原因:PML旧版本解释器对UTF-8编码支持不好,源文件保存成UTF-8后中文字符被拆成多字节解释;CSV没写BOM头时,Excel默认按本地编码打开,也会乱码。

解决:PML源文件统一保存为系统区域对应的ANSI编码,不要在源码里直接用中文做条件判断;CSV写到文件时,可以写一个UTF-8的BOM头,或者干脆输出成制表符分隔的文本。

路径分隔符单独说一句:PML字符串里反斜杠是转义字符,写Windows路径必须用\\,或者改用正斜杠C:/export/pipe_list.csv,能绕开转义问题。

5.5 菜单配置了但没有反应

现象:自定义菜单按钮显示正常,点击后什么也不发生,命令行也没有报错。

原因:菜单ACTION里的脚本路径写错,或者脚本开头已经出错,但错误被菜单系统静默吞掉。

解决:先在命令行手动执行脚本路径,确认能跑通;再检查菜单配置里是否用了绝对路径,部分环境下绝对路径会被限制。改成相对于PML根目录的相对路径,重载菜单再试。

这五个坑覆盖了脚本生命周期里的主要翻车点:循环控制、数据持久化、句柄管理、编码路径、菜单加载。下面把这套经验沉淀成可复用的工具库写法。

6. 扛得起工程级别的二次开发:封装自己的PML工具库

脚本跑通只是开始,真正交给团队用,还得把散落的逻辑收拢成库。我一般把导出数据、改属性、写日志三件事拆成公共函数,业务脚本只做参数配置。

6.1 把固定逻辑抽成函数

把上一章的导出功能改成带参数函数:

FUNCTION @ExportPipeList($!zonePath, $!filePath, $!minDia) $!zone = DB().GetElement($!zonePath) $!pipes = COLLECT ALL ELEMENTS FROM $!zone WHERE TYPE EQ PIPE AND BORE GE $!minDia $!file = NEW FileW() $!file.Open($!filePath, "w") $!file.WriteLine("Name,BORE,RATING") DO $!p IN $!pipes $!file.WriteLine($!p.Name() & "," & $!p.BORE & "," & $!p.RATING) LOOP $!file.Close() ENDFUNCTION

这样不同区域、不同导出文件的场景都通过参数传入,不再复制整段脚本。调用时只需写一行:$!result = @ExportPipeList("/ZONE-A", "C:/export/zone_a.csv", 100)。

6.2 加日志和错误处理

工具一旦给别人用,异常处理就是底线。PML2支持ON ERROR结构,捕获后统一写日志:

ON ERROR EXCEPTION $!ex $!log.WriteLine("Error: " & $!ex.Text()) ENDON

逻辑说明:当脚本在执行过程中抛出异常时,控制权会跳到ON ERROR块,$!ex.Text()保存了错误描述。我把这段放在每个工具函数开头,配合日志文件,每次运行都能留下痕迹,排查问题不再靠猜。

6.3 用干跑模式验证逻辑

我给所有批量工具加一个$!dryRun参数,传入TRUE时只统计数量、不写数据,先在屏幕上输出“将处理 N 条管道”,确认收集条件正确后再真跑。这步操作看上去简单,但能避免把错误属性刷进工程库。

这套习惯源于一次没有加日志、脚本死锁后无处寻踪的事故。从那以后我每次写PML工具都强制走一遍“函数化参数+日志+干跑验证”三件套,踩坑概率直线下降。希望帮到你。

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

返回列表