ARTICLE · 1090098
实战|用 AI 写 CAD 插件:设置标注(附源码 + 提示词)

点击蓝字,关注我们
这是《零基础 CAD 插件从 0 到 1》第 10 篇
做全屋定制的设计师肯定都被标注样式折腾过:CAD 的标注样式是跟图纸走的,新建一张空白图,样式栏里空空如也,又得重新建一遍标准 —— 字高、箭头、偏移、精度挨个调,调完一套完整样式又得七八分钟。公司有统一出图标准,要么靠模板文件,要么别的图里复制,最后调出来的样式五花八门,说白了,CAD 原生就做不到「一次设好,所有新图通用」。
今天我们就用 AI 写一个真正跨图纸的标注样式插件:一次把你的标准样式存好,以后不管新建多少空白图纸,一键注入样式,输入命令设为当前默认。不用存模板、不用复制粘贴、不用找别的图复制,新图打开直接用,画出来的标注永远统一标准。
先看效果:一次保存,永久通用
分两个核心场景看实际操作:

首次使用先叫你设置样式

打开新的图纸可以看到也是有之前的样式的
不用每次开新图都重建样式,不用记参数,不用来回切换图纸复制。第一次花 10 秒拾取保存,后面所有新图一键生效,标注样式永远统一,图纸干干净净没有多余东西。
这个插件解决了什么问题?
针对标注样式反复设置、跨图复用麻烦的痛点,做了四个核心优化:
样式跨图纸永久保存把完整的标注样式参数存在本地配置文件,不跟图纸走。一次保存,换图、重启 CAD 都能复用,真正的一次设置永久使用。
新图一键注入样式新建空白图纸,执行命令自动把标准样式创建到当前图里,直接设为当前标注样式。不用手动新建、不用从别的图复制,图里干干净净只有你要的标准样式。
批量套用一键统一选中图里所有标注,一键统一成标准样式,旧图、外来图几秒规范完,比格式刷效率高几十倍。
自动设为当前默认注入样式后自动设为当前默认,接下来画的标注自动套用标准,不用手动去样式栏切换。
交互设计:两种场景,都只要几步
完全符合日常操作习惯,不用学习复杂操作:
首次配置(只做一次)
打开一张带有目标样式的图纸
输入 KJDim 回车,选择「拾取当前样式保存为标准」
选择要保存的样式名,确认后自动永久保存
日常使用(所有图纸通用)
新建 / 打开任意图纸
输入 KJDim 回车
自动注入标准样式并设为当前;如果提前选中标注则批量套用
核心干货:给 AI 的精准提示词
还是那句话
用 AI 写插件,写出来的功能不对,本质是需求没说细。AI 就像助理,你说清楚规则,它才能做准。
下面是实测多轮、输出稳定能用的提示词模板,直接复制就能用:
角色设定 :你是资深 CAD .NET 二次开发工程师,精通 C# 与 CAD 原生 API,面向全屋定制设计场景开发插件。 功能需求 :开发一个「跨图纸标注样式管理器」插件,具体要求: 命令名:KJDim 核心逻辑 :带本地配置记忆功能,标注样式参数保存在本地 ini 文件,跨图纸永久通用;支持从当前图纸已有样式保存为标准 首次流程 :启动命令后检测本地配置,若不存在则提示用户从当前图纸选择一个已有标注样式,读取该样式完整参数并保存到本地配置文件 日常流程 :检测到配置文件存在,则读取参数;检查当前图纸是否存在同名样式,不存在则按参数创建该标注样式;创建后自动设为当前文档的默认标注样式 批量套用:用户点击按钮选中标注对象,则将选中的所有标注批量应用该标准样式 环境:.NET Framework 4.8,引用 AcMgd、AcDbMgd;配置文件保存在插件同目录 界面要求使用wpf 输出要求: 给出完整可复制编译的代码 关键逻辑加中文注释 说明编译和加载方法 |
写提示词的核心原则
先定身份先让 AI 进入专业 CAD 开发的角色,避免用错 API、写出行外代码。
再划边界明确命令名、CAD 版本、运行环境,从根源减少兼容问题和报错。
讲清规则怎么保存、怎么注入、怎么设为当前,每一步规则都说死,不给 AI 自由发挥的空间。
固定格式要完整代码、要注释、要部署说明,都明确提出来,输出直接能用。
照着这个结构写,AI 输出代码的可用率至少提升 80%。
核心源码分享
下面是核心实现代码,基于 C# +CAD .NET API 编写,样式参数本地存储,跨图纸通用,新图自动注入并设为当前。
命令主逻辑:样式拾取替换 + 新图注入 + 设为当前 + 批量套用
using System;using System.Globalization;using System.Linq;using Autodesk.AutoCAD.ApplicationServices;using Autodesk.AutoCAD.DatabaseServices;using Autodesk.AutoCAD.EditorInput;using Autodesk.AutoCAD.Runtime;[assembly: CommandClass(typeof(KJDimPlugin.KJDimCommand))]namespaceKJDimPlugin{publicclassKJDimCommand{privatestring _pendingStyleName;// ═══════════════════════════════════════════════════════════// 主命令// ═══════════════════════════════════════════════════════════[CommandMethod("KJDim")]publicvoidExecuteKJDim(){var doc = Application.DocumentManager.MdiActiveDocument;if (doc == null) return;_pendingStyleName = null;try{if (!IniConfigManager.ConfigExists()){if (!RunStyleConfigFlow(doc, isFirstTime: true)) return;if (_pendingStyleName != null) return;}else{var parameters = DimStyleService.LoadParamsFromIni();if (parameters.Count == 0){doc.Editor.WriteMessage("\n配置文件损坏,重新初始化...\n");IniConfigManager.DeleteConfig();if (!RunStyleConfigFlow(doc, isFirstTime: true)) return;if (_pendingStyleName != null) return;}else{string std = DimStyleService.EnsureDimStyle(doc.Database, parameters);doc.Editor.WriteMessage($"\n标准样式 \"{std}\" 已就绪并置为当前。\n");}}RunDailyLoop(doc);}catch (System.Exception ex){doc.Editor.WriteMessage($"\n运行错误:{ex.Message}\n");}}// ═══════════════════════════════════════════════════════════// 首次配置流程// ═══════════════════════════════════════════════════════════privateboolRunStyleConfigFlow(Document doc, bool isFirstTime){var db = doc.Database;var ed = doc.Editor;var firstWin = new FirstTimeWindow(isFirstTime);var r1 = Application.ShowModalWindow(firstWin);if (r1 != true) returnfalse;if (firstWin.Action == FirstTimeAction.CreateNew){string newStyle = DoCreateNewStyle(doc);if (string.IsNullOrEmpty(newStyle)) returnfalse;returntrue;}elseif (firstWin.Action == FirstTimeAction.UseExisting){string chosen = DoPickExistingStyle(doc);if (string.IsNullOrEmpty(chosen)) returnfalse;var parameters = DimStyleService.ReadDimStyleParams(db, chosen);if (parameters == null) returnfalse;DimStyleService.SaveParamsToIni(parameters);DimStyleService.EnsureDimStyle(db, parameters);ed.WriteMessage($"\n标准样式 \"{chosen}\" 已保存并置为当前。\n" +$"配置文件:{IniConfigManager.GetConfigPath()}\n");returntrue;}returnfalse;}// ═══════════════════════════════════════════════════════════// 新建样式// ═══════════════════════════════════════════════════════════privatestringDoCreateNewStyle(Document doc){var db = doc.Database;var ed = doc.Editor;var nameWin = new NameInputWindow();var nr = Application.ShowModalWindow(nameWin);if (nr != true) returnnull;string newName = nameWin.StyleName;double dimScale = nameWin.DimScale;if (DimStyleService.GetAllDimStyleNames(db).Contains(newName)){ed.WriteMessage($"\n样式 \"{newName}\" 已存在,请换一个名称。\n");returnnull;}if (!CreateStyleFromDefaultTemplate(db, newName, dimScale)){ed.WriteMessage($"\n创建样式 \"{newName}\" 失败。\n");returnnull;}_pendingStyleName = newName;doc.CommandEnded -= OnDimStyleCommandEnded;doc.CommandEnded += OnDimStyleCommandEnded;ed.WriteMessage($"\n已创建样式 \"{newName}\"(比例 1:{dimScale:0.##})并置为当前。\n" +$"正在打开【标注样式管理器】...\n");doc.SendStringToExecute("_.DIMSTYLE\n", true, false, false);return newName;}privatevoidOnDimStyleCommandEnded(object sender, CommandEventArgs e){if (!string.Equals(e.GlobalCommandName, "DIMSTYLE",StringComparison.OrdinalIgnoreCase)) return;var doc = sender as Document;if (doc == null) return;doc.CommandEnded -= OnDimStyleCommandEnded;string styleName = _pendingStyleName;_pendingStyleName = null;if (string.IsNullOrEmpty(styleName)) return;var db = doc.Database;var ed = doc.Editor;try{var parameters = DimStyleService.ReadDimStyleParams(db, styleName);if (parameters == null) return;DimStyleService.SaveParamsToIni(parameters);DimStyleService.EnsureDimStyle(db, parameters);ed.WriteMessage($"\n标准样式 \"{styleName}\" 已保存并置为当前。\n" +$"配置文件:{IniConfigManager.GetConfigPath()}\n");RunDailyLoop(doc);}catch (System.Exception ex){ed.WriteMessage($"\n保存参数失败:{ex.Message}\n");}}// ═══════════════════════════════════════════════════════════// 选取已有样式// ═══════════════════════════════════════════════════════════privatestringDoPickExistingStyle(Document doc){var db = doc.Database;var ed = doc.Editor;var items = DimStyleService.GetAllDimStyleItems(db);if (items.Count == 0){ed.WriteMessage("\n当前图纸中没有标注样式。\n");returnnull;}var pickWin = new StylePickWindow(items);var pr = Application.ShowModalWindow(pickWin);if (pr != true) returnnull;string chosen = pickWin.SelectedStyleName;using (var tr = db.TransactionManager.StartTransaction()){var table = (DimStyleTable)tr.GetObject(db.DimStyleTableId, OpenMode.ForRead);if (table.Has(chosen)){ObjectId sid = table[chosen];db.Dimstyle = sid;var rec = (DimStyleTableRecord)tr.GetObject(sid, OpenMode.ForRead);db.SetDimstyleData(rec);}tr.Commit();}return chosen;}// ═══════════════════════════════════════════════════════════// 从系统模板创建// ═══════════════════════════════════════════════════════════privateboolCreateStyleFromDefaultTemplate(Database db, string newName, double dimScale){using (var tr = db.TransactionManager.StartTransaction()){var table = (DimStyleTable)tr.GetObject(db.DimStyleTableId, OpenMode.ForRead);if (table.Has(newName)) { tr.Commit(); returnfalse; }table.UpgradeOpen();var newRecord = new DimStyleTableRecord();string baseName = (db.Measurement == MeasurementValue.English)? "STANDARD" : "ISO-25";if (table.Has(baseName)){var baseRec = (DimStyleTableRecord)tr.GetObject(table[baseName], OpenMode.ForRead);newRecord.CopyFrom(baseRec);}newRecord.Name = newName;newRecord.Dimscale = dimScale;if (table.Has(newRecord.Name)) { tr.Commit(); returnfalse; }ObjectId id = table.Add(newRecord);tr.AddNewlyCreatedDBObject(newRecord, true);db.Dimstyle = id;db.SetDimstyleData(newRecord);tr.Commit();}returntrue;}// ═══════════════════════════════════════════════════════════// 日常循环// ═══════════════════════════════════════════════════════════privatevoidRunDailyLoop(Document doc){while (true){var db = doc.Database;var ed = doc.Editor;string std = IniConfigManager.ReadValue("Name", "KJ_STD");double stdScale = 1.0;double.TryParse(IniConfigManager.ReadValue("Dimscale", "1"),NumberStyles.Any, CultureInfo.InvariantCulture, out stdScale);if (stdScale <= 0) stdScale = 1.0;var allStyles = DimStyleService.GetAllDimStyleItems(db);var win = new DailyUseWindow(std, stdScale, allStyles);var res = Application.ShowModalWindow(win);if (res != true) return;switch (win.Action){case DailyAction.ReplaceAll:ReplaceAllDimensions(doc, win.SourceStyle, win.TargetStyle);return;case DailyAction.ReplaceSelection:ReplaceSelectionInteractive(doc, win.TargetStyle);return;case DailyAction.CreateNewStyle:DoCreateNewStyle(doc);return;case DailyAction.ChangeStandardStyle:ChangeStandardStyle(doc);break;}}}// ═══════════════════════════════════════════════════════════// 更换标准样式(直接弹样式列表)// ═══════════════════════════════════════════════════════════privatevoidChangeStandardStyle(Document doc){var db = doc.Database;var ed = doc.Editor;var items = DimStyleService.GetAllDimStyleItems(db);if (items.Count == 0) return;var pickWin = new StylePickWindow(items);var pr = Application.ShowModalWindow(pickWin);if (pr != true) return;string chosen = pickWin.SelectedStyleName;var p = DimStyleService.ReadDimStyleParams(db, chosen);if (p == null) return;DimStyleService.SaveParamsToIni(p);DimStyleService.EnsureDimStyle(db, p);ed.WriteMessage($"\n标准样式已更换为 \"{chosen}\"。\n");}// ═══════════════════════════════════════════════════════════// 全图替换// ═══════════════════════════════════════════════════════════privatevoidReplaceAllDimensions(Document doc, string sourceStyle, string targetStyle){var db = doc.Database;var ed = doc.Editor;int count = 0;using (var tr = db.TransactionManager.StartTransaction()){var dimTable = (DimStyleTable)tr.GetObject(db.DimStyleTableId, OpenMode.ForRead);if (!dimTable.Has(sourceStyle) || !dimTable.Has(targetStyle)){ed.WriteMessage("\n源样式或目标样式在当前图纸中不存在。\n");tr.Commit();return;}ObjectId srcId = dimTable[sourceStyle];ObjectId tgtId = dimTable[targetStyle];var bt = (BlockTable)tr.GetObject(db.BlockTableId, OpenMode.ForRead);foreach (ObjectId btrId in bt){var btr = (BlockTableRecord)tr.GetObject(btrId, OpenMode.ForRead);foreach (ObjectId entId in btr){var ent = tr.GetObject(entId, OpenMode.ForRead);if (ent is Dimension dim && dim.DimensionStyle == srcId){dim.UpgradeOpen();dim.DimensionStyle = tgtId;count++;}}}tr.Commit();}ed.WriteMessage($"\n已将 {count} 个标注从 \"{sourceStyle}\" 替换为 \"{targetStyle}\"。\n");}// ═══════════════════════════════════════════════════════════// 交互式选择 + 替换// ═══════════════════════════════════════════════════════════privatevoidReplaceSelectionInteractive(Document doc, string targetStyle){var db = doc.Database;var ed = doc.Editor;var opt = new PromptSelectionOptions{MessageForAdding = "\n请选择要替换的标注对象:",AllowDuplicates = false};var res = ed.GetSelection(opt);if (res.Status != PromptStatus.OK){ed.WriteMessage("\n未选择任何对象,操作取消。\n");return;}int count = 0;using (var tr = db.TransactionManager.StartTransaction()){var dimTable = (DimStyleTable)tr.GetObject(db.DimStyleTableId, OpenMode.ForRead);if (!dimTable.Has(targetStyle)){ed.WriteMessage($"\n目标样式 \"{targetStyle}\" 在当前图纸中不存在。\n");tr.Commit();return;}ObjectId tgtId = dimTable[targetStyle];foreach (SelectedObject so in res.Value){if (so == null) continue;var ent = tr.GetObject(so.ObjectId, OpenMode.ForRead);if (ent is Dimension dim){dim.UpgradeOpen();dim.DimensionStyle = tgtId;count++;}}tr.Commit();}ed.WriteMessage($"\n已将选中的 {count} 个标注替换为 \"{targetStyle}\"。\n");}}}
说明:配置文件保存在插件同目录的KJDimStyle.ini,想更换标准样式删除配置文件重新拾取即可;代码默认包含文字高度、箭头大小、偏移、精度等核心参数,可根据出图标准自行扩展更多样式参数。
用 AI 开发插件的完整流程
这类带记忆功能的工具,用 AI 分步开发,上手最快:
需求拆解把「跨图纸标注样式」拆成「配置读写→样式选择→注入创建→设为当前」四个模块,逐个让 AI 实现,不要一次性丢完整需求。
分步验证先写配置读写逻辑,测好保存和读取;再加样式选择和创建逻辑,测准参数还原;最后加设为当前和批量套用逻辑。每一步跑通再往下,出问题好定位。
实测优化拿不同版本的图纸测试:比如旧版本 CAD 的图、带自定义箭头的图,看样式注入是否完整;把遇到的问题丢给 AI,让它优化参数兼容。
细节调整比如默认样式名、包含哪些参数、是否自动建图层,都可以根据自己的公司标准改,让 AI 直接调参数就行。
成品领取 & 下期预告
成品插件直接领
如果不想自己折腾的小伙伴,可以后台回复 「标注设置」,直接领取编译好的 DLL 插件文件,使用NETLOAD 加载或是用我开发的dll插件加载工具。完整工程源码 ,拿到手就能二次修改。
下期预告
下一篇咱们继续深化标注提效工具,如果有想要的插件可以在评论区留言,我看看哪个留言最多就做哪个。
我是做插件的设计师,专注用 AI 提高 CAD 设计。关注我,下班一起做插件,把重复工作交给程序。

E
N
D