首页 / 资讯中心 / 文章详情

CAD多重插入引用无法分解?AutoLISP解锁与上锁实战

CAD多重插入引用无法分解?AutoLISP解锁与上锁实战 ★ FEATURED ARTICLE
简介围绕CAD多重插入引用炸开与加密解密整理的doc文档面向需要处理受保护图纸的CAD绘图员、设计人员及具备一定AutoLISP基础的从业者帮助理解多重插入块的加密机制与解除思路。内容从多重插入引用的概念切入梳理加载AutoLISP脚本、借助CAD加密插件以及自定义lsp函数等处理路径并延伸讲解AutoLISP编程基础、ENTSEL与ENTGET获取块信息、DEFUN定义转换函数以及普通块与多重插入块互转的实现逻辑同时交代图纸加密技术在知识产权保护中的实际背景。对块实体组码、实体类型切换及脚本加载异常也给出提示便于读者理解转换原理并排查常见问题。包内仅含1个doc文件压缩包约18KB篇幅精炼便于按知识点专项查阅。已有457人学习可作为CAD加密图纸处理与AutoLISP入门实践的参考材料适合需要补齐块编辑与脚本排错思路的读者。1. 一份文档解决的老问题接手一个老项目CAD 图纸打开正常双击块却弹“无法分解”想改个标高整张总图纹丝不动。翻遍互联网上关于图纸锁定的帖子答案都指向同一个词——多重插入引用。这份资源围绕它展开怎么判断手上的块是不是 MInsertBlock怎么用一段 AutoLISP 把它炸开成普通块以及怎么反向把普通块重新“加密”回去。内容形态是一份 Word 文档加两段可直接粘贴的源码附带两个现成工具的调用方式。它面向的是天天跟图纸打交道、被锁死图卡过进度的绘图员以及需要把解锁、上锁动作批量化的 CAD 二次开发人员。2. 先看数据结构MInsertBlock 比普通块多了什么动手写代码前先确认对象。很多“炸不开”的图纸用 LIST 命令看块参照显示的信息与普通块几乎一样块名、插入点、比例、旋转角四个值都对得上。差别藏在 LIST 不显示的 DXF 组码里只有拿到实体表才看得出来这也是这类问题经常被当成玄学处理的原因。2.1 一条 entget 输出里的四个多余组码在命令行敲(entget (car (entsel)))选中一个怀疑被锁的块会看到类似下面这样一串点对;; 被锁定多重插入的块参照entget 输出节选 (0 . INSERT) ; 实体类型仍然是 INSERT (5 . 2A7) ; 句柄entmake 时不能带 (330 . 图元名) (100 . AcDbEntity) ; 第一层子类标记 (8 . 0) ; 图层 (100 . AcDbMInsertBlock) ; 关键这一行决定了它炸不开 (2 . BLK_TITLE) ; 引用的块定义名 (10 1200.0 800.0 0.0) ; 插入点 (41 . 1.0) (42 . 1.0) (43 . 1.0) (50 . 0.0) (70 . 3) ; 列数 (71 . 4) ; 行数 (44 . 6000.0) ; 列间距 (45 . 3000.0) ; 行间距同样的位置换成普通块输出里(100 . AcDbMInsertBlock)会变成(100 . AcDbBlockReference)并且通常看不到 70、71、44、45 这四个组码。也就是说锁定与解锁在数据层面只差一个子类标记加四个数值并不存在什么加密算法。组码多重插入块普通块参照含义0INSERTINSERT实体类型一致100AcDbMInsertBlockAcDbBlockReference子类标记决定行为2块名块名引用的块定义10/20/30插入点插入点基准坐标41/42/43比例比例三个方向的缩放50旋转角旋转角弧度制70列数1通常缺失阵列列数71行数1通常缺失阵列行数44列间距通常缺失实际间距45行间距通常缺失实际间距这张表是后面所有代码的依据。实际项目中遇到的“加密图”九成以上都是把 70、71 设成大于 1 的数把 44、45 设成偏移距离再把子类标记换成 AcDbMInsertBlock。视觉上它还是一堆图元但 AutoCAD 在实体层面已经不把它当作可分解的块引用来管理了。2.2 EXPLODE 为什么直接拒绝EXPLODE命令的内部逻辑会先判断选中实体的子类标记。如果读到 AcDbMInsertBlock它走的是阵列分支而阵列分支本身没有“分解成子图元”的实现于是直接返回失败命令行提示“无法分解多重插入块”。这个过程中没有任何提示告诉你缺什么所以很多人第一反应是图被加密了、必须找专用工具。理解这一点之后处理思路就很明确不去想办法突破 EXPLODE而是先把实体的数据结构改回普通块参照让 EXPLODE 自己愿意接手。这也是资源里提供 AutoLISP 方案的原因——LISP 可以直接改实体表不需要经过任何图形界面命令。2.3 真死锁和假死锁的分界文档里用了“永远无法解开的死锁”这个说法实际使用中要分成两种情况判断错了会白忙一整天。第一种是假死锁块定义还在。用(tblsearch BLOCK BLK_TITLE)能查到名字或者在图块管理器里能看到这个块那没问题改完组码用 entmake 重建参照就能正常炸开。第二种是真死锁块定义已经被清理掉了。图纸里只剩一个 INSERT 引用指向一个不存在的块名视图里显示的图形来自代理对象或者残留的缓存数据。这种情况下无论怎么改组码都救不回来因为拆出来的子图元本来就不存在了。判断方法很直接选中块看属性面板里的块名是否以*开头或者用(tblsearch BLOCK 块名)返回值是否为 nil。如果是 nil先别急着跑脚本把原始图纸备份一份然后考虑用几何提取的方式把可见图形导出来而不是执着于炸开。提示任何修改前先另存副本。entmake 重建实体失败时原实体已经被 entdel 删掉了没有后悔药。3. 拆解 exm 与 lockb两个函数的逐行手术资源里给的两段代码本身不长但原始文档从网页复制过来时全角括号和换行都乱了直接粘贴会报“输入的列表有缺陷”。整理成可运行版本之后核心逻辑其实只有四步取实体表、改两个数值、换子类标记、重建实体。3.1 exm 的骨架一个 while 循环吃掉整张图先把结构看清楚这是整理后的 exm(defun c:exm ( / stm e stlx r70 r71 d44 d45) (setvar cmdecho 0) ; 关掉命令回显避免刷屏 (setq stm (car (entsel \n点取多重块: ))) ; 单选返回实体名 (while stm ; 选到就循环右键退出 (setq e (entget stm)) ; 取实体表 (setq stlx (cdr (assoc 0 e))) ; 实体类型 (if ( stlx INSERT) (progn (setq r70 (assoc 70 e)) ; 列数点对 (setq r71 (assoc 71 e)) ; 行数点对 (setq d44 (assoc 44 e)) ; 列间距 (setq d45 (assoc 45 e)) ; 行间距 (setq e (subst (cons 44 0) d44 e)) ; 清零普通块不需要 (setq e (subst (cons 45 0) d45 e)) (setq e (subst (cons 70 0) r70 e)) (setq e (subst (cons 71 0) r71 e)) (setq e (subst (list 100 AcDbBlockReference) (list 100 AcDbMInsertBlock) e)) ; 换子类标记 (entmake e) ; 用新表重建 (entdel stm) ; 删掉旧实体 ) ) (setq stm (car (entsel \n点取下一多重块: ))) ) (setvar cmdecho 1) (princ) ; 静默退出不返回多余值 )几个参数值得单独说明。cmdecho置 0 是为了让subst、entmake这些内部调用不在命令行留下输出批量处理几十个块时差别很明显。entsel外面套car是因为它返回的是一个列表(实体名 点坐标)不带car传给entget会直接报错这是新手最常见的翻车点之一。while循环的退出条件是entsel返回 nil也就是用户按空格或右键。局部变量表( / stm e ...)里的斜杠后面必须跟空格这是 AutoLISP 的语法要求写成(/stm会导致整个 defun 解析失败而且报错信息完全指不到这一行。3.2 改表的三处关键动作subst的语义是“在表里找与旧项 equal 的元素替换成新项”。它不做位置匹配只看结构是否相等所以(cons 70 0)能精确换掉(70 . 3)不会误伤别的位置。第一处动作是 70 和 71 归零。严格来说普通块参照里这两个组码直接缺失也行但保留为 0 更保险某些版本的 entmake 对缺组码的容忍度不一致。第二处是 44 和 45 清零这一步在原始代码里是有的作用是让重建后的实体不带阵列偏移参数。第三处也是真正起作用的一步把(100 . AcDbMInsertBlock)换成(100 . AcDbBlockReference)。子类标记一换AutoCAD 在加载这个实体时就会按普通块参照来解析EXPLODE 自然就接受了。注意(list 100 AcDbMInsertBlock)和(cons 100 AcDbMInsertBlock)生成的点对完全等价打印出来都是(100 . AcDbMInsertBlock)所以 subst 能匹配上。原始代码用的是 list 写法能跑只是可读性差一点。3.3 entmake 加 entdel 的代价这个方案的代价在重建环节。entmake是根据一张 DXF 表从头创建实体它不接受 5句柄和 330所有者这两个组码带上就会返回 nil而 entdel 已经在后面准备好了删原实体两者一叠加结果就是块直接从图上消失命令行还什么都没提示。稳妥的写法是在 entmake 之前过滤掉这些组码;; 在 entmake 之前插入剔除不能由 entmake 携带的组码 (setq e (vl-remove-if (lambda (x) (member (car x) (5 330 360 102))) e)) (if (entmake e) (entdel stm) (princ \n重建失败原实体已保留请检查组码顺序。) )vl-remove-if需要(vl-load-com)先加载扩展函数放在文件开头调用一次即可。这段过滤同时解决了另一个隐性损失带 102 组码的实体通常挂着扩展字典比如某些插件的自定义数据重建后这部分数据一定丢失。还有一处无法回避的损失是阵列信息。转成普通块之后70、71、44、45 这四个值就没了原本 3 列 4 行显示出来的 12 份图形炸开只会得到一份。要恢复阵列得在转换前把四个值记下来炸完之后按间距自己复制;; 转换前先留档后面复制用 (setq info (list (cdr (assoc 70 e)) (cdr (assoc 71 e)) (cdr (assoc 44 e)) (cdr (assoc 45 e)))) ;; info (3 4 6000.0 3000.0) 列数 行数 列距 行距这段留档逻辑在批量场景里很关键第 6 章会把它接进循环里。3.4 lockb 反向上锁原版少写了四行反向函数把普通块改回多重插入块思路对称但原始代码这里有个实质缺陷。它写的是(setq r_zm70 (assoc 70 e)) (setq e (subst (cons 70 1) r_zm70 e))问题在于普通块参照的实体表里根本没有 70 这个组码(assoc 70 e)返回 nil(subst 新项 nil 表)在 AutoLISP 里不会报错它会原样返回整张表——什么都没改。44 和 45 同理。最终结果只是把子类标记换成了 AcDbMInsertBlock却没有任何阵列参数重建出来的块要么被 AutoCAD 拒绝要么所有图形叠在同一个位置。正确做法是显式追加这四组点对(defun c:lockb ( / stm e cg rg) (setvar cmdecho 0) (setq cg (getreal \n列间距 6000: )) (if (null cg) (setq cg 6000.0)) (setq rg (getreal \n行间距 3000: )) (if (null rg) (setq rg 3000.0)) (while (setq stm (car (entsel \n点取要上锁的块: ))) (setq e (entget stm)) (if ( INSERT (cdr (assoc 0 e))) (progn (setq e (vl-remove-if (lambda (x) (member (car x) (5 330 360 102))) e)) (setq e (subst (100 . AcDbMInsertBlock) (100 . AcDbBlockReference) e)) ;; 普通块没有 70/71/44/45必须 append 补上 (setq e (append e (list (cons 70 2) (cons 71 2) (cons 44 cg) (cons 45 rg)))) (if (entmake e) (entdel stm) (princ \n上锁失败多半是 70/71 位置不对试着手工调整顺序。)) ) ) ) (setvar cmdecho 1) (princ) )getreal返回 nil 表示用户直接回车用if (null ...)兜一个默认值这样批量操作时一路回车就能跑完。70 和 71 都设成 2 是最小可用的阵列规模想做得更隐蔽可以设大一点但超过 20 之后图纸打开会明显变慢这点要有预期。append 到表尾在某些版本上会让 entmake 报错因为 DXF 对组码顺序有要求70、71 应该跟在子类标记后面、10 插入点之前。如果实测返回 nil把这四组用插入位置的方式拼进表里而不是简单追加。4. 从加载到炸开一次完整的执行链路代码写对只是第一步LISP 能不能被 CAD 认出来、命令名对不对、编码有没有问题是另一类高频故障。资源文档里提到的“拖进窗口”这种加载方式在不同版本上表现并不一致得准备备选路径。4.1 三条加载路径与适用场景第一条是命令行输入APPLOAD弹出对话框后选文件这是最通用的方式任何版本都能用。第二条是把 .lsp 文件直接拖进绘图区AutoCAD 2012 之后的版本支持早期版本会把它当成未知文件类型忽略。第三条是自动加载适合团队统一部署把文件放到支持文件搜索路径下的任意目录然后在一个叫acaddoc.lsp的文件里写加载语句。每次打开图纸都会自动执行绘图员不需要记任何操作。;; acaddoc.lsp 片段放在支持文件搜索路径内 ;; findfile 找不到时不会报错避免影响其他图纸打开 (if (findfile exm.lsp) (progn (load exm.lsp) (princ \n[块工具] 已加载EXM 解锁LOCKB 上锁。) ) ) (princ) ; 结尾必须静默否则会吞掉后续自动加载的返回值最后那行(princ)不是可有可无的。acaddoc.lsp 的最后一个表达式的返回值会回显到命令行如果返回的是个列表屏幕上就会冒出一串看不懂的字符新同事看到会以为加载出错。4.2 命令名和文件名没有关系定义命令靠的是(defun c:exm ...)里的c:前缀加名字跟文件叫什么完全无关。文件叫wjjm.lsp但里面定义的是c:exm那加载后能用的命令就是EXM。资源文档里出现“将 wjjm 拖入窗口输入 wjmm”这种描述名字不一致是因为原文本身有笔误实际使用时以代码里c:后面的名字为准。命令行输入时不区分大小写EXM、exm、Exm效果一样。但如果代码里写成了(defun c:exm () ...)而在命令行敲C:EXM会提示未知命令——带冒号的写法只在代码里有效。4.3 一次完整操作的标准流程把上面的东西串起来处理一张锁死图纸的顺序是这样打开图纸先另存为一份带_backup后缀的副本后续所有操作都在副本上做。APPLOAD加载exm.lsp命令行看到“[块工具] 已加载”的提示才算成功。输入EXM光标变成选择框点中那个炸不开的块。命令行没有任何反馈是正常的因为cmdecho被关掉了。此时用 LIST 复查该块子类标记应该已经变成 AcDbBlockReference。输入EXPLODE或点分解按钮块正常散开可以编辑。需要还原时先UNDO回退或者从备份副本重来不要指望LOCKB能百分百还原原始状态。第 6 步要强调一下。LOCKB重建的是结构不是原图。原块的图层过滤、属性值、动态块参数、扩展数据在 entmake 之后全部丢失还原出来只是“看起来像阵列”的新块。真正需要反复上锁解锁的场景正确做法是保留原始文件而不是靠脚本往返转换。注意加载.lsp前先用记事本打开看一眼。纯文本能读懂的才加载遇到.fas、.arx、.vlx这类编译过的二进制文件来源不明就不要往 CAD 里拖它们有完整的文件系统和注册表访问权限。5. 避坑排查五种转换结果不对的现场脚本能跑通不代表结果正确下面这几条都是实际处理图纸时反复遇到的按现象、原因、解决三段来拆。5.1 炸开后所有图形叠在一个点上现象是执行完 EXM 再 EXPLODE本该铺开成一整片的图元全部堆在插入点图纸看起来像被压扁了。原因有两层。表层是LOCKB上锁时没写 44 和 45阵列间距为 0所以行列偏移全是零深层原因是转换过程本来就丢弃了阵列参数即使原始图纸间距正常解锁后炸开也只有一份图元剩下的 11 份从来没被重建过。解决方式是在转换前用 3.3 节那段代码把(列数 行数 列距 行距)存下来炸开之后用COPY配合相对位移按矩阵补齐或者直接在 LISP 里循环 entmake 出 N×M 份。数量少的时候手工复制更快超过二十份就值得写循环。5.2 执行后块直接消失命令行毫无提示现象是按流程走完选中的块从图上没了UNDO能退回来但看不出哪里出错。原因基本可以锁定在entmake返回了 nil。最常见的触发条件是实体表里带着 5 句柄或 330 所有者组码这两个值由 AutoCAD 内部管理不允许通过 entmake 指定。第二种可能是表里有 102 组码挂着扩展字典结构不完整导致解析失败。第三种是组码顺序被打乱尤其是子类标记跑到 10 之后。解决办法是把 3.3 节的vl-remove-if过滤加上并且在entmake外面套if判断失败时打印提示而不执行entdel。定位具体原因时把要 entmake 的表用(print e)打到命令行逐项对照 DXF 参考里 INSERT 实体的组码顺序通常一眼就能看出多出来什么。5.3 炸出来是一堆 *U 开头的匿名块现象是 EXPLODE 成功执行了但得到的不是线条和文字而是一堆名字形如*U12的块参照还得再炸一轮。原因是原块是动态块或者被程序生成的匿名块。动态块在插入时会在后台生成一个匿名块定义来保存当前的参数状态多重插入引用指向的往往就是这个匿名块所以第一层炸开只是剥掉外壳。解决方式是连续炸两次或者用BURST命令一次性把属性也摊平成文字。如果匿名块数量很多写个循环对选择集反复执行 explode 更快判断条件是实体类型为 INSERT 且块名以*开头。5.4 块能看见但查不到定义怎么都炸不开现象是图上明明显示着完整图形EXM执行后子类标记也改对了EXPLODE却依然失败tblsearch查块名返回 nil。这是 2.3 节说的真死锁。块定义在图纸传递过程中被清理掉了屏幕上看到的图形来自代理实体的缓存显示实际几何数据并不在图纸里。这种情况下任何代码都救不回来因为要拆出来的东西本身不存在。处理方式是放弃炸开改用几何提取把可见图形用NCOPY复制到当前空间或者输出成 DXF 后从 ENTITIES 段里把代理图形的边界数据捞出来重建。前者适合少量图形后者工程量大但能保住坐标精度。5.5 转换后颜色、线型、属性全变了现象是图形位置对了但颜色从随层变成了白色线型从虚线变实线带属性的标题栏变成一堆没有内容的空框。原因是entmake重建的是实体本身不携带块定义内部的图层映射和属性定义。普通块参照的颜色来自块定义里图元的ByBlock设置重建后这个关联还在但如果原图纸用了图层替代或者对象特性替代那部分数据挂在扩展字典里早就被过滤掉了。属性块更麻烦属性值存在 INSERT 后面的 ATTRIB 子实体里entdel 删原实体时一并没了。解决方式是在转换前先用(vlax-invoke ... GetAttributes)把属性值导出来存成文本转换后再用ATTEDIT或者脚本批量回填。颜色和线型的丢失只能靠转换前截图或者记录图层名来对照这也是为什么第 4 章强调所有操作都在备份副本上做。6. 二次开发把一次性脚本做成批量工具单个块手工点选处理一两张图纸够用。真正需要写工具的场景是接手一整个项目的图纸包几十张图、每张几十个锁定块这时候得把选择方式从entsel换成ssget再加上留档和日志。批量过滤不能简单写(ssget X ((0 . INSERT) (70 . 1)))。70 在 INSERT 实体的多重插入里表示列数不是位码这个过滤条件只会命中列数恰好为 1 的块行数大于 1 的会被漏掉。靠谱的做法是先选出全部 INSERT再逐个读实体表判断(defun c:exmall ( / ss i e col row cg rg cnt) (setvar cmdecho 0) (setq ss (ssget X ((0 . INSERT))) cnt 0) (if ss (progn (setq i 0) (while ( i (sslength ss)) (setq e (entget (ssname ss i))) (setq col (cdr (assoc 70 e))) ; 列数可能为 nil (setq row (cdr (assoc 71 e))) ; 行数可能为 nil ;; 只要行或列超过 1就判定为多重插入 (if (or (and col ( col 1)) (and row ( row 1))) (progn (setq cg (cdr (assoc 44 e))) (setq rg (cdr (assoc 45 e))) ;; 打印留档出问题可以照这个表还原 (princ (strcat \n锁定块: (cdr (assoc 2 e)) 列 (itoa (if col col 1)) 行 (itoa (if row row 1)) 间距 (rtos (if cg cg 0.0) 2 1) , (rtos (if rg rg 0.0) 2 1))) (setq cnt (1 cnt)) ) ) (setq i (1 i)) ) (princ (strcat \n共扫描到 (itoa cnt) 个锁定块。)) ) ) (setvar cmdecho 1) (princ) )这段先只做扫描不做修改是我习惯的验证顺序先看清有多少、都是什么块名、间距多少确认清单没问题再把 entmake 那段接进来。直接一步到位改成“扫完就改”一旦某张图的组码结构特殊整批数据都会被写坏而原来的实体已经被 entdel 掉了只能从备份恢复。验证改造结果有一个笨但有效的办法转换前后各输出一次 DXF用文本对比工具看 ENTITIES 段里对应块参照的组码差异。正常情况下应该只看到 100 子类标记变化、70、71、44、45 消失其他组码原样保留。如果多出或少掉了别的东西说明过滤条件切得太狠或者顺序被打乱。我一般还会顺手统计一次块参照总数转换前后数量必须一致少一个就说明某次 entmake 失败被 entdel 吞了。从那以后我每次给团队写这类批量脚本都强制先跑一遍只读扫描把清单打出来人工过一眼再执行写入。图纸这东西不像代码有版本控制改坏了就是改坏了希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?
咨询建站