嘤嘤嘤,写了一堆。网页一卡都没了......,草稿里也找不到......以前会有定时保存的......可能修改不会有吧额......那就再开一个避免重蹈覆辙......续上面的(估计没有第一次漂亮了哎,就当再次复习了)
四、图元操作万能套路
创建 → new → AppendEntity → 提交
cs
// 标准创建流程
Line line = new Line(p1, p2); // 1. new 创建
tr.AddNewlyCreatedDBObject(line, true); // 2. 加入数据库
AppendEntity(line); // 3. 加入空间(可选写法)
tr.Commit(); // 4. 提交
读取 → tr.GetObject(id, ForRead)
cs
Entity ent = tr.GetObject(id, OpenMode.ForRead) as Entity;
修改 → tr.GetObject(id,ForWrite) → 改属性 → 提交
cs
Entity ent = tr.GetObject(id, OpenMode.ForWrite); // 必须写打开
ent.ColorIndex = 1; // 修改
tr.Commit(); // 提交
删除 → tr.GetObject(id,ForWrite) → ent.Erase() → 提交
cs
Entity ent = tr.GetObject(id, OpenMode.ForWrite);
ent.Erase(); // 删除
tr.Commit(); // 提交
五、几何相关的
Point2d / Point3d 是结构体(Struct),不是类
cs
Point3d a = new Point3d(10, 20, 30);
Point3d b = a; // 复制一份全新的
b.X = 999; // 改 b
// a 依然是 10,20,30
// b 变成 999,20,30
Point2d p1 = new Point2d(0,0);
Point2d p2 = p1; // 复制值
p2.X = 100;
// p1 还是 (0,0)
顺便提一嘴:
官网看到带美元符号 的,不是付费的意思,是静态static的意思(在Matrix3d这里基本静态的是创建矩阵,非静态,实例的是操作已有矩阵)。
另外一般大写的是C#小写的是C++的,例如:GetDistanceTo和getDistanceTo
【创建】
cs
Point2d p2 = new Point2d(x, y);
Point3d p3 = new Point3d(x, y, z);
Point3d org = Point3d.Origin; // (0,0,0)
【取值】
cs
double x = p3.X;
double y = p3.Y;
double z = p3.Z;
【距离】
cs
using Autodesk.AutoCAD.Geometry;
// 1. Point3d 点到点:只能用 DistanceTo
Point3d p1 = new Point3d(0,0,0);
Point3d p2 = new Point3d(10,0,0);
double distPoint = p1.DistanceTo(p2); // ✅ 编译通过
// double distError = p1.GetDistanceTo(p2); // ❌ 编译报错:Point3d无此方法
// 2. Curve2d 点到曲线:只能用 GetDistanceTo
Line2d line2d = new Line2d(new Point2d(0,0), new Point2d(10,0));
Point2d pt2d = new Point2d(5, 5);
double distCurve = line2d.GetDistanceTo(pt2d); // ✅ 编译通过
// double distCurveError = line2d.DistanceTo(pt2d); // ❌ 编译报错:Curve2d无此方法
| 类 | 方法名 | 作用 |
|---|---|---|
| Point3d | DistanceTo(Point3d) | 点→点 直线距离 |
| Point2d | DistanceTo(Point2d) | 点→点 直线距离 |
| Curve2d | GetDistanceTo(Point2d) | 点→曲线 最短距离 |
| Curve3d | GetDistanceTo(Point3d) | 点→曲线 最短距离 |
| Curve2d | GetDistanceTo(Curve2d) | 曲线→曲线 距离 |
| Curve3d | GetDistanceTo(Curve3d) | 曲线→曲线 距离 |
【运算】
cs
Point3d p = p1 + p2;
Point3d p = p1 - p2;
Point3d p = p1 * 2;
Point3d p = p1 / 2;
Vector3d vec = p2 - p1; // 点-点=向量
Point3d p = p + vec; // 点+向量=点
【2D ↔ 3D 互转】
cs
Point2d p2 = p3.Convert2d();
Point3d p3 = p2.Convert3d();
【矩阵变换】
cs
Point3d newP = p.TransformBy(matrix);
【相等判断】
cs
bool eq = p1.IsEqualTo(p2, 0.001);
【常用小公式】
cs
Point3d mid = p1 + (p2 - p1) * 0.5; // 中点
Vector3d dir = (p2 - p1).Normalize(); // 单位方向
Point3d newP = p1 + dir * 10; // 沿方向走10
坐标:WCS 世界坐标系 为主
WCS 世界坐标系:原点(0,0,0)永远在同一个位置;X向右,Y向上,Z垂直屏幕向外;永远不会被用户修改、移动、旋转。 (API拿到的、数据库存的,永远是WCS,代码只认WCS)
UCS 用户坐标系 :人看着方便,界面显示给用户看;用户可以新建、旋转、移动、换视角。
**DCS显示坐标系/屏幕坐标系:**鼠标/屏幕交互,左上角是原点,往右 X+,往下 Y+(频频木像素,极少用)
旋转、矩阵变换用 Matrix3d
Matrix3d是一个"变形工具包(黑盒子)",他专门做:移动、旋转、缩放、镜像、斜切这5件事。
【固定模板】
cs
// 1. 定义一个点
Point3d pt = new Point3d(10, 0, 0);
// 2. 创建变形矩阵(旋转/移动/缩放/镜像)
Matrix3d mat = Matrix3d.Rotation(...);
// 3. 应用变换 → 得到新点
Point3d newPt = pt.TransformBy(mat);
注意:
TransformBy 只会计算新的点,原来的点不变,不直接影响图纸显示;
(TransformBy 生成 Point3d 内存坐标,不生成 DBPoint)
Point3d 不是对象,是 struct(值类型,存在栈),不受GC管理;是内存里的点,他是数据坐标。
DBPoint 才是CAD图纸可见的,存在堆Heap,需要GC(DBPoint点、Line线、Polyline多边形、Circle圆......)
cs
// 1. 只是坐标(看不见)
Point3d pt = new Point3d(10, 20, 0);
// 2. 真正画到图上(看得见)
DBPoint dbPt = new DBPoint(pt);
【旋转】Matrix3d.Rotation(角度, Z轴, 中心点)
cs
// 角度:弧度(Math.PI = 180°)
double angle = Math.PI / 2; // 90度
Point3d basePoint = new Point3d(0,0,0); // 旋转基点
// 构建旋转矩阵(绕 Z 轴 = 平面旋转)
Matrix3d matRot = Matrix3d.Rotation(angle, Vector3d.ZAxis, basePoint);
// 应用到点/实体
Point3d newPt = pt.TransformBy(matRot);
line.TransformBy(matRot);
【移动】Matrix3d.Rotation(角度, Z轴, 中心点)
cs
Point3d fromPt = new Point3d(0,0,0);
Point3d toPt = new Point3d(10,20,0);
// 移动矩阵:从 from → to
Matrix3d matMove = Matrix3d.Displacement(fromPt, toPt);
// 应用
ent.TransformBy(matMove);
【缩放】Matrix3d.Scaling(比例, Z轴, 中心点)
cs
double scale = 2.0; // 放大2倍
Point3d basePoint = new Point3d(0,0,0);
Matrix3d matScale = Matrix3d.Scaling(scale, Vector3d.ZAxis, basePoint);
ent.TransformBy(matScale);
【镜像】Matrix3d.Mirror(轴点1, 轴点2)
cs
// 镜像轴:两点确定一条直线
Point3d p1 = new Point3d(0,0,0);
Point3d p2 = new Point3d(0,10,0);
Matrix3d matMirror = Matrix3d.Mirror(p1, p2);
ent.TransformBy(matMirror);
【组合变换-矩阵叠加】矩阵是右乘 → 右边先直线,左边后执行(A *B=先B后A)
cs
Matrix3d mat1 = 旋转;
Matrix3d mat2 = 移动;
// 叠加:先旋转 → 再移动
Matrix3d finalMat = mat2 * mat1;
// 一次变换完成
Point3d newPt = pt.TransformBy(finalMat);
长度、面积用几何方法
【长度】
line.Length
curve.Length
【面积】
poly.Area
region.Area
【距离】
point.DistanceTo(point)
cure.GetDistanceTo(point)
【交点】
curve.IntersectWith(curve)
Curve2d.Intersect
六、选择集
定义
选择集 = 用户在 CAD 里框选/点选的一堆图元(线、圆、块、点...)
我们的代码要做4件事:
1、用PromptSelectionOptions设置选择提示
2、用SelectionFilter设置过滤条件
3、用ed.GetSelection()让用户选择
4、遍历 SelectionSet
5、拿到每一个图元的ObjectId
6、用事务打开并操作
模板
【核心固定写法】
cs
using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.EditorInput;
using Autodesk.AutoCAD.Geometry;
// 固定开头(必写)
Document doc = Application.DocumentManager.MdiActiveDocument;
Database db = doc.Database;
Editor ed = doc.Editor;
// 1. 设置选择提示
PromptSelectionOptions pso = new PromptSelectionOptions();
pso.MessageForAdding = "请选择图元:";
// 2. 设置过滤(替换这里的条件即可)
TypedValue[] filters = {
new TypedValue(0, "LINE") // 只选直线
};
SelectionFilter filter = new SelectionFilter(filters);
// 3. 获取选择集
PromptSelectionResult res = ed.GetSelection(pso, filter);
// ==============================================
// ✅ 关键:选择成功后 必须开启事务 才能操作图元
// ==============================================
if (res.Status == PromptStatus.OK)
{
// 👇 这一行绝对不能少!!
using (Transaction tr = db.TransactionManager.StartTransaction())
{
// 遍历选择集
foreach (SelectedObject so in res.Value)
{
ObjectId id = so.ObjectId;
// 👇 打开图元(必须在事务内)
Entity ent = tr.GetObject(id, OpenMode.ForRead) as Entity;
if (ent != null)
{
ed.WriteMessage("\n选中图元:" + ent.Layer);
}
}
tr.Commit(); // 提交事务
}
}
【只选 直线Line】
cs
TypedValue[] filters = {
new TypedValue(0, "LINE")
};
【只选 圆Circle】
cs
TypedValue[] filters = {
new TypedValue(0, "CIRCLE")
};
【只选 指定图层】
cs
TypedValue[] filters = {
new TypedValue(8, "轴线")
};
【只选 指定颜色图元】(1红 2黄 3绿 4青 5蓝 6紫 7白)
cs
TypedValue[] filters = {
new TypedValue(62, 1)
};
【只选 指定块】
cs
TypedValue[] filters = {
new TypedValue(0, "INSERT"),
new TypedValue(2, "门")
};
【多条件组合】
cs
//只选:图层 = 墙线 + 类型 = 直线
TypedValue[] filters = {
new TypedValue(0, "LINE"),
new TypedValue(8, "墙线")
};
cs
//选 墙线 OR 轴线 OR 门窗 三层
TypedValue[] filters = {
new TypedValue(-4, "<or"),
new TypedValue(8, "墙线"),
new TypedValue(8, "轴线"),
new TypedValue(8, "门窗"),
new TypedValue(-4, "or>")
};
cs
TypedValue[] filters = {
new TypedValue(-4, "<and"), // 同时满足
// 条件1:直线 或 圆
new TypedValue(-4, "<or"),
new TypedValue(0, "LINE"),
new TypedValue(0, "CIRCLE"),
new TypedValue(-4, "or>"),
// 条件2:不是轴线层
new TypedValue(-4, "<not"),
new TypedValue(8, "轴线"),
new TypedValue(-4, "not>"),
new TypedValue(-4, "and>"),
};
过滤代码表
| 数字 | 代表含义 | 示例 |
|---|---|---|
| -4 | 逻辑符组码 | ADN(且)、OR(或)、NOT(非)、XOR(异或) |
| 0 | 图元类型 | LINE,CIRCLE,ARC,TEXT,INSERT |
| 1 | 文字内容 | "ABC123" |
| 2 | 块名 | "门","窗","办公桌" |
| 8 | 图层 | "墙线","轴线","尺寸" |
| 62 | 颜色 | 1 = 红,2 = 黄,3 = 绿,4 = 青,5 = 蓝,6 = 紫,7 = 白 |
逻辑符号表
| 逻辑 | 开头写法 | 结尾写法 | 意思 |
|---|---|---|---|
| AND | <and | and> | 同时满足(默认就是AND,不用写) |
| OR | <or | or> | 满足一个就行 |
| NOT | <not | not> | 不是这个、排除这个 |
| XOR | <xor | xor> | 二选一(很少用) |
七、图层、颜色、线型 规范
【核心原则】
图元的颜色、线型 尽量不要单独设置,全部交给图层去控制。图层管样式,图元管内容。
【图层】
图层 = CAD的"文件夹" + "样式总管"
一层统一 :颜色、线型、线宽、开关/冻结/锁定
操作图层必须用的2个类:
LayerTable: 图层的总表(所有图层都在这里)
LayerTableRecord:一个图层的信息(名称、颜色、开关)
图层标准操作模板(创建 / 判断 / 获取)
cs
// 固定写法:获取图层
Database db = HostApplicationServices.WorkingDatabase;
using (Transaction tr = db.TransactionManager.StartTransaction())
{
LayerTable lt = tr.GetObject(db.LayerTableId, OpenMode.ForRead) as LayerTable;
// 判断图层是否存在
if (lt.Has("我的图层"))
{
// 图层已存在,直接用
}
else
{
// 创建新图层
LayerTableRecord ltr = new LayerTableRecord();
ltr.Name = "我的图层";
ltr.Color = Color.FromColorIndex(ColorMethod.ByAci, 3); // 绿色
// 写入图层表
tr.GetObject(lt, OpenMode.ForWrite);
lt.Add(ltr);
tr.AddNewlyCreatedDBObject(ltr, true);
}
tr.Commit();
}
【颜色】
**标准色:**1 = 红,2 = 黄,3 = 绿,4 = 青,5 = 蓝,6 = 紫,7 = 白
规范 (交给图层)
cs
line.Layer = "墙线"; // ✅ 墙线层是红色,线就自动变红
不规范 (单独给图元上色,全局硬改)
cs
line.ColorIndex = 1; // ❌ 不推荐!图元自己覆盖颜色
原因:
1、改图元颜色=脱离图层控制,后期无法统一修改
2、别人用你的CAD文件,改图层颜色却改不动你的图元
【线型(Linetype)】
线型也要从图层来,不要单独设置
cs
line.Linetype = "MyLayer"; // ✅ 正确
八、事件
事件=监听+触发(订阅事件,CAD干完自动喊你)
Application.Idle:CAD 空闲时触发
用途:后台刷新、轻量任务
cs
// 订阅/注销
Application.Idle += WhenIdle;
Application.Idle -= WhenIdle;
// 方法(名字随便改:WhenIdle / 空闲执行 / ABC 都行)
private void WhenIdle(object sender, EventArgs e)
{
// 这里写你要做的事
}
注意:Idle 会疯狂触发 → 必须控制 "执行频率"
1、加事件间隔:每隔1~2秒跑一次,不是每次Idle都跑。
cs
DateTime lastRun = DateTime.Now;
private void OnIdle(object sender, EventArgs e)
{
if ((DateTime.Now - lastRun).TotalSeconds < 1)
return; // 不到1秒,直接退出,不执行
// =========== 这里写你的代码 ===========
DoSomething();
lastRun = DateTime.Now; // 更新最后执行时间
}
2、只在"真空闲"时执行(判断是否有命令正在运行,有命令就不跑)
cs
if (doc.CommandInProgress) return;
3、代码必须轻量化
❌ 大量遍历图元
❌ 频繁开启事务
❌ 弹窗、输入、界面阻塞
✅ 刷新界面
✅ 状态检查
✅ 轻量自动任务
4、用完立即-=注销,避免一直跑
Document.CommandWillStart:命令即将开始
cs
doc.CommandWillStart += BeforeCommand;
doc.CommandWillStart -= BeforeCommand;
private void BeforeCommand(object sender, CommandEventArgs e)
{
// e.CommandName 拿到命令名
}
使用场景举例:
1、禁止用户使用某些命令(比如禁止删除、禁止炸开)
2、命令开始前做初始化(比如画直线前切换图层)
3、监听用户要执行什么命令(做日志、统计、操作记录)
4、自动准备选择集/变量(命令开始前先预先加载数据)
Document.CommandEnded:命令已经结束
cs
doc.CommandEnded += AfterCommand;
doc.CommandEnded -= AfterCommand;
private void AfterCommand(object sender, CommandEventArgs e)
{
}
Database.ObjectAppended:图元被【加入数据库】时触发
cs
db.ObjectAppended += OnObjectAdded;
db.ObjectAppended -= OnObjectAdded;
private void OnObjectAdded(object sender, ObjectEventArgs e)
{
// e.DBObject 拿到图元
}
注意:触发了 ObjectAppended ≠ 界面一定看得见
因为:ObjectAppended 只要图元加入数据库(任何块、任何空间)就触发。包括:
1、显示空间(看得见)
模型空间 ModelSpace
图纸空间 PaperSpace
2、非显示空间(看不见)
块定义内部(普通块、匿名块、内部块)
只进数据库、没放到任何空间里,例如:db.AddDBObject(line);
【正确的显示流程】
1、new Line () → 创建图元(内存)
2、添加到模型空间 ModelSpace → 自动触发 ObjectAppended
3、事务 Commit
4、界面显示
【如何判断是不是真正滑倒界面上】
cs
if (ent.BlockId == db.CurrentSpaceId)
{
// ✅ 真正显示在模型/图纸空间
}
else
{
// ❌ 在块里、或其他空间,界面看不见
}
Database.ObjectErased:有东西被删除
cs
db.ObjectErased += OnObjectDeleted;
db.ObjectErased -= OnObjectDeleted;
private void OnObjectDeleted(object sender, ObjectEventArgs e)
{
}
Database.ObjectModified:图元被修改
cs
db.ObjectModified += OnObjectChanged;
db.ObjectModified -= OnObjectChanged;
private void OnObjectChanged(object sender, ObjectEventArgs e)
{
}
Document.BeginSave / EndSave:保存开始 / 结束
cs
// 保存前
doc.BeginSave += BeforeSave;
doc.BeginSave -= BeforeSave;
private void BeforeSave(object sender, DocumentBeginSaveEventArgs e)
{
// e.FileName 文件名
}
//保存后
doc.EndSave += AfterSave;
doc.EndSave -= AfterSave;
private void AfterSave(object sender, EventArgs e)
{
}
SystemEvents.AppDomainUnload:插件卸载时(统一注销所有事件)
cs
AppDomain.CurrentDomain.DomainUnload += OnPluginUnload;
private void OnPluginUnload(object sender, EventArgs e)
{
// 这里把所有事件 -= 一遍,防崩溃
Application.Idle -= WhenIdle;
doc.CommandWillStart -= BeforeCommand;
// ...其他事件都 -=
}
System.AppDomain.CurrentDomain.DomainUnload -= OnDomainUnload; // 一般不用
通用写法套路
cs
对象.事件 += 方法; //订阅
对象.事件 -= 方法; //注销,不注销 = 内存泄漏 = 崩溃 **
private void 方法(object sender, 参数 e)
{
// 这里写你要干的事!!!
}
【文档类】
-
CommandEnded(命令结束时触发→ 自动修改刚画的图元)
-
BeginSave / EndSave(保存前 / 保存后→ 自动检查、自动处理)
-
DocumentActivated / DocumentDestroyed(切换文档、关闭文档)
【数据库(图元)类】
-
ObjectAppended(图元被画出来时触发→ 自动改图层、自动编号)
-
ObjectErased(图元被删除时)
-
ObjectModified(图元被修改时(移动、旋转、拉伸))
【系统类】
-
SystemEvents.AppDomainUnload(插件卸载时→ 统一注销所有事件,防止崩溃)
-
Application.SystemQuiescent(CAD 完全启动完成后)
-
LayerModified / LayerDeleted(图层被修改 / 删除)
-
LinetypeModified(线型被修改)
常用事件全套完整可运行 Demo
cs
using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.EditorInput;
using Autodesk.AutoCAD.Runtime;
using System;
public class EventDemo
{
// 全局保存 doc 和 db,方便事件用
private Document doc = Application.DocumentManager.MdiActiveDocument;
private Database db => HostApplicationServices.WorkingDatabase;
// 一次性订阅所有事件
[CommandMethod("StartEvents")]
public void StartEvents()
{
// 1. 空闲事件
Application.Idle += OnIdle;
// 2. 命令开始/结束
doc.CommandWillStart += OnCommandWillStart;
doc.CommandEnded += OnCommandEnded;
// 3. 图元增删改
db.ObjectAppended += OnObjectAppended;
db.ObjectErased += OnObjectErased;
db.ObjectModified += OnObjectModified;
// 4. 保存事件
doc.BeginSave += OnBeginSave;
doc.EndSave += OnEndSave;
// 5. 插件卸载时统一清理(只+不-)
AppDomain.CurrentDomain.DomainUnload += OnDomainUnload;
doc.Editor.WriteMessage("\n✅ 事件已启动");
}
// 手动停止事件(也会被卸载时自动调用)
[CommandMethod("StopEvents")]
public void StopEvents()
{
UnsubscribeAll();
doc.Editor.WriteMessage("\n❌ 事件已停止");
}
// ====================== 统一注销所有事件(核心)======================
private void UnsubscribeAll()
{
Application.Idle -= OnIdle;
doc.CommandWillStart -= OnCommandWillStart;
doc.CommandEnded -= OnCommandEnded;
db.ObjectAppended -= OnObjectAppended;
db.ObjectErased -= OnObjectErased;
db.ObjectModified -= OnObjectModified;
doc.BeginSave -= OnBeginSave;
doc.EndSave -= OnEndSave;
}
// ====================== 各个事件方法 ======================
// 空闲
private void OnIdle(object sender, EventArgs e)
{
// 轻量代码,别写重逻辑
}
// 命令即将开始
private void OnCommandWillStart(object sender, CommandEventArgs e)
{
Editor ed = doc.Editor;
ed.WriteMessage($"\n→ 命令开始: {e.CommandName}");
}
// 命令结束
private void OnCommandEnded(object sender, CommandEventArgs e)
{
Editor ed = doc.Editor;
ed.WriteMessage($"\n← 命令结束: {e.CommandName}");
}
// 图元添加
private void OnObjectAppended(object sender, ObjectEventArgs e)
{
using (Transaction tr = db.TransactionManager.StartTransaction())
{
if (e.DBObject is Entity ent)
{
// 真正显示在模型空间才打印
if (ent.BlockId == db.CurrentSpaceId)
{
doc.Editor.WriteMessage($"\n➕ 添加图元: {ent.GetType().Name}");
}
}
tr.Commit();
}
}
// 图元删除
private void OnObjectErased(object sender, ObjectEventArgs e)
{
doc.Editor.WriteMessage($"\n➖ 删除图元: {e.DBObject.GetType().Name}");
}
// 图元修改
private void OnObjectModified(object sender, ObjectEventArgs e)
{
// 图元被移动/旋转时触发
}
// 保存前
private void OnBeginSave(object sender, DocumentBeginSaveEventArgs e)
{
doc.Editor.WriteMessage($"\n💾 准备保存: {e.FileName}");
}
// 保存后
private void OnEndSave(object sender, EventArgs e)
{
doc.Editor.WriteMessage("\n✅ 保存完成");
}
// 插件卸载时自动清理(防止崩溃)
private void OnDomainUnload(object sender, EventArgs e)
{
UnsubscribeAll();
}
}
九、性能要点
1、少遍历全图,多用选择集/过滤
(遍历全图巨慢)
2、批量操作统一一个事务。
不要每操作一个图元就开一次事务,N个操作用一个事务(频繁事务巨卡、崩溃)
3、不要在Idle里做heavy操作
Idle会疯狂触发
Heavy = 遍历、大量事务、循环、弹窗
正确做法:加时间间隔、轻量判断
4、关闭屏幕刷新(提速神器)
cs
ed.SetSuppressScreenDisplay(true); // 关闭刷新
// 你的批量操作...
ed.SetSuppressScreenDisplay(false); // 恢复刷新
十、常见坑
- 没事务 / 没 Commit
- ForRead 想改图 → 报错
- 持有 Entity 跨事务 → 崩溃
- 事件没注销 → 内存泄漏
- 多线程直接操作 CAD → 必崩
- GetLastRect 遍历全图 → 大图卡爆
- 没 try/catch → 闪退、丢图、崩 CAD
- 批量操作不关刷新 → 巨慢
- Idle 做 heavy 操作 → 卡到爆
- 选择集不过滤 → 遍历全图慢死
- 忘记 Dispose / 释放资源 → 内存越来越大