夜雨聆风学习资料网

ARTICLE · 1128622

实战|用 AI 写 CAD 插件:文字搜索统计(附源码 + 提示词)

实战|用 AI 写 CAD 插件:文字搜索统计(附源码 + 提示词)

点击蓝字,关注我们

这是《零基础 CAD 插件从 0 到 1》第 17 篇

小伙伴想让我做一个文字统计工具

确实有时候想统计点东西用cad的命令确实有点麻烦

比如:想数一数图上有多少个“门”、多少个“窗”,或者统计一下材料表里各种规格出现了多少次。手动找?眼睛看花不说,还容易漏。用 CAD 自带的查找?只能一个一个找,没法批量统计,更没法导出。

更烦的是,有些文字在块里面,有些是属性文字,还有些大小写不统一、前后带空格,统计起来简直要命。

今天咱们用 AI 写一个文字统计小助手,从根上解决“数文字”的麻烦:框选区域 → 自动统计 → 列表显示 → 支持搜索、复制、导出 CSV。走哪带哪,一键搞定

先看效果:打开窗口框选直接搜索

GIF1(打开窗口):输入 STEXT → 弹出文字统计窗口 → 上方是框选和选项,中间是结果表格,底部是复制导出按钮,整体清爽,不抢 CAD 焦点。

GIF2(框选统计):点“框选区域” → 窗口自动隐藏 → 在图纸上框选 → 窗口重新出现,列表里已经按数量从多到少排好了。

GIF3(搜索过滤):在搜索框输入关键词 → 列表实时过滤,支持“包含匹配”和“全词匹配”。

这个插件解决了什么问题?

针对文字统计的五大痛点,做了五个核心优化:

  1. 一键框选统计
    不用一个一个选,框选区域后自动提取所有文字,包括普通文字、多行文字、块里的属性文字,甚至嵌套块里的文字也能挖出来。

  2. 忽略大小写和空格
    勾选“忽略大小写”,Door 和 door 算一种;勾选“忽略首尾空格”,门 和 门 算一种。统计更准确。

  3. 结果自动排序
    按出现次数从多到少排列,一眼就能看出哪种文字最多。

  4. 搜索过滤
    结果多了不好找?搜索框输入关键词,支持“包含匹配”和“全词匹配”,瞬间筛选。

  5. 复制和导出
    一键复制到剪贴板,直接粘贴到 Excel;或者导出 CSV 文件,方便存档和汇报。

  6. 不抢焦点
    窗口打开后不抢 CAD 焦点,鼠标直接移到图纸上就能框选。框选时窗口自动隐藏,完成后自动显示,流程顺畅。

交互设计:从框选到数据导出

输入 STEXT 回车,弹出文字统计窗口。窗口清爽,不抢 CAD 焦点,鼠标可以直接移到图纸上操作。

点“框选区域”,窗口会自动隐藏,让你在图纸上框选。框选完成后,窗口重新出现,结果已经按数量从多到少排好,列在中间的表格里。表格有两列:文字内容、数量。

如果结果太多不好找,上面搜索框输入关键词,列表实时过滤。默认是“包含匹配”,勾上“全词匹配”就只显示完全一样的文字。搜索框旁边还会显示当前筛出来的项数和合计数量,比如“共 12 项,合计 48”。

觉得统计没问题了,底下按钮一点:复制结果,直接粘贴到 Excel;或者导出 CSV,带 BOM,Excel 打开不乱码。想重新框选,点“重新框选”就行,窗口不用关。

整个窗口从上到下就是:标题栏(能拖动)、框选和选项按钮、搜索栏、结果表格、底部操作按钮、状态提示。没有多余的东西,用起来顺手。

核心干货:给 AI 的精准提示词

还是那句话:用 AI 写插件写出来的功能不对,本质是需求没说细。

下面是实测多轮、输出稳定能用的提示词模板:

角色设定

你是资深 CAD 插件开发工程师,用 C# 和 WPF 做 AutoCAD 插件。

功能需求

开发一个「文字统计」插件,命令名 STEXT。

核心目标

解决“图纸文字统计麻烦”的问题。用户框选一个区域,自动统计区域内所有文字的出现次数,支持忽略大小写、忽略首尾空格,结果可以搜索、复制、导出。

界面

