唯杰地图 logo唯杰地图
VJMAP
EN

关联标注(最常用反应器场景)

vjcad/references/reactor/dimension-association.md 7.2 KB152 行
原文

示例:vjcad/example/reactor/02dimension.js 文档:vjcad/doc/reactor/index.md、vjcad/doc/command/dimension-commands.md

vjcad 标注实体(LinearDimensionEnt、AlignedDimensionEnt、RadialDimensionEnt 等)已经内置了 IEntityReactor。直接调用 dim.setSourceEntities(...) / dim.setSourceEntity(...) 就能让标注随源实体自动更新,无需自己实现接口。方法名按标注类型不同:

标注类 关联方法
LinearDimensionEnt / AlignedDimensionEnt setSourceEntities(startId, startSegIdx, endId, endSegIdx, startPointType?, endPointType?, startT?, endT?)
TwoLineAngularDimensionEnt / AngleDimensionEnt setSourceEntities(line1Id, line1SegIdx, line2Id, line2SegIdx)
ThreePointAngularDimensionEnt setSourceEntities(e1Id, e1SegIdx, p1Type, e2Id, e2SegIdx, p2Type, centerId?, centerType?, t1?, t2?, tc?)
RadialDimensionEnt / DiametricDimensionEnt setSourceEntity(circleId, pointAngle?)(单数)
ArcDimensionEnt setSourceEntity(arcId)(单数)
OrdinateDimensionEnt setSourceEntity(entityId, pointType?)(单数)

1. setSourceEntities 签名

dim.setSourceEntities(
    startLineId, startSegIdx,    // 起点关联实体 ID + 线段下标(LINE 忽略下标,传 -1 即可;PLINE 必须 ≥0)
    endLineId,   endSegIdx,
    startPointType, endPointType, // 'start' | 'end' | 'midpoint' | undefined(类型里有 'center' 但未实现,回退为起点)
    startParameterT, endParameterT // 可选:0~1 曲线参数(更精细的位置)
);

注意:必须 先 Engine.addEntities(dim),再调用 setSourceEntities。否则标注还没拿到 docId,注册不到反应器表。

只支持 LINE 与 PLINE 的直线段作为 Linear/Aligned 的源:PLINE 按 segIdx 取 getSubEnts()[segIdx],该段须是直线(segIdx=-1 或弧段 → 取不到点,标注不更新);ARC/CIRCLE 等其他类型同样取不到点。

2. 标准用法:水平线性标注关联到一段直线

const line1 = new LineEnt([0, 0], [100, 0]);
line1.setDefaults();
Engine.addEntities(line1);

const dim = new LinearDimensionEnt(
    new Point2D(0, 0),    // 测量起点
    new Point2D(100, 0),  // 测量终点
    new Point2D(50, 25),  // 标注线位置点
    0                     // 标注角度(弧度,0=水平)
);
dim.setDefaults();
Engine.addEntities(dim);

dim.setSourceEntities(
    line1.id, -1,          // 起点关联整条 line1
    line1.id, -1,
    'start',               // 起点取 line1 的起点
    'end'                  // 终点取 line1 的终点
);

之后 line1.move(...) / 修改端点,dim 会在下一次 Engine.regen() 时重新测量(见下方说明)。

⚠️ 联动链路是 owner.setModified()(只置 _modified 旗标)→ Engine.regen()(bucket 脏扫描时派发 EntityModified)→ 反应器桥 notifyOwnerChanged → dim.setReactorDirty() → updateDirtyReactors()/buildNestEnts 时 updateFromOwners() 重算测量值。只改属性不 regen 不会联动;Engine.updateEntities/Engine.pcanvas.updateEntities 均不存在,勿用。也可手动 ReactorNotifier.notifyModified(entity) 直接驱动。

3. 对齐标注关联斜线

const line2 = new LineEnt([0, -60], [80, -100]);
line2.setDefaults(); Engine.addEntities(line2);

