唯杰地图 logo唯杰地图
VJMAP
EN

block-reference

vjmapext/references/entity/block-reference.md 7.4 KB153 行
原文

本文回答:块定义 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 个小引用

九、典型场景

  • 机械设备 / 元器件
  • 建筑家具(椅子、桌子)
  • 道路标志
  • 图面图例

十、常见坑

  1. BlockDef 未注册就 ref:blockId 找不到定义 → 展开为空。先 mapcadLayer.blocks.add(def)。注意顺序:new BlockDef() 后 blockId 还是 "",blocks.add() 才生成——所以必须 add 完再 new BlockRefEnt(def.blockId, ...)。
  2. ref.blockName/ref.getNestEntities() 依赖注入:二者要经 mapcadLayer.addEntity(ref) 注入 blocks 引用(内部字段 _blocksRef)才有效——注入前 blockName 返回 ''、getNestEntities() 返回 []。注意缓存中毒:getNestEntities() 结果缓存在 _cachedNestEnts,而 setBlocksRef 注入时不清缓存——若在注入前查过一次,注入后拿到的仍是 [],须调 ref.changed() 才重建(changed() 会清缓存)。blockName 是活 getter 不受影响,会出现"blockName 正常但 nest 恒空"的假象。
  3. 引用用错键:new BlockRefEnt 第 1 参是 def.blockId(blocks.add 时生成的 id),不是 'chair' 这个名字;按名反查用 blocks.itemByName('chair').blockId。
  4. 动态修改 BlockDef 不刷新:改完定义要把引用 changed() 标脏(见第六节)。
  5. position 字段不生效:BlockAttribute.position 只被序列化保留——渲染时属性文字显示在 ATTDEF 的位置,DWG 导出只写 {tag: textString}。要控制文字位置,改定义里那个 ATTDEF TextEnt 的 insertionPoint(本地坐标)。