WPF 无边框圆角窗口,毛玻璃卡片风格:

  • 标题栏:应用图标 + “文字统计” + 最小化/关闭,可拖拽

  • 操作卡片:框选按钮 + 清空按钮 + 两个复选框(忽略大小写、忽略首尾空格)

  • 搜索栏:搜索框 + 全词匹配 + 统计数量显示

  • 结果列表:表格,两列——文字内容、数量

  • 底部按钮:复制结果 / 导出 CSV / 重新框选

  • 状态栏:显示统计结果提示

功能要求

窗口行为

  • 打开后不抢 CAD 焦点,鼠标能直接到图纸上操作

  • 框选时先隐藏窗口,框选完成后自动显示

  • 关闭窗口后恢复 CAD 焦点

文字统计

  • 用户框选区域后,提取区域内所有文字

  • 包括普通文字、多行文字、块属性文字,块内嵌套文字也要统计

  • 相同文字合并计数,按数量从多到少排序

  • 支持忽略大小写、忽略首尾空格

搜索过滤

  • 输入关键词实时过滤列表

  • 支持“包含匹配”和“全词匹配”

  • 显示过滤后的项数和合计数量

复制和导出

  • 复制结果:文字内容 + 数量,制表符分隔,可直接粘贴 Excel

  • 导出 CSV:带 BOM,Excel 打开不乱码

环境

.NET Framework 4.8,用 AcMgd、AcDbMgd、AcCoreMgd 三个引用。

输出要求

完整可编译的代码,分几个文件,关键逻辑加中文注释,说明怎么编译、怎么在 CAD 里加载。

写提示词的核心原则

  1. 先定身份先让 AI 进入专业 CAD 开发的角色,避免用错 API、写出行外代码。

  2. 再划边界明确命令名、CAD 版本、运行环境,从根源减少兼容问题和报错。

  3. 讲清规则怎么保存、怎么注入、怎么设为当前,每一步规则都说死,不给 AI 自由发挥的空间。

  4. 固定格式要完整代码、要注释、要部署说明,都明确提出来,输出直接能用。

照着这个结构写,AI 输出代码的可用率至少提升 80%。

核心源码分享

下面是核心实现代码,基于 C# + CAD .NET API 编写

