Skip to content

viewer.labels — LabelsManager

设计意图

viewer.labels序号标签域——装配图上那些「①②③」的气泡与引线。它管的是数据模型与投影,不管画:引擎负责维护「几号标签贴在哪几个零件上、气泡落在视口哪个位置、引线锚在零件表面哪一点」,以及每帧把三维锚点投影成屏幕坐标;真正的渲染交给宿主(viewer-ui 提供的 <fmb-label-overlay> 就是一个现成实现)。

三个设计要点:

  • 一个号可以带多个零件(members,members[0] 是代表件)。同一种螺栓在装配里有 20 个实例,它们该共用一个序号——relateSameMesh() 一步把同 mesh 的全部实例并入。
  • 引线锚点存零件局部坐标(anchorLocalPoint),不是世界坐标。零件被变换编辑挪动或爆炸散开后,锚点跟着零件走,引线不会指到空气里。
  • 标签是视图级的。号在当前视图内唯一,不同视图各有一套标签;撤销步也带视图标签,走共享的 viewer.history

气泡落点用归一化 0..1 视口坐标存储,所以换分辨率、换窗口大小,标签的相对位置不变。

何时用它:装配说明图、BOM 对照图、需要给零件编号讲解的场景。

典型用法

点选零件贴标签(订阅 label-pick 拿到表面命中点作引线锚):

ts
viewer.on("label-pick", (e) => {
  if (e.item.type !== SelectionEntityType.Node) return;
  const number = viewer.labels.addLabel(e.item.nodeId, undefined, e.worldPoint);
  // 不传 pos 时,气泡自动落在点击处附近(而不是全叠在视口正中)
});

渲染循环里把标签投影到屏幕(自定义 overlay 的接法):

ts
viewer.on("labels-changed", redraw);
viewer.on("camera-changed", redraw);

function redraw() {
  const { labels, style } = viewer.labels.list();
  for (const l of labels) {
    const anchor = viewer.labels.getLabelAnchorScreen(l.number); // { x, y, behind } | null
    drawBalloon(l.number, l.pos, anchor, style); // l.pos 是归一化 0..1
  }
}

拖拽气泡(拖动期间实时更新且不记史,松手压一条撤销步):

ts
onDragStart = (n) => viewer.labels.beginMove(n);
onDragMove = (n, x, y) => viewer.labels.moveLabel(n, { x, y }); // 归一化 0..1
onDragEnd = () => viewer.labels.commitMove();

编号维护:

ts
viewer.labels.relateSameMesh(nodeId); // 同 mesh 的全部实例并入该号
viewer.labels.mergeNumbers([2, 5, 7]); // 合并为最小号(返回保留的号)
viewer.labels.setNumber(5, 1); // 改号;目标号已存在则两者交换
viewer.labels.renumber(); // 重排为紧凑的 1..N(已紧凑则不压撤销步)
viewer.labels.deleteLabel(3);
viewer.labels.clearViewLabels(); // 清空当前视图的全部标签

样式(视图级,一套作用于该视图全部气泡):

ts
viewer.labels.setStyle({ shape: "hex", content: "numqty", leader: "elbow", term: "arrow" });

注意事项

  • label-pick 只在真实视口拾取时发,程序化 selection.select() 不发——所以它天然只对应「用户点了模型上的这一点」,适合做贴标签的入口。
  • moveLabel 不记史:它是拖拽过程中的实时更新。要让拖拽可撤销,必须 beginMove() → 多次 moveLabel()commitMove() 三段配齐。
  • 气泡 pos 是归一化 0..1 并被钳制,超出范围的输入会被夹回视口内。
  • getLabelAnchorScreen 返回 behind 标志:锚点在相机背后时为 true,宿主应据此隐藏引线(否则会画出穿到屏幕另一侧的线)。锚点取不到时返回 null
  • setNumber 是交换语义,不是覆盖:目标号已被占用时,两个标签互换号,不会丢标签。
  • mergeNumbers 保留最小号并返回它;少于两个有效号时不做事。
  • 删除零件会静默摘除其 membership:结构删除是原子事务的一部分,不额外压撤销步(整条删除步撤销时标签一并恢复)。
  • 换模型清空标签:model-disposed 时自动重置。

完整签名与延伸