模型空间、布局控件、自定义块

一、先把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:是定义一个块然后插入到模型空间形成块参照吗?

✅完全正确!

  1. 新建块定义(BTR)→存入块表
  2. 插入 → 生成块参照(BlockReference)实体,放到模型空间。

Q3:CAD中一条直线、一个多段线、一个注记都属于块吗?

不属于!!

直线 Line、多段线 Polyline、文字 DBText、标注 Dimension

→ 全部叫 基本图元实体(Entity)

它们直接放在「模型空间那条块定义(*ModelSpace)」里面。

模型空间本质是一条特殊的块定义(*ModelSpace),它里面存放所有直接画上去的直线、文字。

你在模型空间随手画一条直线:这条直线的所有者就是 *ModelSpace 这条BlockTableRecord。

Q4:块参照是指选择块定义插入形成的实体对应的那个块是吗

✅对!画布上那个插入后的实体,实体类型就是 BlockReference,也就是块参照。


三、模型空间、布局,特殊的块定义(最容易混淆的地方)

  1. *ModelSpace
    • BlockTableRecord,IsLayout = true
    • 所有你模型里直接绘制的直线、块参照,都放在这条块定义内部
  2. *Paper_Space(布局1),*Paper_Space1(布局2)
    • BlockTableRecord,IsLayout = true
    • 布局图纸空间,专门放图纸、视口

所以遍历块表的时候:

csharp 复制代码
if (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业务强相关)

  1. 遍历所有画布上已经插入的块 → 遍历模型空间的实体,找类型是BlockReference的对象,不要去遍历块表!
  2. 遍历图纸里所有「已经定义好、还没插入的块模板」→ 才遍历BlockTable(块表),并且跳过IsLayout的模型/布局记录。

先直接纠正你里面几个核心误区,然后再逐条拆解

一句话结论:IsLayout 和有没有插入、有没有块参照,完全没关系!!

IsLayout块定义(BlockTableRecord)自身的固有属性,和你插没插入图纸半毛钱无关。

1、先分清两个完全不同的对象

  1. BlockTableRecord(BTR,块定义)→存放在块表BlockTable里面
    • 才有属性:btr.IsLayout
  2. 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 用户自定义块定义

规则铁律

  1. IsLayout=true 的 BTR,永远只能是各个布局(Paper‑Space)那条内置块定义
  2. 你自己创建的任何自定义块定义,IsLayout永远等于 false,一生不会变
  3. 一个自定义块,哪怕你插入100次到布局空间,这份原始块定义的 IsLayout 依旧是 false!!

插入到布局,改变的只是【块参照】放在哪个空间,不会修改源块定义的任何属性


3、逐条回答你的疑问

Q1:btr.IsLayout是不是用来判断某个块定义是不是插入了模型空间或者布局空间?

❌不是!

完全不检测有没有被插入

它只是标记:这条BTR本身是不是布局图纸空间。
Q2:遍历块表的时候会获取数据库中所有东西,哪怕是自己创建的块定义,只要插入了模型空间或者布局空间,对应的块参照btr.IsLayout就是true

❌这里犯了两个错误:

  1. 遍历块表(BlockTable)拿出来的全部都是【块定义BTR】,拿不到块参照!!
    块参照(BlockReference)是实体,存放在 *ModelSpace 或者 *Paper_Space 的内部实体列表里,不在块表里面
  2. 块参照没有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、用印章例子再复盘一遍

  1. 你建一个消火栓块定义(BTR‑FireHydrant) → IsLayout=false(印章模板)
  2. INSERT,放到模型空间 → 生成块参照BR1(印在模型纸上)
  3. INSERT,放到布局1 → 生成块参照BR2(印在布局图纸纸上)

👉源印章模板FireHydrant那份BTR,从头到尾 IsLayout=false,不会因为你印到布局就变成true

最简记忆口诀

  1. IsLayout 属于块定义(BTR)不属于块参照(BR)
  2. IsLayout=true = 这条块定义本身就是布局图纸空间
  3. 用户自定义块定义:永远 IsLayout=false
  4. 插在哪里(模型/布局)是块参照的存放位置,和源块定义属性互不干扰

结论

Insert(I)对话框下拉列表 = 当前图纸内【普通命名的用户块定义】的子集

并不是块表里所有BTR都会出现在这里,有一部分块定义看不见。

一、哪些块定义会出现在Insert列表

✅ 会显示:

  1. 你用B(BLOCK)命令手动创建、起正常名字(不带星号开头):门窗消火栓WS_POINTIsLayout=false
  2. 动态块(块编辑器做出来的,自定义名字),也能在Insert列表看见

绝对不会出现在Insert下拉列表,但是真实存在于块表BlockTable里面的BTR:

  1. *ModelSpace*Paper_Space*Paper_Space1(模型空间、所有布局)→ IsLayout=true
  2. 匿名块 :名字以*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 的对象包含:

  1. *MODEL_SPACE(模型空间)
  2. *PAPER_SPACE*PAPER_SPACExxx(所有布局图纸空间)
  3. 用户自定义块定义 → 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;

把之前两个矛盾点彻底讲清楚

  1. 早期错误回答:*ModelSpace‑>IsLayout=false → 错
  2. 官方ARX文档:*ModelSpace‑>IsLayout=true → 正确

两个属性分工(记住这一组就永远不会乱)

  1. btr.IsLayout
    判断这条BTR是否属于「布局容器」,模型空间、图纸布局全部算布局
  2. 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),遍历它内部实体列表。

相关推荐
z落落37 分钟前
C# WinForm TCP 服务器+WinForm TCP 客户端
服务器·tcp/ip·c#
csdn_aspnet1 小时前
C# 多个串口多个线程发送数据和接收数据
c#·串口·thread·com·serialport
吴可可12313 小时前
多段线局部约束缩放实现
c#
棉晗榜17 小时前
C# HttpClient配置具有tls指纹发起跟浏览器一样的请求解决反爬虫防护
开发语言·爬虫·c#
吴可可12318 小时前
在CAD中点击添加多段线顶点的方法与实现
c#
全栈小518 小时前
【C#】.net core,静态方法线程安全解析,面试时经常被问的线程安全就藏在里面
安全·c#·.netcore
天下无敌笨笨熊19 小时前
C#视频开发心得
开发语言·c#·音视频
LccKyI19 小时前
C#基础编程 面试题汇总(三)
笔记·c#
半亩码田21 小时前
C#转Python第4.1篇:当 try-catch 遇上 try-except:异常处理的大不同
开发语言·python·c#