Appearance
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时自动重置。
完整签名与延伸
LabelsManager— 全部方法的精确签名。ViewLabel/LabelStyle/DEFAULT_LABEL_STYLE— 标签数据与样式取值。- 组件:
<fmb-label-overlay>— 现成的气泡 / 引线渲染层,接上就能用。 - 指南:编辑能力 — 三类编辑与统一撤销栈的全景。
- 概念:事件系统 —
labels-changed/label-pick事件参考。