夜雨聆风学习资料网

ARTICLE · 1227010

实战|用 AI 写 CAD 插件:一键画柜子门板(附源码 + 提示词)

实战|用 AI 写 CAD 插件:一键画柜子门板(附源码 + 提示词)
点击蓝字,关注我们
这是《零基础 CAD 插件从 0 到 1》第 27 篇

为什么要写这个插件──────────────────────────────────────────────────────────────────────────────────────────────────── 

干全屋定制的同行都懂,画施工图的时候,画门板简直能让人崩溃:

得拿计算器算尺寸、得手动一根根偏移、得画左开右开的虚线、还得挨个标上“反弹器”、“免拉手”的文字。

好不容易画完一套图,甲方轻飘飘来一句:“帮我改一下”。你只能默默把线全炸开,重新偏移,结果尺寸全乱,标注全飞。改一次尺寸,中间的工艺文字还得重新挪一遍,简直是精神折磨。这时候你该如何应对。

每天处理完这些繁琐、机械又极度消耗耐心的重复工作,今天我决定不再忍了。我要把时间留给真正的设计,而不是把自己变成人肉计算器。所以,我直接给自己写个画门板的插件,用 AI 提升一下效率,早点下班!

插件效果────────────────────────────────────────────────────────────────────────────────────────────────────

插件界面

命令 DOORPANEL 回车,在框内随便点一下(或者输入 C 对角框选),瞬间弹出一个非常有现代感的操作窗口。

上边是参数控制,下边是极其直观的实时预览画布。

你想改门板数量?直接输数字,下边瞬间重新排布。
你想加缝隙?勾选一下,上下左右中,五种缝隙全部支持独立输入,门板自动缩小扣掉缝隙。

最爽的是它的纯图形交互:
      尺寸直接点:上方标着两层尺寸,外圈是标称尺寸(比如400),里面浅灰色小字是扣完缝隙的净尺寸(比如397.5)。直接点标称数字,输入新值,剩下没改的门板自动重新均分。
     开向直接点:在门板上点一下,左开、右开、上翻、下翻直接循环切,虚线和对角线跟着瞬间翻转。
     奇数门位置直接切:如果对开门是奇数(比如5扇),画布最左边和最右边会悬浮两个极简的箭头,点左边就是 < < > < >,点右边就是 < > < > >,排版随心所欲。
     文字随便加:在左侧选好预设的“反弹器”或者自己打字,直接白底黑字居中显示。选中某一块门板,还能单独给它改成“下延伸10”。

调好之后点“确认生成”,CAD 里瞬间生成带对应图层、颜色、线型的门板和开向虚线。

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

从底层逻辑来说,它跨越了三个“深坑”:

1标称尺寸与物理尺寸的分离

这是全屋定制最痛的点。设计师脑子里想的是 400 一扇门(标称),但实际上缝隙一扣,实物只有 397.5(物理)。
插件必须在后台做双轨计算:用户输入的、界面上方显示的、被点击修改的,永远是“标称尺寸”。而实际画在 CAD 里的矩形,是精确扣除了所有缝隙的“物理尺寸”。只要哪个程序员把这两个概念搞混,门板就会互相打架重叠。

2超丝滑的双向实时交互

很多插件一改数字,要点一下“刷新”才能看到结果,极其反人类。
为了实现输入 1 秒后图面就变,必须要把底层数据结构、界面的输入框、右侧的画布彻底绑定。无论你拖动滚动条、切换策略、还是点选单块门板单独改字,整个底层模型都在瞬间重算,并直接反映在屏幕上,没有任何卡顿。

3CAD 实体生成的容错与居中

在界面上画得漂漂亮亮,一旦“确认生成”到 CAD 里,如果图层没建好、或者中心文字对齐方式用错了,整个命令直接崩溃,什么都不会生成。
所以我们在后台默默建好了专属图层,把文字换成了极度稳定的多行文字格式,并且根据门板的高宽自动算好合适的字高,保证它永远死死咬在门板正中心。

核心干货:给AI的提示词────────────────────────────────────────────────────────────────────────────────────────────────────

要让 AI 做出这种逻辑严密、交互丝滑的工具,提示词得一步步“诱导”。

立项目标