using System;using System.Collections.Generic;using System.Linq;using System.Windows.Controls;using Autodesk.AutoCAD.ApplicationServices;using Autodesk.AutoCAD.DatabaseServices;using Autodesk.AutoCAD.EditorInput;namespace SheetCount{    ///<summary>    /// 文字统计核心    ///</summary>    public static class TextCounter    {        ///<summary>        /// 框选,然后统计区域内所有文字        ///</summary>        publicstatic List<CountRow> Run(Document doc, bool ignoreCase, bool trim)        {            // 1. 让用户框选            var pso = new PromptSelectionOptions();            pso.MessageForAdding = "\n请框选要统计的区域: ";            var psr = doc.Editor.GetSelection(pso);            if (psr.Status != PromptStatus.OK) return null;            ObjectId[] ids = psr.Value.GetObjectIds();            if (ids.Length == 0) return new List<CountRow>();            // 2. 统计            var counter = new Dictionary<string, int>(StringComparer.Ordinal);            var displayName = new Dictionary<string, string>(StringComparer.Ordinal);            using (doc.LockDocument())            using (var tr = doc.Database.TransactionManager.StartTransaction())            {                foreach (ObjectId id in ids)                {                    var ent = tr.GetObject(id, OpenMode.ForRead) as Entity;                    if (ent == null) continue;                    // 直接实体                    ExtractFromEntity(ent, tr, counter, displayName, ignoreCase, trim);                    // 块参照 → 递归取块内文字                    var br = ent as BlockReference;                    if (br != null)                    {                        ExtractFromBlockRef(br, tr, counter, displayName,                                            ignoreCase, trim, new HashSet<ObjectId>());                    }                }                tr.Commit();            }            // 3. 转成列表并按数量降序            var result = new List<CountRow>();            foreach (var kv in counter)            {                result.Add(new CountRow                {                    Text = displayName[kv.Key],                    Count = kv.Value                });            }            return result.OrderByDescending(r => r.Count)                         .ThenBy(r => r.Text)                         .ToList();        }        // =====================================================        // 从一个实体里提取文字        // =====================================================        privatestaticvoidExtractFromEntity(            Entity ent,            Transaction tr,            Dictionary<string, int> counter,            Dictionary<string, string> displayName,            bool ignoreCase,            bool trim)        {            string content = null;            var dbText = ent as DBText;            if (dbText != null)            {                content = dbText.TextString;            }            else            {                var mText = ent as MText;                if (mText != null)                {                    content = mText.Contents;                    // 去掉多行文字的格式代码                    content = mText.Text;                }                else                {                    var attr = ent as AttributeReference;                    if (attr != null)                    {                        content = attr.TextString;                    }                }            }            if (string.IsNullOrEmpty(content)) return;            AddToCounter(content, counter, displayName, ignoreCase, trim);        }        // =====================================================        // 递归统计块参照里的文字        // =====================================================        privatestaticvoidExtractFromBlockRef(            BlockReference br,            Transaction tr,            Dictionary<string, int> counter,            Dictionary<string, string> displayName,            bool ignoreCase,            bool trim,            HashSet<ObjectId> visited)        {            // 防止循环嵌套的块            if (br.BlockTableRecord == ObjectId.Null) return;            if (visited.Contains(br.BlockTableRecord)) return;            visited.Add(br.BlockTableRecord);            // 块属性            foreach (ObjectId attId in br.AttributeCollection)            {                var att = tr.GetObject(attId, OpenMode.ForRead) as AttributeReference;                if (att == null) continue;                if (string.IsNullOrEmpty(att.TextString)) continue;                AddToCounter(att.TextString, counter, displayName, ignoreCase, trim);            }            // 块定义内部的实体            var btr = tr.GetObject(br.BlockTableRecord, OpenMode.ForRead) as BlockTableRecord;            if (btr == null) return;            foreach (ObjectId eid in btr)            {                var inner = tr.GetObject(eid, OpenMode.ForRead) as Entity;                if (inner == null) continue;                ExtractFromEntity(inner, tr, counter, displayName, ignoreCase, trim);                // 嵌套块                var innerBr = inner as BlockReference;                if (innerBr != null)                {                    ExtractFromBlockRef(innerBr, tr, counter, displayName,                                        ignoreCase, trim, visited);                }            }        }        // =====================================================        // 把一条文字加进统计表        // =====================================================        privatestaticvoidAddToCounter(            string content,            Dictionary<string, int> counter,            Dictionary<string, string> displayName,            bool ignoreCase,            bool trim)        {            string display = content;            if (trim) display = display.Trim();            if (string.IsNullOrEmpty(display)) return;            string key = ignoreCase ? display.ToUpperInvariant() : display;            int c;            if (counter.TryGetValue(key, out c))            {                counter[key] = c + 1;            }            else            {                counter[key] = 1;                displayName[key] = display;            }        }    }}

用 AI 开发插件的完整流程

需求拆解
把「文字统计」拆成五个模块:窗口框架 → 框选获取文字 → 统计计数 → 列表显示与搜索 → 复制导出。逐个让 AI 实现。

分步验证
先做“能框选并读出文字”,再做“能统计并排序”,最后做“搜索、复制、导出”。每一步跑通再往下,出问题好定位。

实测优化
拿不同 CAD 版本测试:2021、2024 都有细微差异。踩的坑包括:

  • 块内文字提取不全,后来加了递归遍历块定义;

  • 忽略大小写时,显示的文字还是原始大小写,但统计键统一转大写;

  • 框选时窗口隐藏后,CAD 没有及时获得焦点,加了延时和激活;

  • 导出 CSV 时没加 BOM,Excel 打开中文乱码。

细节调整
窗口大小、按钮颜色、默认勾选状态、列表行高,都可以根据自己的操作习惯改。

成品领取 & 下期预告

成品插件直接领

不想自己编译、调代码的朋友,关注公众号 「下班让AI教我做插件」,后台回复 「文字统计」,直接领取编译好的 DLL 插件文件,NETLOAD 加载或者是使用我写的dll插件加载工具箱。 完整工程源码拿到手就能二次修改。

下期预告

下一篇咱们继续图纸提效工具,如果有想要的插件可以在评论区留言,我看看哪个留言最多就做哪个。

我是做插件的设计师,专注用 AI 提高 CAD 设计效率。关注我,下班一起做插件,把重复工作交给程序。

声明

本插件完全免费分享,仅供交流学习使用。严禁任何人将其用于商业倒卖或牟利。如需转载,请注明出处。请大家尊重作者的劳动成果,共同维护良好的交流环境。

E

N

D

相关学习资料