block-reference
本文回答:块定义
BlockDef+ 块引用BlockRefEnt—— 复用图形、参数化插入、属性 Attribute。
一、概念
- Block Definition:一组实体的封装(类似"符号"模板),不直接显示
- Block Reference (INSERT):在地图上的一次放置,通过
blockId引用某个 BlockDef;可单独设旋转 / 比例 / 插入点 - 同一 BlockDef 被 1000 次引用,只占用 1 份定义的内存
二、定义块
const def = new vjmapext.BlockDef('chair'); // name='chair';blockId 此刻还是 ""——
// 在下方 blocks.add(def) 注册时才自动生成(如 "shzmgyqi")
def.addEntity(new vjmapext.LineEnt([0,0], [40,0]));
def.addEntity(new vjmapext.LineEnt([40,0], [40,40]));
def.addEntity(new vjmapext.LineEnt([40,40], [0,40]));
def.addEntity(new vjmapext.LineEnt([0,40], [0,0]));
mapcadLayer.blocks.add(def); // 注册进 layer 的块表
基点 (basePoint)
basePoint 默认 (0,0),是"定义原点"元数据——渲染时不会自动减基点:约定是 def 里的实体坐标本身就必须是相对基点的本地坐标(内置 BLOCK 命令建块时先把选中实体 move(basePoint → 原点) 再入 def)。basePoint 字段的实际用途:DWG 导出时写成 DbBlock.origin、随序列化往返、块管理面板显示。
const def = new vjmapext.BlockDef('chair', [20, 20]); // ctor 第二参即 basePoint
// 或 def.basePoint = new vjmapext.Point2D(20, 20);
// 想让 (20,20) 成为锚点,def 内实体坐标要事先写成相对它的本地坐标:
def.addEntity(new vjmapext.LineEnt([-20,-20], [20,-20])); // 而不是 [0,0]→[40,0]
三、插入(BlockRefEnt)
引用按 blockId(不是 name)关联:
const ref1 = new vjmapext.BlockRefEnt(def.blockId, [100, 100]);
mapcadLayer.addEntity(ref1); // addEntity 会自动注入 blocks 引用
const ref2 = new vjmapext.BlockRefEnt(def.blockId, [200, 200], 1.5, Math.PI / 4);
mapcadLayer.addEntity(ref2);
属性
| 属性 | 说明 |
|---|---|
blockId |
引用的 BlockDef.blockId(构造第 1 参);blockName 为只读 getter,反查定义名 |
insertionPoint |
插入点(构造第 2 参,Point2DLike) |
scaleFactor |
统一缩放(构造第 3 参,number) |
rotation |
旋转(构造第 4 参,弧度) |
attributes |
属性标签(见下) |
四、属性标签 (Attributes)
属性值要能显示,块定义里必须有对应的 ATTDEF——即设了 tag 的 TextEnt;渲染时属性值按 tag 匹配替换该文本(文本位置由 ATTDEF 自己的位置决定):
const def = new vjmapext.BlockDef('device');
def.addEntity(new vjmapext.CircleEnt([0,0], 20));
// ATTDEF:带 tag 的 TextEnt,位置/字高/旋转由它决定
const attdef = new vjmapext.TextEnt([0, -25], 'NAME', 8);
attdef.tag = 'NAME';
attdef.prompt = '设备名称';
def.addEntity(attdef);
mapcadLayer.blocks.add(def);
const ref = new vjmapext.BlockRefEnt(def.blockId, [100, 100]);
ref.initAttributesFromDef(); // 按 ATTDEF 初始化属性(INSERT 命令会自动调)
ref.addAttribute({ tag: 'NAME', textString: 'P-001' }); // 按 tag upsert
mapcadLayer.addEntity(ref);
没有匹配 ATTDEF 的属性不渲染,但仍会进属性面板、随序列化保存、DWG 导出时写入 ref.attribute({tag: textString} 映射)。
BlockAttribute 字段:tag / textString / position / height / rotation / invisible / constant / preset / prompt 等(见 d.ts)。相关方法:addAttribute / getAttributes / getAttributeByTag(tag) / setAttributes(attrs) / initAttributesFromDef()。
五、展开(getNestEntities)
取引用展开后的实体数组(不修改原引用):
const nested = ref.getNestEntities(); // EntityBase[],已应用插入点/缩放/旋转
交互式炸开用内置 EXPLODE 命令(mapcadLayer.executeCommand('EXPLODE'),支持 BlockRefEnt 和闭合 PolylineEnt)。程序化:把展开结果加进 store 并删原引用:
ref.getNestEntities().forEach(e => mapcadLayer.addEntity(e.clone()));
mapcadLayer.deleteEntity(ref.id);
炸开后失去 block 关联,无法再集中修改。
六、动态块修改
修改 BlockDef 里的实体后,需要把相关引用标脏才会重绘:
const def = mapcadLayer.blocks.itemByName('chair');
def.getEntities()[0].color = 0xff0000;
mapcadLayer.getEntities()
.filter(e => e.type === 'INSERT' && e.blockName === 'chair')
.forEach(e => e.changed());
七、嵌套
Block 可以包含 Block 引用(多层嵌套支持),但内层引用要手动注入块表——BlockDef.addEntity 不注入,只有 mapcadLayer.addEntity/内置命令才 setBlocksRef;内层没有 _blocksRef 时展开为空,外层引用里那把椅子就不显示:
const outer = new vjmapext.BlockDef('room');
outer.addEntity(new vjmapext.PolylineEnt([[0,0],[500,0],[500,300],[0,300]], true));
const inner = new vjmapext.BlockRefEnt(chairDef.blockId, [100, 100]);
inner.setBlocksRef(mapcadLayer.blocks); // ← 必须,否则内层展开为 []
outer.addEntity(inner);
mapcadLayer.blocks.add(outer);
⚠️ fromJSON/deserializeStore 还原块定义时同样不给块内的 INSERT 注入(只给顶层实体注入)。含嵌套块的文档读回后要补一遍,再让顶层引用重建缓存:
const blocks = mapcadLayer.blocks;
blocks.getAllItems().forEach(def => def.getEntities()
.forEach(e => { if (e.type === 'INSERT') e.setBlocksRef(blocks); }));
mapcadLayer.getEntities().filter(e => e.type === 'INSERT').forEach(e => e.changed());
八、性能对比
| 方式 | 1000 个相同图形 |
|---|---|
| 直接 add 1000 个 PolylineEnt | 内存 × 1000 |
| BlockRef 引用同一 BlockDef | 内存 × 1(定义)+ 1000 个小引用 |
九、典型场景
- 机械设备 / 元器件
- 建筑家具(椅子、桌子)
- 道路标志
- 图面图例
十、常见坑
- BlockDef 未注册就 ref:
blockId找不到定义 → 展开为空。先mapcadLayer.blocks.add(def)。注意顺序:new BlockDef()后blockId还是"",blocks.add()才生成——所以必须 add 完再new BlockRefEnt(def.blockId, ...)。 ref.blockName/ref.getNestEntities()依赖注入:二者要经mapcadLayer.addEntity(ref)注入 blocks 引用(内部字段_blocksRef)才有效——注入前blockName返回''、getNestEntities()返回[]。注意缓存中毒:getNestEntities()结果缓存在_cachedNestEnts,而setBlocksRef注入时不清缓存——若在注入前查过一次,注入后拿到的仍是[],须调ref.changed()才重建(changed()会清缓存)。blockName是活 getter 不受影响,会出现"blockName正常但nest恒空"的假象。- 引用用错键:
new BlockRefEnt第 1 参是def.blockId(blocks.add时生成的 id),不是'chair'这个名字;按名反查用blocks.itemByName('chair').blockId。 - 动态修改 BlockDef 不刷新:改完定义要把引用
changed()标脏(见第六节)。 position字段不生效:BlockAttribute.position只被序列化保留——渲染时属性文字显示在 ATTDEF 的位置,DWG 导出只写{tag: textString}。要控制文字位置,改定义里那个 ATTDEFTextEnt的insertionPoint(本地坐标)。