我是一名化工配管工程师。
如果你也是做配管的,你肯定经历过下面这个场景。
拿着一叠P&ID图纸,少则十几张,多则几十张。你在第3张图里看到一根管线,管号写着「6"-P-1001-01」,它画着画着,箭头一拐,标了个「TO SHEET 7」。你去第7张P&ID找,找到了,接着看,又写着「TO SHEET 12」。再去第12张,再来一个「FROM SHEET 3 & 7, TO SHEET 18」。
一套装置里几百上千根管线,每根管道都得从头到尾对一遍。管号写错了没有,管径对不对,阀门类型对不对,仪表有没有配对。你得在几十张P&ID之间来回翻,手动一个管号一个管号地找,眼睛都看花。漏了一个,施工队按错的做了,现场返工,那成本就不是闹着玩的。
CAD自带的FIND命令能干这个事。但对话框弹出来,输入管号,点查找,出来一个列表,点一下跳一个,看完这个再重新开对话框输管号找下一个。六十多根管线,按这个节奏搞一遍,一下午就过去了。
我就想要一个东西。在图纸里点一个管号,按一个键,直接跳到下一个同样的管号。再按,再跳。不用对话框,不用输入,不用在鼠标和键盘之间来回倒腾。
所以那段时间我学了点AutoLISP,写了一个小插件,叫FF。
FF的逻辑特别简单。你在任意一张P&ID里选中一个管线号,按下快捷键,它把这个管号记下来,然后搜索整个dwg文件里所有写了同样管号的地方。再按一下FF,视图自动跳到下一个匹配项。再按,再跳。从头到尾循环,一个都不会漏。

