简介:这份文档面向需要处理加密CAD图纸的设计人员与制图学习者,聚焦多重插入引用这一常见图纸加密方式,提供可落地的炸开与解密思路。内容围绕AutoLISP脚本与加密插件展开,涵盖wjjm文件加载、CYN-命令转换、exm与lockb函数定义,以及ENTSEL、ENTGET、DEFUN等命令在块转换中的实际用法,并延伸至CAD图纸加密技术的原理与知识产权保护背景。资源包共1个doc文件,约18KB,以文字说明与代码片段为主,便于对照操作与查阅。目前已有455人学习下载,适合希望掌握图纸解密与块转换技巧的初、中级CAD用户参考,也可作为AutoLISP入门实践的辅助材料。
1. 一份 2009 年的 LISP 小抄,为什么今天还有人翻出来
上周帮一个做幕墙深化的朋友看图纸,他发来一个 DWG,说整张图就是一个块,双击进不去,EXPLODE敲了十几遍命令行只回一句“无法分解”。我打开一看,特性面板里 X、Y、Z 三个方向的比例都是 1,但块名后面挂着一串看不出含义的后缀——典型的多重插入引用(MINSERT)。这东西在 CAD 圈里被叫成“图纸加密”,其实它压根不是加密算法,而是把块参照的阵列参数和行列数写死,让常规分解命令失效。这份流传很广的CAD多重插入引用-炸开方法(加密解密).doc就是围绕这个场景攒出来的操作笔记,核心是三套解法:现成的 LISP 工具wjjm、插件命令CYN-,以及一段可以自己存成.lsp的源码,用exm把 MINSERT 降级成普通块,再用lockb反向加回去。适合谁?经常接手外单位图纸、需要二次编辑底图的施工图深化、算量、翻模从业者。下面我按“先搞懂它到底锁了什么,再动手,最后说坑”的顺序拆。
2. 多重插入引用到底锁在哪:从 DXF 组码看“炸不开”的真相
很多人以为图纸被加密了,其实 DWG 文件本身没动过手脚,只是块参照的实体类型从INSERT变成了MINSERT。要理解为什么EXPLODE失效,得先看 AutoCAD 内部怎么描述一个块参照。用ENTGET把实体数据抓出来,会得到一串以组码(group code)为键的关联表,关键几项是:
| 组码 | 含义 | 普通块(INSERT) | 多重插入块(MINSERT) |
|---|---|---|---|
| 0 | 实体类型 | INSERT | INSERT |
| 100 | 子类标记 | AcDbBlockReference | AcDbMInsertBlock |
| 70 | 列数 | 0 或 1 | ≥1(常被设成较大值) |
| 71 | 行数 | 0 或 1 | ≥1 |
| 44 | 列间距 | 0 | 非 0 |
| 45 | 行间距 | 0 | 非 0 |
真正卡住分解的是100这个子类标记。AutoCAD 在判断一个实体能不能被EXPLODE时,会看它的子类是不是AcDbMInsertBlock;只要是,就按“阵列块”处理,拒绝常规分解。70、71、44、45只是让这个阵列在视觉上铺开,哪怕你把行列都改成 1,只要子类标记还在,照样炸不开。这就是为什么网上有些“改比例”“改行列数”的偏方时灵时不灵——它们没动到根上。
2.1 为什么改行列数没用,改子类才有用
我见过有人手动在特性面板里把“列数”从 50 改成 1,然后满怀希望地敲EXPLODE,结果还是失败。原因就在上面那张表:70和71控制的是阵列重复次数,属于显示参数;100才是实体身份。AutoCAD 的对象模型里,AcDbMInsertBlock是AcDbBlockReference的派生类,派生关系写在实体数据里,不是靠面板能改的。所以正确的降级路径是:读出实体表,把100的值从AcDbMInsertBlock替换成AcDbBlockReference,同时把70、71、44、45归零,再用ENTMAKE重建实体、ENTDEL删掉原实体。这套动作就是exm命令干的事,顺序不能乱——先ENTMAKE再ENTDEL,反过来的话原实体没了,新实体还没建,图元就丢了。
2.2 三套解法的选型:什么时候用工具,什么时候自己写
文档里给了三条路,实际用起来差别不小。wjjm是打包好的 LISP,拖进窗口、命令行敲wjjm、点块就行,适合不想碰代码的人;CYN-是某个加密插件的命令,前提是你装了那个插件,环境不干净的时候容易和别的插件抢命令名;自己存exm.lsp最可控,源码摆在眼前,改个命令名、加个批量循环都方便。我的习惯是:单张图、赶时间,用现成工具;要批量处理一个目录、或者图纸来自不同来源、块结构五花八门,自己维护一份exm.lsp,出问题能改。下面两章分别把“用工具”和“自己写”走一遍。
3. 用现成工具炸开:wjjm 加载与 CYN- 命令的实操路径
先说最省事的那条。拿到一份被 MINSERT 锁住的图纸,第一反应不应该是急着找插件,而是先确认它到底是不是 MINSERT——有些图只是块被设了“不允许分解”,那是另一回事,用BLOCK命令改属性就行,跟本文说的不是一类问题。确认方法很简单:选中那个块,看特性面板里有没有“列数/行数”这两项,有就是 MINSERT。
3.1 wjjm 的加载与调用步骤
wjjm是一个 AutoLISP 文件,本质和后面要自己写的exm.lsp同类,只是封装好了交互提示。加载方式有两种,文档里都提到了,我按可靠性排序:
第一种,拖拽加载。直接把wjjm.lsp从资源管理器拖进 AutoCAD 绘图区,命令行会回显加载成功的提示。这种方式快,但有个前提——CAD 的SECURELOAD系统变量不能设成 2(严格模式),否则会拦截非信任路径的 LISP。查一下:
; 在 AutoCAD 命令行直接输入,查看当前安全加载级别 SECURELOAD ; 返回 0 或 1 表示允许加载任意路径,2 表示只允许信任路径第二种,走“工具 → 加载应用程序”(APPLOAD命令),在对话框里选中文件加载。这种方式不受SECURELOAD的拖拽限制影响那么直接,但加载记录会留在注册表里,下次开图可以设成启动时自动加载。加载完在命令行敲wjjm,按提示点选目标块,它会把这个块的多重插入属性剥掉。注意命令名是wjjm,文档里有一处写成wjmm,是笔误,敲错了命令行只会提示“未知命令”,别以为是文件没加载上。
3.2 CYN- 命令的适用边界
CYN-来自某个第三方加密插件,文档只说“输入 CYN- 将多重插入块转换为普通块”。这条路的坑在于命令名带连字符,而且依赖插件本身已加载。实际排查时经常遇到两种情况:一是插件装了但没随 CAD 启动,命令行敲CYN-无反应,得先APPLOAD把插件的主文件加载进来;二是插件版本和 CAD 版本不匹配,命令注册上了但执行报错。我的建议是,如果手头没有这个插件的明确来源和版本说明,别在这条路上耗,直接用第 4 章的exm.lsp,源码可控,不依赖任何第三方。
提示:无论用哪种工具,动手前先把原图另存一份。MINSERT 降级过程中如果
ENTMAKE失败,原实体已经被ENTDEL删掉,图元就找不回来了,这是没有后悔药的操作。
3.3 炸开之后的检查清单
块变成普通块之后,别急着EXPLODE完就收工。先做三件事:第一,AUDIT一下图纸,看有没有实体数据损坏;第二,检查块内图元的图层和颜色有没有跑偏,MINSERT 降级后属性继承偶尔会丢;第三,如果原图是阵列铺开的(比如一排 50 个相同图块),降级后它们会重叠在同一个插入点上,需要手动或写脚本按原间距重新排布。最后这点最容易被忽略——exm只负责把“阵列块”变成“单个块”,不负责还原视觉上的阵列布局,行列间距在归零那一刻就丢了。所以如果原图靠 MINSERT 铺了一整片,炸开前先记下44、45的值和70、71的数量,后面重新阵列时用得上。
4. 自己写 exm.lsp:从 ENTGET 到 ENTMAKE 的完整降级逻辑
这一章是整份文档里最值钱的部分,也是我最推荐自己维护一份的原因。文档给的源码是 2009 年前后的写法,能跑,但有几个地方按今天的习惯可以写得更稳。我先把原始逻辑拆开讲清楚,再给一版加了注释和边界处理的版本。
4.1 原始源码逐段拆解
核心函数C:exm的流程是:ENTSEL选块 →ENTGET取实体表 → 判断0组码是不是INSERT→ 取出70、71、44、45四个组码的关联项 → 用SUBST把它们替换成 0 → 把100子类从AcDbMInsertBlock换成AcDbBlockReference→ENTMAKE重建 →ENTDEL删旧 → 循环等下一次选择。SUBST是 LISP 的替换函数,签名是(subst 新项 旧项 表),返回替换后的新表,原表不变,所以每一步都得把结果接回去,源码里(setq e (subst ...))连写四次的道理就在这。
有个细节值得说:源码里(list 100 "AcDbMInsertBlock")和(list 100 "AcDbBlockReference")用的是LIST而不是CONS。因为100组码的值是字符串,CONS构造的是点对(100 . "xxx"),而ENTGET返回的关联表里字符串组码是以列表形式(100 "xxx")存在的,用CONS替换会类型不匹配,ENTMAKE直接失败。这是新手改这段代码最容易翻车的地方。
4.2 一版加了边界处理的 exm.lsp
原始代码有两个薄弱点:一是没判断选中的实体是不是真的 MINSERT,普通块点上去也会走一遍替换,虽然结果多半无害,但ENTMAKE可能因为组码冲突报错;二是while循环里第二次ENTSEL如果用户按了回车(返回 nil),car取 nil 得 nil,循环正常退出,这点没问题,但中间任何一步ENTMAKE失败都没有提示。下面这版补了类型判断和失败回显:
;; exm.lsp - 将多重插入块(MINSERT)降级为普通块,便于 EXPLODE ;; 用法:APPLOAD 加载后,命令行输入 EXM,点选目标块,回车结束 (defun c:exm ( / stm e stlx r70 c71 d44 d45 newE) (setvar "cmdecho" 0) (setq stm (car (entsel "\n点取多重插入块(回车结束): "))) (while stm (setq e (entget stm)) (setq stlx (cdr (assoc 0 e))) ;; 只处理 INSERT 类型,且子类必须是 AcDbMInsertBlock (if (and (= stlx "INSERT") (equal (cdr (assoc 100 (reverse e))) "AcDbMInsertBlock")) (progn (setq r70 (assoc 70 e) c71 (assoc 71 e) d44 (assoc 44 e) d45 (assoc 45 e)) ;; 行列数与间距归零 (setq e (subst (cons 70 0) r70 e)) (setq e (subst (cons 71 0) c71 e)) (setq e (subst (cons 44 0) d44 e)) (setq e (subst (cons 45 0) d45 e)) ;; 关键一步:子类标记降级 (setq e (subst (list 100 "AcDbBlockReference") (list 100 "AcDbMInsertBlock") e)) (setq newE (entmake e)) (if newE (entdel stm) (princ "\n重建实体失败,原块保留,请检查组码") ) ) (princ "\n选中的不是多重插入块,已跳过") ) (setq stm (car (entsel "\n点取下一多重插入块(回车结束): "))) ) (setvar "cmdecho" 1) (princ) )逻辑说明:(cdr (assoc 100 (reverse e)))这一句是为了取最后一个100组码。一个块参照的实体表里可能有多个100子类标记(比如AcDbEntity、AcDbBlockReference、AcDbMInsertBlock依次出现),ASSOC默认取第一个,REVERSE之后取到的才是最后那个、也就是真正标识实体类型的子类。参数上,70/71是列数/行数,44/45是列间距/行间距,全部归零是为了让降级后的块只保留一个插入点,避免视觉上还残留阵列。ENTMAKE的返回值是新实体的图元名,非 nil 才执行ENTDEL,这是防止重建失败把原图弄丢的保险。
4.3 lockb:反向把普通块变回多重插入块
文档里还给了lockb,作用相反:把普通块改成 MINSERT,用于自己给图纸加一层“防误改”。逻辑就是把上面四步反过来——70、71设成 1,100从AcDbBlockReference换成AcDbMInsertBlock。这里有个参数要留意:70、71设成 1 表示 1 行 1 列,视觉上还是一个块,但已经具备 MINSERT 身份,EXPLODE会失效。如果想让它在图上铺开,就把70、71设成想要的阵列数,同时给44、45赋间距值。我一般只设 1×1,纯粹当“防手滑分解”用,铺开阵列交给ARRAY命令,职责分开,后面维护清楚。
;; lockb.lsp - 将普通块转为多重插入块(1x1),使其无法被常规 EXPLODE (defun c:lockb ( / stm e stlx newE) (setvar "cmdecho" 0) (setq stm (car (entsel "\n点取普通块(回车结束): "))) (while stm (setq e (entget stm)) (setq stlx (cdr (assoc 0 e))) (if (and (= stlx "INSERT") (equal (cdr (assoc 100 (reverse e))) "AcDbBlockReference")) (progn (setq e (subst (cons 70 1) (assoc 70 e) e)) (setq e (subst (cons 71 1) (assoc 71 e) e)) (setq e (subst (list 100 "AcDbMInsertBlock") (list 100 "AcDbBlockReference") e)) (setq newE (entmake e)) (if newE (entdel stm) (princ "\n转换失败,原块保留")) ) (princ "\n选中的不是普通块,已跳过") ) (setq stm (car (entsel "\n点取下一普通块(回车结束): "))) ) (setvar "cmdecho" 1) (princ) )两个文件可以合并成一个.lsp,也可以分开加载,看个人习惯。合并的好处是一次APPLOAD两个命令都能用;分开的好处是给别人发的时候,只想给“解”不想给“锁”,选择更灵活。
5. 避坑与排查:五条血泪经验
这一章按“现象 → 原因 → 解决”写,都是我在实际图纸上撞过的。
现象一:APPLOAD加载成功,命令行敲exm提示“未知命令”。原因多半是文件里函数定义写成了(defun exm ()而不是(defun c:exm ()。AutoCAD 只把C:前缀的函数注册成命令行命令,少了这个前缀,函数存在但敲不出来。解决:打开 lsp 文件搜defun,确认每个要当命令用的函数都带c:。
现象二:ENTMAKE返回 nil,原块还在但没变化。常见原因是实体表里残留了 MINSERT 特有的组码,比如44、45没归零,或者100子类替换时用了CONS而不是LIST,导致字符串组码类型不对。解决:在ENTMAKE前加一句(princ e)把实体表打出来,对照第 2 章那张组码表逐项核对,重点看100是不是列表形式、44/45是不是 0。
现象三:炸开后块内图元位置整体偏移。原因是原 MINSERT 的插入点(组码10)和阵列基准点之间有关系,降级时只改了行列参数,没处理插入点。解决:降级前记下原块的10组码坐标,降级后用MOVE把新块对齐回原位;批量处理时在 lsp 里把10的值原样带过去,ENTMAKE会沿用,一般不会偏,偏的情况多出现在块定义本身带基点偏移的图上。
现象四:图纸来自外部,exm跑完AUDIT报一堆错误。这类图往往不止 MINSERT 一层,块里还嵌套了代理对象(由其他软件生成的自定义实体),ENTMAKE重建时无法完整复制代理对象的数据。解决:先EXPORTTOAUTOCAD或-EXPORTTOAUTOCAD把图转成纯 AutoCAD 格式,再跑exm;转格式会丢掉一部分智能对象属性,但底图几何能保住。
现象五:批量处理时脚本跑到一半卡住。多半是ENTSEL在无人交互环境下等输入。ENTSEL是交互函数,不能用于批处理。解决:批处理改用SSGET按过滤器选实体,过滤条件写(0 . "INSERT"),再遍历选择集逐个处理,把上面exm里的ENTSEL换成SSGET的遍历逻辑即可。
注意:
lockb加锁后的块,如果对方也用本文的方法解,一样能解开。MINSERT 从来不是真正的加密,它只是提高误操作门槛。真要保护图纸,得走数字签名或权限管理那一套,别把 MINSERT 当安全机制用。
6. 进阶:把单块处理改成批量脚本,以及一个验证习惯
单张图点选够用,但接手一个项目动辄几十张底图,逐张点选不现实。把exm改成批量版,核心是把ENTSEL换成SSGET加遍历,并且处理“选择集里混有普通块”的情况。下面这段可以直接存成exm-batch.lsp,加载后敲EXMB,它会处理当前图纸里所有 MINSERT,普通块自动跳过:
;; exm-batch.lsp - 批量降级当前图纸中所有多重插入块 (defun c:exmb ( / ss i ent e stlx cnt) (setvar "cmdecho" 0) (setq cnt 0) ;; 选择集过滤:只选 INSERT 类型 (setq ss (ssget "X" '((0 . "INSERT")))) (if ss (progn (setq i 0) (while (< i (sslength ss)) (setq ent (ssname ss i)) (setq e (entget ent)) ;; 只处理子类为 AcDbMInsertBlock 的实体 (if (equal (cdr (assoc 100 (reverse e))) "AcDbMInsertBlock") (progn (setq e (subst (cons 70 0) (assoc 70 e) e)) (setq e (subst (cons 71 0) (assoc 71 e) e)) (setq e (subst (cons 44 0) (assoc 44 e) e)) (setq e (subst (cons 45 0) (assoc 45 e) e)) (setq e (subst (list 100 "AcDbBlockReference") (list 100 "AcDbMInsertBlock") e)) (if (entmake e) (progn (entdel ent) (setq cnt (1+ cnt))) ) ) ) (setq i (1+ i)) ) (princ (strcat "\n共处理 " (itoa cnt) " 个多重插入块")) ) (princ "\n当前图纸没有块参照") ) (setvar "cmdecho" 1) (princ) )参数上,SSGET的第一个参数"X"表示全图范围选择,不带交互;过滤表'((0 . "INSERT"))只捞块参照,减少遍历量。cnt计数是为了跑完有个回显,知道到底改了几个——批量脚本最怕“以为跑了其实一个没匹配上”,有个数字心里有底。跑完仍然建议AUDIT一遍,批量操作出错的面比单块大。
验证这块我有个固定习惯:任何一次 MINSERT 降级之后,先选中处理过的块,看特性面板里“列数/行数”两项是不是消失了,再敲一次EXPLODE确认能分解,最后AUDIT看有没有实体错误。三步都过,才认为这张图干净了。这个习惯是从一次翻车里养成的——早年图省事,批量跑完直接归档,结果其中一张图的块定义本身损坏,ENTMAKE静默失败,原块被删、新块没建,整片图元凭空消失,只能从备份重来。从那以后我每次跑批量脚本前都强制另存一份,跑完必走这三步验证,再急也不省。希望帮到你。
本文还有配套的精品资源,点击获取