我跟 AI 说,我要做一个衣柜门板设计插件,需求如下:

弹出一个左右分栏的窗口。左边调参数,右边实时预览。
支持设置总宽高、门板数量、以及五种缝隙(上下左右中)。
界面上要有两层尺寸线,标称的在上,扣缝的在下。直接点击标称数字就能改尺寸,剩下的门板自动均分。
门板中间要能写字,有下拉预设,也能自定义,每块板子要能单独改。
最核心:画布上的门板如果点击文字,可以循环切换左右上下开向。

解决偶数和奇数的排版算法

第一版做出来,偶数对开门没问题,但奇数门排版乱套了。

我跟 AI 说:

“现在的开向逻辑不对。如果是偶数扇对开,一定是 < > < >。但如果是奇数扇对开,用户需要在界面上选择单开门靠左还是靠右。靠右就是 < > < > >,靠左就是 < < > < >。请把单开门的位置作为全局变量,同时在画布的最左侧和最右侧加悬浮的极小按钮来控制。”

解决尺寸扣缝的算法冲突

做到中途,发现点了尺寸修改后,明明没改数字,门板却变固定尺寸了,缝隙也被算错。

我给 AI 的反馈是:

“用户输入的尺寸是包含缝隙的标称尺寸。如果用户输入的值等于均分值,就不能标记为固定。另外,计算物理净宽时,必须用标称宽扣除中缝的一半。一定要把‘计算模型’和‘绘制模型’分开,界面只认标称,画图只用物理宽。”

写提示词的核心原则────────────────────────────────────────────────────────────────────────────────────────────────────

做这种设计工具,不要一上来就让 AI 写全套:

讲清底层逻辑:明确告诉 AI “界面输入的是含缝尺寸,画图要用扣缝尺寸”,这两者必须分离,AI 就能帮你避开大坑。

描述真实操作:把“点击文字就能变左开右开”直接翻译成操作步骤,AI 解决交互冲突的能力比人类强。

重反馈:遇到文字把虚线挡住了、悬浮按钮太丑或者位置不对,直接截图发给 AI,让它调 CSS 或者坐标,效率极高。

核心源码分享────────────────────────────────────────────────────────────────────────────────────────────────────

这是分享的部分核心逻辑代码,完整代码看文章底部

CSHARP
✏️ 编辑

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

134

135

136

137

138

139

140

141

142

143

144

145

146

147

148

149

150

151

152

153

154

155

156

157

158

159

160

using System;

using Autodesk.AutoCAD.ApplicationServices;

using Autodesk.AutoCAD.DatabaseServices;

using Autodesk.AutoCAD.EditorInput;

using Autodesk.AutoCAD.Geometry;

using Autodesk.AutoCAD.Runtime;

using DoorPanelPlugin.Cad;

using DoorPanelPlugin.UI;

using AcApp = Autodesk.AutoCAD.ApplicationServices.Application;

namespaceDoorPanelPlugin