如果中间你点了一个新的管号,它会识别出来,自动换成新的管号重新搜。
这玩意在P&ID校审的时候有多好用,谁用谁知道。
听起来挺简单的对吧,但实现上有很多细节要处理。
先说AutoCAD里文字的类型。P&ID图纸里的管线号,有的写在单行文字TEXT里,有的写在多行文字MTEXT里,更常见的是写在定制的属性块里。这三种东西存文字的方式完全不一样。
TEXT最简单,文字直接存在组码1里。
MTEXT也存组码1,问题是里面塞了一堆格式化代码。\P是换段,\A是对齐,\C是颜色,\f是字体,花括号用来分组。你搜「6"-P-1001-01」的时候,如果这个管号在图纸里是用红色标注的,那它在MTEXT里实际存的是「{\C1;6"-P-1001-01}」。不处理这些格式化代码,搜索直接失败。
我写了一个ff-strip-mtext函数专干这个。逐个字符扫过去,遇到反斜杠判断后面是什么,是换段换行就替换成空格,是颜色字体这些格式化代码就整个跳过,花括号直接丢掉。
属性块INSERT更麻烦。属性不是存在块参照本身上的,而是作为子实体挂在块后面。你得从块参照开始,用entnext一个一个往后遍历子实体,直到碰到SEQEND标记才停止。每个ATTRIB实体的组码1里才是属性值,把所有属性拼起来才是完整的管号。
核心的取值函数是这样的。
(defun ff-get-value (ename / elist etype raw) (if (not ename) "" (progn (setq elist (entget ename)) (if (not elist) "" (progn (setq etype (cdr (assoc 0 elist))) (cond ;; 单行文字,组码1就是管号内容 ((= etype "TEXT") (setq raw (cdr (assoc 1 elist))) (if raw raw "")) ;; 多行文字,需要先把格式化代码剥掉 ((= etype "MTEXT") (setq raw (cdr (assoc 1 elist))) (if raw (ff-strip-mtext raw) "")) ;; 属性块,钻进子实体里捞属性值 ((= etype "INSERT") (ff-get-attrib-values ename)) (t "") ) ) ) ) ))三种实体走到三个分支里,各取各的值。拿到值之后,就可以搜了。
搜全图用的是AutoLISP的ssget函数。选全部TEXT和MTEXT和INSERT,拿到值,用wcmatch做模糊匹配。大小写不管,只要值里面包含了搜索关键词就加入结果列表。
但这块又有一个坑。
wcmatch里的通配符。管号里不是没有特殊字符。英寸符号"是双引号,有些老图纸里习惯用#号分隔,还有用句号.做分隔的。但#在wcmatch里匹配任意数字,.匹配任意单个字符。不处理的话搜「2#阀门」会搜出「2号阀门」。
所以我又写了一个转义函数。
(defun ff-escape-wcmatch (str / i ch result specials) (setq result "") ;; wcmatch里的特殊字符,每个前面加反引号转义 (setq specials '("#" "@" "." "?" "*" "~" "," "[" "]" "`")) (setq i 1) (while (<= i (strlen str)) (setq ch (substr str i 1)) (if (member ch specials) (setq result (strcat result "`" ch)) (setq result (strcat result ch)) ) (setq i (1+ i)) ) result)函数本身没几行。但少了它,搜管号的时候碰到特殊字符就翻车。写代码就是这样的,七八成的精力花在处理两成的边界情况上。
整个插件最核心的设计,是主命令C:FF里的状态机。
我用了四个全局变量来记状态。FF-A是当前搜索的管号,FF-MATCH-LIST是所有匹配到的实体列表,FF-CURRENT-INDEX是当前翻到第几个了,FF-LAST-ENTITY是上次按FF时跳到的那个实体。
每次你按FF,它走三步。
第一步,拿到你当前选中的实体。你如果已经点选了一个管号,直接取它。没选的话,提示你点一下。
第二步,读出这个实体的文字值,跟当前存的管道号做个比对。如果管号是空的,或者你新选的值不包含当前的管号,说明你要换一个管号搜了,那就重新扫全图。如果管号没变,那就继续翻页。
第三步是最妙的。你当前选中的实体,跟上次FF带你跳到的那个实体,是同一个吗?如果是同一个,说明你在连续按FF,那就前进到匹配列表里的下一个。如果不是,说明你手动在图纸上点选了列表中间的某一个,那就直接跳到那个位置,从那接着往下翻。
;; 这条if是整个交互体验的核心(if (equal ent *FF-LAST-ENTITY*) ;; 连续按FF,前进到下一个,到末尾了回到第一个 (setq *FF-CURRENT-INDEX* (rem (1+ pos) (length *FF-MATCH-LIST*))) ;; 手动点选了别的匹配项,直接跳到那个位置 (setq *FF-CURRENT-INDEX* pos))同一个→前进,不同→跳转。就这一句if,整个操作的流畅感全凭它。
ff-select-and-show这个函数也值得一提。它的工作很简单,清掉之前的选中状态,用ZOOM Object把目标实体拉到屏幕正中间,再把它选中显示夹点。你连续按FF,视图就跟着管号一个一个跳,像是在翻一本管线的索引目录。那个感觉太爽了。
说真的,用全局变量做状态机,放在现在的软件工程课上可能会被老师说不够优雅。但一个三百多行的小工具,你非要搞什么设计模式,搞什么面向对象封装,那才是真的不优雅。四个变量,九个函数,一条命令。干净利落。
回到这个工具的初心。它解决的是一个非常非常具体的痛点。不涉及AI,没用什么高级技术,甚至代码风格都透着土味。
但正是因为它够小,够具体,够直接,所以真的好用。
每次拿到一套新装置的P&ID做校审,把dwg打开,脚本导入,一根管号一根管号地过。手指在键盘上规律地敲两下,视图刷地一下跳到下一张图里的同一条管线。管号、管径、阀门、仪表,一行一行核过去。一个上午能干完以前一整天的工作量。
我就觉得,嗯,那个周末学AutoLISP,值了。
关键的代码上面都贴了,自己拼一下就能跑。
想要完整lsp文件的,公众号后台私信「配管提效」我直接发你。
如果你也是搞化工配管的,或者经常在CAD里跟大量文字打交道,可以把这段代码拿去用。加载很简单,CAD命令行里输入 load 回车,然后选择下载好的FF.LSP文件就可以了,也可以直接把这个文件拖拽到CAD窗口中。
最后说句跟技术没关系的。
我自己有时候觉得,做工程技术这行,很多人对手头重复、繁琐的工作是认了的。觉得这就是工作的一部分,忍着呗。但我总觉得,如果能花一点点时间,学一个最小的工具,写一个最简单的脚本,把那些纯机械的重复操作省下来,把精力花在真正需要你去判断、去思考的事情上,这个投资怎么算都是值的。
何况,自己做出来的东西,用着是真的有成就感。
不是那种「我完成了一个项目」的成就感。就是那种,把一件原来要花三个小时的事,变成了二十分钟。然后你靠在椅子上,喝了口水,想着今天可以早点下班。
这种感觉,谁用谁知道。
以上,既然看到这里了,如果觉得不错,随手点个赞、在看、转发三连吧,如果想第一时间收到推送,也可以给我个星标⭐~
谢谢你看我的文章,我们,下次再见。
夜雨聆风