一、先把4个核心概念一次性讲透(ZWCAD/AutoCAD .NET,C#二次开发视角)
记住一条铁律:直线、多段线、文字、圆、标注,它们统统不是块!
1、BlockTable 块表(数据库层面,容器)
CAD数据库里有一张表叫 BlockTable(块表) ,就像一个文件夹。
文件夹里面放着很多 BlockTableRecord(块定义,BTR)。
数据库 → BlockTable(块表)
├─ *ModelSpace 【模型空间】块定义
├─ *Paper_Space 【布局1】块定义
├─ *Paper_Space1 【布局2】块定义
└─ MyPipeBlock 【你自己新建的自定义块定义】
⚠️重点:模型空间、每一个布局,本身本质就是一条「块定义 BlockTableRecord」
所以你之前代码:
csharp
if(btr.IsLayout) continue;
IsLayout=true 代表这条块定义是布局图纸空间,不是普通自定义块。
2、BlockTableRecord 块定义(BTR) ------「模板、原型、图纸」
块定义 = 模板,仓库里存的原始图纸。
它内部存放一堆子实体:直线、圆、文字、多段线。
块定义本身不会直接显示在画布上,它只是一份保存起来的模板。
例子:
你画了一个消火栓符号(一个圆+文字),执行 B 创建块,起名 FireHydrant
👉 CAD就在块表里面新增一条 BlockTableRecord,名字叫 FireHydrant,里面保存着圆、文字。
这就是【用户自定义块定义】。
3、BlockReference 块参照(BR) ------「插入出来的实例、副本」
当你执行 INSERT,把刚才的消火栓块插到模型空间某一点。
画布上出现的那个看得见的消火栓实体,它的类型就是 BlockReference 块参照。
- 块参照本身不带图形;
- 块参照只保存:位置、旋转角度、缩放比例、属性值;
- 它有一个属性
BlockTableRecordId,指向仓库里那份原始的块定义。
最通俗比喻
- 块定义(BTR)= 印章本体(锁在仓库,看不见)
- 块参照(BR)= 盖在纸上的印 (画布上看得见)
你盖10次印章,画布生成10个块参照,但是仓库永远只有1个块定义。
二、回答你所有疑问,一条一条对号入座
Q1:用户自定义块定义,这个自定义块是块定义吗?
✅是的。B命令创建出来保存到块表里的就是自定义块定义 BlockTableRecord。
Q2:是定义一个块然后插入到模型空间形成块参照吗?
✅完全正确!
- 新建块定义(BTR)→存入块表
- 插入 → 生成块参照(BlockReference)实体,放到模型空间。
Q3:CAD中一条直线、一个多段线、一个注记都属于块吗?
❌不属于!!
直线 Line、多段线 Polyline、文字 DBText、标注 Dimension
→ 全部叫 基本图元实体(Entity)
它们直接放在「模型空间那条块定义(*ModelSpace)」里面。
模型空间本质是一条特殊的块定义(*ModelSpace),它里面存放所有直接画上去的直线、文字。
你在模型空间随手画一条直线:这条直线的所有者就是
*ModelSpace这条BlockTableRecord。
Q4:块参照是指选择块定义插入形成的实体对应的那个块是吗
✅对!画布上那个插入后的实体,实体类型就是 BlockReference,也就是块参照。
三、模型空间、布局,特殊的块定义(最容易混淆的地方)
*ModelSpace- BlockTableRecord,IsLayout = true
- 所有你模型里直接绘制的直线、块参照,都放在这条块定义内部
*Paper_Space(布局1),*Paper_Space1(布局2)- BlockTableRecord,IsLayout = true
- 布局图纸空间,专门放图纸、视口
所以遍历块表的时候:
csharpif (btr == null || btr.IsLayout) continue;这一句的含义就是:跳过模型空间、所有布局,只遍历用户自己创建的自定义块定义。
四、一张层级结构图(CAD数据库)
BlockTable(块表)
├─ BlockTableRecord(*ModelSpace) //模型空间(IsLayout=true)
│ ├─ Line(直线)
│ ├─ Polyline(多段线)
│ ├─ BlockReference(消火栓_块参照) ←看得见的块实例
│
├─ BlockTableRecord(*Paper_Space) //布局1 (IsLayout=true)
├─ BlockTableRecord(*Paper_Space1) //布局2 (IsLayout=true)
│
└─ BlockTableRecord(FireHydrant) //【自定义块定义,模板】(IsLayout=false)
├─ Circle
└─ DBText
关键点区分记忆:
对象 数据库类型 是否画布可见 块定义模板 BlockTableRecord ❌不会直接显示 插入出来的块 BlockReference ✅画布可见实体 直线、多段线 Line/Polyline ✅画布可见实体,不是块参照
五、二次开发最常踩坑点(和你导出MDB业务强相关)
- 遍历所有画布上已经插入的块 → 遍历模型空间的实体,找类型是BlockReference的对象,不要去遍历块表!
- 遍历图纸里所有「已经定义好、还没插入的块模板」→ 才遍历BlockTable(块表),并且跳过IsLayout的模型/布局记录。
先直接纠正你里面几个核心误区,然后再逐条拆解
一句话结论:
IsLayout和有没有插入、有没有块参照,完全没关系!!
IsLayout是块定义(BlockTableRecord)自身的固有属性,和你插没插入图纸半毛钱无关。
1、先分清两个完全不同的对象
- BlockTableRecord(BTR,块定义)→存放在块表BlockTable里面
- 才有属性:
btr.IsLayout
- 才有属性:
- BlockReference(BR,块参照,画布上那个插入出来的实体)
- ❌块参照根本没有 IsLayout 属性!!
- 块参照有一个属性:
BlockTableRecordId,用来指向它所使用的那份块定义(BTR)
2、IsLayout 的真实含义
csharp
btr.IsLayout
含义:这个块定义是不是「图纸空间(布局)的容器块定义」
CAD块表里,永远自带几条特殊的块定义:
| 块定义名称 | IsLayout 值 | 说明 |
|---|---|---|
*ModelSpace |
true | 模型空间容器 |
*Paper_Space |
true | 布局1(图纸空间) |
*Paper_Space1 |
true | 布局2(图纸空间) |
*Paper_Space2 |
true | 布局3...... |
你自己建的块 WS_POINT、消火栓块 |
false | 用户自定义块定义 |
✅规则铁律
IsLayout=true的 BTR,永远只能是各个布局(Paper‑Space)那条内置块定义- 你自己创建的任何自定义块定义,IsLayout永远等于 false,一生不会变
- 一个自定义块,哪怕你插入100次到布局空间,这份原始块定义的 IsLayout 依旧是 false!!
插入到布局,改变的只是【块参照】放在哪个空间,不会修改源块定义的任何属性。
3、逐条回答你的疑问
Q1:btr.IsLayout是不是用来判断某个块定义是不是插入了模型空间或者布局空间?
❌不是!
它完全不检测有没有被插入 。
它只是标记:这条BTR本身是不是布局图纸空间。
Q2:遍历块表的时候会获取数据库中所有东西,哪怕是自己创建的块定义,只要插入了模型空间或者布局空间,对应的块参照btr.IsLayout就是true❌这里犯了两个错误:
- 遍历块表(BlockTable)拿出来的全部都是【块定义BTR】,拿不到块参照!!
块参照(BlockReference)是实体,存放在*ModelSpace或者*Paper_Space的内部实体列表里,不在块表里面。 - 块参照没有IsLayout属性。你插入到布局,源块定义IsLayout还是false。
Q3:任何块参照都是true吧
❌块参照没有这个属性。
Q4:但是遍历块表的时候也会遍历到块定义,块定义的btr.IsLayout就是false,是这样吗✅你自己新建的普通块定义 → IsLayout = false
但是!有例外:
*Paper_Space、布局那些内置块定义,它们也是块定义,但是IsLayout=true
4、两张遍历场景彻底分清(你写ZWCAD插件最容易踩坑)
场景A:遍历【块表BlockTable】
拿到的全部是块定义(BTR)
csharp
BlockTable bt = tr.GetObject(db.BlockTableId, OpenMode.ForRead) as BlockTable;
foreach(ObjectId btrId in bt)
{
BlockTableRecord btr = tr.GetObject(btrId, OpenMode.ForRead) as BlockTableRecord;
if (btr.IsLayout) continue;
// 走到这里:排除掉所有布局(*Paper_Space*),剩下:*ModelSpace + 用户自定义块
}
⚠️注意!上面代码跳过IsLayout=true,*但是 ModelSpace 的IsLayout是false,所以循环还会进来!
很多人踩坑:忘了再判断名字,导致把模型空间这条BTR也当成自定义块了。
想要只拿用户自定义块定义,完整判断应该写成:
csharp
if (btr.IsLayout || btr.Name == "*ModelSpace")
continue;
场景B:遍历画布上【已经插入好的块参照】
不要遍历块表!!
你要打开模型空间的BTR,遍历它里面的实体,筛选BlockReference:
csharp
BlockTableRecord msBtr = tr.GetObject(db.CurrentSpaceId, OpenMode.ForRead) as BlockTableRecord;
foreach(ObjectId entId in msBtr)
{
Entity ent = tr.GetObject(entId, OpenMode.ForRead) as Entity;
if(ent is BlockReference br)
{
// br就是画布上看得见的块参照
// br.BlockTableRecordId → 指向源块定义
BlockTableRecord sourceBtr = tr.GetObject(br.BlockTableRecordId,OpenMode.ForRead) as BlockTableRecord;
// sourceBtr.IsLayout 永远false(只要它是你画的自定义块)
}
}
5、用印章例子再复盘一遍
- 你建一个消火栓块定义(BTR‑FireHydrant) →
IsLayout=false(印章模板) - INSERT,放到模型空间 → 生成块参照BR1(印在模型纸上)
- INSERT,放到布局1 → 生成块参照BR2(印在布局图纸纸上)
👉源印章模板FireHydrant那份BTR,从头到尾 IsLayout=false,不会因为你印到布局就变成true
最简记忆口诀
IsLayout属于块定义(BTR) ,不属于块参照(BR)IsLayout=true= 这条块定义本身就是布局图纸空间- 用户自定义块定义:永远 IsLayout=false
- 插在哪里(模型/布局)是块参照的存放位置,和源块定义属性互不干扰
结论
Insert(I)对话框下拉列表 = 当前图纸内【普通命名的用户块定义】的子集
并不是块表里所有BTR都会出现在这里,有一部分块定义看不见。
一、哪些块定义会出现在Insert列表
✅ 会显示:
- 你用
B(BLOCK)命令手动创建、起正常名字(不带星号开头):门窗、消火栓、WS_POINT,IsLayout=false - 动态块(块编辑器做出来的,自定义名字),也能在Insert列表看见
❌ 绝对不会出现在Insert下拉列表,但是真实存在于块表BlockTable里面的BTR:
*ModelSpace、*Paper_Space、*Paper_Space1(模型空间、所有布局)→IsLayout=true- 匿名块 :名字以
*U、*D、*X开头,CAD自动生成,填充、标注、动态块内部状态块,看不见,不能用Insert直接插入
匿名块也是一条BlockTableRecord块定义,IsLayout=false,但是Insert列表看不到它
二、对应到你C#遍历块表的代码
csharp
foreach(ObjectId btrId in bt)
{
BlockTableRecord btr = tr.GetObject(btrId, OpenMode.ForRead) as BlockTableRecord;
if (btr.IsLayout) continue;
//走到这里:已经过滤掉模型、布局
//剩下 = 用户命名块 + 匿名块(*Uxxx)
}
⚠️ 大坑!
if(btr.IsLayout) continue; 过滤不掉匿名块!
匿名块名字带*、IsLayout=false,遍历的时候你会把匿名块也一并取出来。
👉 这就是:代码遍历出来的块数量 > Insert对话框看到的块数量 的根源!
如果你想要得到和Insert对话框一模一样的块清单(纯用户自定义命名块,剔除匿名块) ,判断条件要再加一条:名字不能以*开头
最终完美筛选条件(ZWCAD/AutoCAD .NET)
csharp
BlockTableRecord btr = tr.GetObject(btrId, OpenMode.ForRead) as BlockTableRecord;
//条件:跳过布局(模型+图纸布局) + 跳过匿名块
if (btr.IsLayout || btr.Name.StartsWith("*"))
{
continue;
}
//剩下的集合 = Insert对话框下拉列表里面所有的用户自定义块定义
三、一张对照表
| 对象 | 块表(BTR)中存在 | Insert(I)列表可见 | IsLayout | 块名特征 |
|---|---|---|---|---|
| 模型空间 | ✅ | ❌ | true | *ModelSpace |
| 布局1/布局2 | ✅ | ❌ | true | *Paper_Space |
| 用户手动创建块(门窗、WS_POINT) | ✅ | ✅ | false | 不带* |
| 匿名块(填充、标注内部块) | ✅ | ❌ | false | *U123、*D456 |
| 动态块(用户做的) | ✅ | ✅ | false | 不带* |
四、通俗一句话总结
Insert命令里面看到的,全部都是用户自定义块定义;但图纸里面所有用户自定义块定义,不一定全都出现在Insert列表(匿名块就看不见)。
*ModelSpace.IsLayout = true,ObjectARX原生文档定义清清楚楚:
isLayout() 返回 true 的对象包含:
*MODEL_SPACE(模型空间)*PAPER_SPACE、*PAPER_SPACExxx(所有布局图纸空间)- 用户自定义块定义 → false
最终权威真值表(AutoCAD / ZWCAD .NET 通用)
| 块定义名称 | IsLayout | 补充属性 IsModelSpace |
|---|---|---|
*ModelSpace |
true | true |
*Paper_Space(当前激活布局) |
true | false |
*Paper_Space0、*Paper_Space1...其余布局 |
true | false |
| 你画的门窗、WS_POINT、消火栓(自定义块) | false | false |
核心大坑来了!!
你之前写的代码:
csharp
if (btr == null || btr.IsLayout) continue;
👉 这一句会把模型空间也跳过!!
因为 *ModelSpace.IsLayout = true
这就是绝大多数新手二次开发踩的经典陷阱:
IsLayout=true ≠ 仅仅代表图纸布局;模型空间本身也是一种Layout布局对象。
所以,如果你遍历块表,只想跳过图纸布局、保留模型空间,不能只用 IsLayout 判断。
正确筛选写法(3种方案)
方案1:最标准(只过滤图纸布局,放过模型空间)
csharp
//跳过:图纸空间布局;保留模型空间 + 用户自定义块
if (btr.IsLayout && !btr.IsModelSpace)
continue;
方案2:如果你目标:【只拿用户自定义块,模型+布局全部跳过】(你导出业务最常用)
csharp
if(btr.IsLayout)
continue;
这条一执行,模型空间、所有图纸布局全部被continue过滤掉,剩下就纯粹是自定义块定义,这个写法才是你最开始代码的本意!
方案3:兼容老版本,不用IsModelSpace属性,靠名字判断
csharp
if (btr.IsLayout || btr.Name == "*Model_Space")
continue;
把之前两个矛盾点彻底讲清楚
- 早期错误回答:
*ModelSpace‑>IsLayout=false→ 错 - 官方ARX文档:
*ModelSpace‑>IsLayout=true→ 正确
两个属性分工(记住这一组就永远不会乱)
btr.IsLayout
判断这条BTR是否属于「布局容器」,模型空间、图纸布局全部算布局btr.IsModelSpace
专门区分:它是不是模型空间那唯一一个布局
通俗大白话
- Layout大类 = {模型空间,图纸布局1,图纸布局2...}
- IsLayout:是不是Layout大类里面一员
- IsModelSpace:是不是Layout大类里面的模型空间那一个
回到你业务场景(导出MDB)
如果你循环块表,目的是遍历所有用户创建出来的块定义模板
csharp
foreach(ObjectId btrId in bt)
{
var btr = tr.GetObject(btrId, OpenMode.ForRead) as BlockTableRecord;
if (btr == null || btr.IsLayout) continue;
//此处剩下:全部自定义块定义!模型、布局已经被过滤干净
}
✅ 上面这段代码本身是正确可用 的,就是依靠 *ModelSpace.IsLayout=true,被一起continue跳过。
如果你接下来需要遍历画布上的直线、块参照实体,千万不要遍历块表 ;你需要打开模型空间BTR(db.ModelSpaceId),遍历它内部实体列表。