{

publicclassCommands

    {

        [CommandMethod("DOORPANEL")]

publicvoidDoorPanelCommand()

        {

var doc = AcApp.DocumentManager.MdiActiveDocument;

if (doc == null) return;

var ed = doc.Editor;

// 1. 提示点击内部(默认)或输入 C 切换对角框选

var ppo = new PromptPointOptions("\n请在衣柜框架内部点击一点 或 [对角框选(C)]: ");

            ppo.Keywords.Add("C", "C", "对角框选(C)");

            ppo.AllowNone = false;

var ppr = ed.GetPoint(ppo);

if (ppr.Status == PromptStatus.Cancel) return;

            Extents3d ext = default;

bool validExtents = false;

if (ppr.Status == PromptStatus.Keyword && ppr.StringResult == "C")

            {

var ppr1 = ed.GetPoint("\n请指定区域的左下角点: ");

if (ppr1.Status != PromptStatus.OK) return;

var ppr2 = ed.GetCorner("\n请指定区域的右上角点(对角): ", ppr1.Value);

if (ppr2.Status != PromptStatus.OK) return;

var minPt = new Point3d(

                    Math.Min(ppr1.Value.X, ppr2.Value.X),

                    Math.Min(ppr1.Value.Y, ppr2.Value.Y),

                    Math.Min(ppr1.Value.Z, ppr2.Value.Z));

var maxPt = new Point3d(

                    Math.Max(ppr1.Value.X, ppr2.Value.X),

                    Math.Max(ppr1.Value.Y, ppr2.Value.Y),

                    Math.Max(ppr1.Value.Z, ppr2.Value.Z));

                ext = new Extents3d(minPt, maxPt);

                validExtents = true;

            }

elseif (ppr.Status == PromptStatus.OK)

            {

                validExtents = TryGetBoundaryExtents(doc, ppr.Value, out ext);

if (!validExtents)

                {

                    ed.WriteMessage("\n未识别到封闭边界,请确认点击点在框架内部,或按 F2 查看提示。");

return;

                }

            }

if (!validExtents) return;

var vm = new DoorLayoutParams

            {

                TotalWidth = Math.Abs(ext.MaxPoint.X - ext.MinPoint.X),

                Height = Math.Abs(ext.MaxPoint.Y - ext.MinPoint.Y)

            };

            vm.Recalc();

if (vm.TotalWidth <= 1e-6 || vm.Height <= 1e-6)

            {

                ed.WriteMessage("\n包围盒尺寸为 0,无法生成门板。");

return;

            }

var win = new DoorWindow { DataContext = vm };

bool? result = AcApp.ShowModalWindow(win);

if (result != true) return;

// 原点 = 包围盒左下角

var origin = new Point3d(ext.MinPoint.X, ext.MinPoint.Y, ext.MinPoint.Z);

try

            {

using (var tr = doc.TransactionManager.StartTransaction())

                {

                    PanelCommitter.Commit(doc.Database, tr, vm, origin);

                    tr.Commit();

                }

                ed.WriteMessage("\n已成功生成 {0} 块门板。", vm.Panels.Count);

            }

catch (System.Exception ex)

            {

// ★ 如果内部有报错,能直接在 CAD 命令行输出

                ed.WriteMessage("\n生成门板失败: " + ex.Message);

            }

        }

privatestaticboolTryGetBoundaryExtents(Document doc, Point3d insidePoint, out Extents3d extents)

        {

            extents = default;

            DBObjectCollection objs;

try

            {

                objs = doc.Editor.TraceBoundary(insidePoint, true);

            }

catch (System.Exception ex)

            {

                doc.Editor.WriteMessage("\n边界识别失败: " + ex.Message);

returnfalse;

            }

if (objs == null || objs.Count == 0)

returnfalse;

bool ok = false;

try

            {

foreach (DBObject obj in objs)

                {

if (obj is Entity ent)

                    {

                        Extents3d e;

try { e = ent.GeometricExtents; }

catch { continue; }

if (!ok)

                        {

                            extents = e;

                            ok = true;

                        }

else

                        {

var min = new Point3d(

                                Math.Min(extents.MinPoint.X, e.MinPoint.X),

                                Math.Min(extents.MinPoint.Y, e.MinPoint.Y),

                                Math.Min(extents.MinPoint.Z, e.MinPoint.Z));

var max = new Point3d(

                                Math.Max(extents.MaxPoint.X, e.MaxPoint.X),

                                Math.Max(extents.MaxPoint.Y, e.MaxPoint.Y),

                                Math.Max(extents.MaxPoint.Z, e.MaxPoint.Z));

                            extents = new Extents3d(min, max);

                        }

                    }

                }

            }

finally

            {

foreach (DBObject obj in objs)

                    obj.Dispose();

            }

return ok;

        }

    }

}

整个插件的开发流程总结────────────────────────────────────────────────────────────────────────────────────────────────────

第一轮:出原型,搞定门板均分和基础二维预览。
第二轮:死磕尺寸与缝隙,把标称和物理完全剥离开,实现点选尺寸实时修改。
第三轮:死磕交互细节,加入左右悬浮小按钮解决单开门排版,把文字变成独立可编辑。
第四轮:CAD 实体生成修复,加入异常捕获,确保生成的文字永远居中不乱码。

成品领取 & 下期预告────────────────────────────────────────────────────────────────────────────────────────────────────

成品插件直接领

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

下期预告

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

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

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

相关学习资料