const aligned = new AlignedDimensionEnt(
    new Point2D(0, -60),
    new Point2D(80, -100),
    new Point2D(50, -65)
);
aligned.setDefaults(); Engine.addEntities(aligned);
aligned.setSourceEntities(line2.id, -1, line2.id, -1, 'start', 'end');

AlignedDimensionEnt 自动按两端点连线方向出标注。

4. 标注跨越两条线段(端点 → 端点)

const line3 = new LineEnt([150, 0], [200, 40]);
const line4 = new LineEnt([200, 40], [260, 0]);
[line3, line4].forEach(l => { l.setDefaults(); Engine.addEntities(l); });

const span = new LinearDimensionEnt(
    new Point2D(150, 0),
    new Point2D(260, 0),
    new Point2D(205, -25),
    0
);
span.setDefaults(); Engine.addEntities(span);

span.setSourceEntities(
    line3.id, -1,    // 起点 → line3 的 start
    line4.id, -1,    // 终点 → line4 的 end
    'start',
    'end'
);

5. 用 parameterT 精确定位(线段中段)

pointType 拿不到的位置(例如线段 1/4 处),用 parameterT(0~1):

const line5 = new LineEnt([0, -150], [120, -150]);
line5.setDefaults(); Engine.addEntities(line5);

const partial = new LinearDimensionEnt(
    new Point2D(30,  -150),  // 1/4 处
    new Point2D(90,  -150),  // 3/4 处
    new Point2D(60,  -130),
    0
);
partial.setDefaults(); Engine.addEntities(partial);

partial.setSourceEntities(
    line5.id, -1,
    line5.id, -1,
    undefined, undefined,    // 不使用 pointType
    0.25,                    // startParameterT
    0.75                     // endParameterT
);

完整 4 个示例都在 vjcad/example/reactor/02dimension.js。

6. 检查关联状态

console.log(dim.isAssociative); // 仅表示 _ownerRefs 非空(调过 setSourceEntities 且 id≥0),不校验源实体存在/有效
dim.getSourceEntityRefs();      // 查看关联引用

// 读取联动后的测量值:惰性更新,先通知再 regen,然后读公开方法 getMeasurement()
line1.endPoint = new Point2D(150, 0);
ReactorNotifier.notifyModified(line1);   // 让 dim 立即标脏
Engine.regen();                          // regen 开头 updateDirtyReactors() → updateFromOwners()
console.log(dim.getMeasurement());       // 150

红旗

  • 顺序很重要:先 Engine.addEntities(dim),再 dim.setSourceEntities(...)
  • ⚠️ 纠正:Engine.eraseEntities(owner) 首次删除并不通知反应器——EntityErased 单数事件从不派发(源码 0 个 emit 点),ReactorNotifier.notifyErased 只挂在 ERASE 撤销记录的 pushRedo 里,即只有 undo 后再 redo 才触发 unlinkAllOwners。正常 erase 后 dim.isAssociative 仍为 true、_ownerRefs 残留死引用——需要清理时自己 dim.unlinkAllOwners() 或在 EntitiesErased(复数,正常派发)事件里手动通知
  • 修改源实体后标注是惰性同步更新(不是异步):Engine.regen() 开头的 updateDirtyReactors() 才调 updateFromOwners();而 EntityModified 在 regen 的 bucket 扫描里才派发,晚于这一步——所以要么先 ReactorNotifier.notifyModified(owner) 再 Engine.regen(),要么 regen 两次,之后读公开的 dim.getMeasurement()
  • setSourceEntities 接收的是 number 类型的 entity.id(d.ts:line1Id: number),不要传 entity 对象本身
  • pointType: 'midpoint' 在多段线上是 segIdx 那一段的中点,单线段上等价于 parameterT: 0.5;parameterT 优先于 pointType;'center' 未实现(落到 default 分支取起点)

参考(官方文档 MCP)

  • 示例:vjcad/example/reactor/02dimension.js
  • 文档:vjcad/doc/reactor/index.md、vjcad/doc/command/dimension-commands.md