Appearance
事件系统
引擎到应用方向的状态通知统一走事件总线,经 viewer.on() / once() 订阅。事件名与 payload 类型由 ViewerEvents 接口定义,TypeScript 下 handler 参数自动获得正确类型。
on / off / once
ts
// on:订阅,返回退订函数(推荐的清理方式)
const unsubscribe = viewer.on("camera-changed", (e) => {
console.log(e.position, e.target);
});
unsubscribe();
// off:显式退订,需传入与订阅时相同的 handler 引用
const handler = (e: { fps: number; frameMs: number }) => console.log(e.fps);
viewer.on("render", handler);
viewer.off("render", handler);
// once:只触发一次,触发后自动退订;同样返回退订函数
viewer.once("model-loaded", () => console.log("全部几何已就绪"));dispose 后的行为:viewer.dispose() 之后再调 on / once 会抛 ViewerError(Disposed);off 静默返回。已注册的订阅在 dispose 时全部释放,无需手动清理。
模型加载事件时序
model.load() 一次成功加载触发的事件顺序:
text
load(source) 调用
│
├─▶ "model-loading" load 开始,payload 带原始 source
│
├─▶ "model-skeleton-ready" 结构树骨架 + 索引就绪
│ │ ★ load() 的 Promise 在此 resolve,模型已可交互
│ │
│ ├─▶ "view-applied" 传了 view 选项时:该视图是否套上(见 view-on-load)
│ │
│ ├─▶ "model-geom-progress" ┐ 几何分块后台流式到达,
│ ├─▶ "model-geom-progress" ├ 每块更新 ready/total
│ └─▶ ... ┘
│
├─▶ "model-totals-ready" 全量三角 / 边线总数算好(后台读 chunk 头汇总,与几何加载并行)
│
├─▶ "model-loaded" 几何加载收敛(见下)
│
└─▶ "model-edges-ready" 全部已加载可见 mesh 的边线挂载完成model-loaded 的语义是加载收敛,不是「全部分块都解码完」:引擎按相机跳过离屏与亚像素的零件,它们的几何永远不会被请求,所以「已到达数 == 总数」在很多相机位姿下根本不成立。收敛的判据是「引擎不再请求新分块且在飞请求清零」;发事件前引擎会补发一次 model-geom-progress{ ready: total },让进度条正常收口到 100%。
想在「模型看起来已经完整」时收起加载指示,监听 model-loaded 即可。要在骨架可交互时就放行 UI(结构树、属性面板),监听更早的 model-skeleton-ready。
再次 load() 或 unload() 时先触发 "model-disposed"(卸载当前模型),再开始新的时序。加载失败不走事件——load() 的 Promise 直接 reject ViewerError(见模型数据)。
全事件参考
事实源是 ViewerEvents 接口(API 参考中可查每个 payload 的完整类型)。
模型生命周期
| 事件 | 触发时机 | payload 要点 |
|---|---|---|
model-loading | model.load() 开始 | source:原始 ModelSource |
model-skeleton-ready | 骨架(结构树 + 索引)就绪,load() 的 Promise 在此 resolve | boundingBox / nodeCount / meshCount |
model-geom-progress | 几何分块流式加载进度 | ready 已到达数 / total 总数 |
model-loaded | 几何加载收敛(不再请求新分块且在飞清零) | 空对象 |
model-edges-ready | 全部已加载可见 mesh 的边线挂载完成(在 model-loaded 之后) | 空对象 |
model-totals-ready | 整模型全量几何统计算好(后台读分块头汇总,一次性) | triangles / edges 总数(LOD-0 口径) |
model-error | 预留,当前无发射点——加载错误经 load() 的 Promise reject 抛出 | error: ViewerError |
model-disposed | 当前模型被卸载(显式 unload() 或再次 load() 替换) | 空对象 |
model-bounds-changed | 场景有效包围球变化(如爆炸偏移外扩) | boundingSphere |
visibility-changed | 节点可见性实际变化(hide / show / isolate / reset) | hiddenCount:当前隐藏叶子总数 |
appearance-changed | 节点外观变化(颜色 / 表面 / 预设) | nodeIds 受影响节点;animated: true 表示由渐变动画完成触发 |
structure-changed | 结构树逻辑编辑(删除 / 撤销删除 / 合并 / 取消合并 / 批量应用) | change 五种之一、affected 受影响的根;change: "apply" 时须全量重建并忽略 affected |
选中与悬停
| 事件 | 触发时机 | payload 要点 |
|---|---|---|
selection-changed | 选中集变化 | added / removed 本次增删项,current 完整选中集 |
hover-changed | hover 对象变化 | item:当前 hover 项,移出模型时为 null |
context-menu-requested | 触控设备上画布被长按(500ms) — 触控没有右键,这是上下文菜单的唯一入口 | x / y 画布局部坐标(可直接喂 pick)、clientX / clientY 视口坐标(定位浮层)、source: "touch" |
context-menu-dismissed | 菜单弹出后用户又落下第二根手指(他要的是双指手势) — 宿主必须收起菜单 | 无 |
label-pick | 视口真实拾取命中实体(程序化选中不发) | item 命中项、worldPoint / worldNormal 世界命中点与法向 — 序号标签取表面锚点用 |
渲染与相机
| 事件 | 触发时机 | payload 要点 |
|---|---|---|
lod-changed | 某网格本帧实际显示的 LOD 级切换(由流式加载与屏幕误差驱动) | meshId / fromLod / toLod |
camera-changed | 相机位姿变化(交互期间约 30Hz 节流) | position / target / up |
camera-interaction-changed | 相机交互开始 / 结束(拖拽 / 平移 / 缩放) | interacting 布尔 — 引擎据此在拖拽时抑制 hover 拾取,宿主也可用它做交互期降载 |
projection-changed | 投影类型切换(透视 ↔ 正交) | projection |
render | 渲染心跳,约每秒一次 | fps 区间平均帧率 / frameMs 最近一帧渲染耗时 |
渲染是按需的
引擎采用 render-on-demand:场景静止时不重绘,render 心跳也随之停下。不要把 render 当作固定节拍的定时器——它只在画面确实在动时才来。需要在每帧做事的逻辑请挂到自己的 requestAnimationFrame,需要在状态变化后重绘请用经公开 API 的操作(已自动失效)或兜底的 viewer.invalidate()。
剖切
| 事件 | 触发时机 | payload 要点 |
|---|---|---|
section-changed | 剖切状态变化(平面增删改 / 激活集变化) | activeSectionId(多 section 激活时取最近激活者)/ planeCount |
section-activated | 某 section 被激活(开始参与裁剪) | sectionId |
section-deactivated | 某 section 被反激活(退出裁剪) | sectionId |
section-plane-drag-start | 开始用 gizmo 拖拽某剖切平面 | sectionId / planeId / plane |
section-plane-drag | gizmo 拖拽中,平面每次更新都触发 | 同上,plane 为当前平面 |
section-plane-drag-end | gizmo 拖拽结束 | 同上 |
视图
| 事件 | 触发时机 | payload 要点 |
|---|---|---|
view-applied | 一次 view-on-load 应用结束(成功或被跳过) | applied 布尔 + reason:ok / not-found / parse-error / signature-mismatch / empty |
view-changed | 切换了视图、装载 / 清空了视图信封,或「已偏离当前视图」翻转 | index / id / name / count / dirty;无信封时 count 为 0、index 为 -1 |
编辑
只有用到编辑能力(编辑能力)的宿主才需要这一组。
| 事件 | 触发时机 | payload 要点 |
|---|---|---|
transform-start | 变换编辑开始(拖拽起,或一次数值提交前) | nodeIds |
transform-changed | 变换编辑进行中(gizmo 拖拽逐帧;数值提交时发一次) | nodeIds;animated: true 表示由渐变动画驱动 |
transform-end | 变换编辑结束(松手,或一次数值提交后) | nodeIds — 重活(重算 BOM / 写盘)挂这个,别挂 changed |
transform-reset | 复位某些 / 全部节点的编辑 | nodeIds;空数组表示全部复位 |
transform-editmode-changed | 变换编辑模式开关(gizmo 显示 / 隐藏) | editing |
transform-history-changed | 变换撤销 / 重做可用性变化 | canUndo / canRedo |
history-changed | 统一撤销栈可用性变化(变换 + 外观 + 可见性 + 标签共栈) | canUndo / canRedo — 新代码用这个驱动撤销按钮 |
labels-changed | 序号标签集合变化(增删 / 移动 / 样式 / 绑定) | count |