Appearance
架构总览
@modelcubes/viewer-core 的公开 API 围绕单一入口 Viewer 组织:Viewer.create(canvas) 是唯一的创建方式,功能按域拆给一组只读 manager 属性,状态变化经事件总线对外广播。理解这三件事,就理解了整个引擎的使用方式。
Viewer 单一入口与 manager 分域
Viewer 实例本身只做四件事:创建/销毁(Viewer.create() / dispose())、事件订阅(on / off / once)、尺寸同步(画布 CSS 尺寸变化引擎经 ResizeObserver 自动跟踪,resize() 是宿主显式逃生口)、把能力按域转交给 manager。所有具体功能都经 manager 属性访问:
ts
const viewer = await Viewer.create(canvas);
await viewer.model.load("/fixtures/demo.fmbv"); // 模型域
viewer.camera.fitView(); // 相机域
viewer.edges.setVisible(false); // 边线域公开 manager 分两组。浏览能力是任何集成都会用到的 14 个:
| 属性 | 职责 |
|---|---|
viewer.model | 模型加载 / 卸载、结构树查询与节点级外观控制(颜色 / 可见性 / 高亮)。 |
viewer.camera | 相机位姿、标准视图、取景适配(zoom-to-fit)与交互控制。 |
viewer.selection | 点击 / 框选 / 程序化选中与 hover 高亮。 |
viewer.edges | 全局边线显示开关、颜色与不透明度。 |
viewer.renderMode | 全局与按节点的渲染模式(实色线框 / 线框 / 实色 / 消隐)。 |
viewer.lights | 场景光源的增删与强度调整。 |
viewer.stats | 加载与渲染统计快照(fps / 帧时 / 分块进度等)。 |
viewer.navcube | 画布角落导航立方体的开关 / 停靠 / 尺寸。 |
viewer.background | 画布背景(纯色 / 渐变 / 图片 / 透明)。 |
viewer.export | 矢量 SVG 工程图导出(消隐线条)。 |
viewer.explode | 零件爆炸(散开 / 锁定 / 动画)。 |
viewer.section | 剖切 section 与盖面 / gizmo 交互。 |
viewer.quality | 渲染质量(抗锯齿 / AO / 色调映射 / 描边 / 质量档)。 |
viewer.views | 视图捕获 / 应用,以及视图集(.fmbview.json)导航。 |
编辑能力是另外 4 个,只在需要修改并保存模型呈现的宿主(编辑器一类)里用得上——纯阅读器可以完全无视它们:
| 属性 | 职责 |
|---|---|
viewer.transform | 零部件位置编辑(平移 / 旋转 / gizmo)。 |
viewer.explodeEdit | 编辑态爆炸:把散开位移写进变换通道,可撤销、可存盘。 |
viewer.labels | 序号标签(编号 / 成员 / 气泡落点 / 引线锚点)。 |
viewer.history | 统一编辑撤销栈——变换 + 外观 + 可见性 + 标签共用一条栈。 |
结构删除 / 合并与外观编辑归在 viewer.model 内(编辑的对象是模型本体)。全景见编辑能力。
此外还有一个 viewer.diagnostics(LOD 等调试诊断查询),属实验性 API,面向内部排障,可能在小版本间变动,外部集成请勿依赖。
manager 都由 Viewer 在构造时装配好,不要自行 new;viewer.dispose() 之后再调用任何 manager 方法会抛 ViewerError(code 为 Disposed)。
事件总线
引擎到应用方向的状态通知全部走事件:viewer.on(event, handler) 订阅,返回一个退订函数;once 只触发一次;off 需要传入与订阅时相同的 handler 引用。
ts
// 推荐:保存 on() 返回的退订函数,清理时直接调用
const unsubscribe = viewer.on("selection-changed", (e) => {
console.log("当前选中", e.current.length, "项");
});
// 不再需要时
unsubscribe();事件清单与各事件 payload 见事件系统。
底层渲染实现完全隐藏
viewer-core 的公开 API 不暴露任何底层渲染实现的类型:坐标用纯数据对象 Vec3 / Box3,矩阵用列主序 Float32Array,颜色用 { r, g, b, a }。这是有意的设计边界——
- 你的集成代码不需要(也不应该)依赖引擎的底层渲染实现,渲染内核将来升级或演进不会破坏你的集成;
- 不要试图访问 viewer 的内部对象(场景图、renderer 等),内部结构不属于兼容性承诺,任何版本都可能变。
需要的能力如果公开 API 没有覆盖,提需求而不是绕过公开 API。
渲染循环:按需渲染
Viewer.create() resolve 时渲染循环已经启动,但它不是无条件每帧重绘——引擎采用 render-on-demand:只有画面确实需要变化时才渲染,场景静止时 GPU 完全空闲。这对 CAD 场景很重要,用户看着一个静止的装配图思考时,笔记本不该在满负荷转风扇。
失效(invalidation)是自动的:所有经公开 API 的状态变更——相机、选中、hover、外观、显隐、剖切、变换、标签、视图、投影、结构编辑——都已接线到失效检测,改完下一帧就会重绘。集成方通常不需要手动触发任何东西。
只有一种情况需要介入:绕过公开 API 直接改了会影响画面的状态。这时调 viewer.invalidate() 请求下一帧重绘。
ts
viewer.model.setNodesColor(ids, red); // 自动失效,无需额外操作
viewer.invalidate(); // 兜底逃生口,正常集成用不到render 事件是约每秒一次的心跳,payload 中 fps 是该区间的平均帧率、frameMs 是最近一帧的渲染耗时,适合驱动状态栏一类的低频 UI;更完整的统计快照经 viewer.stats 查询。注意画面静止时心跳也会停——不要把它当固定节拍的定时器用。
下一步
- 坐标系与单位 — Z-up 约定、标准视图与 up-axis 自动处理。
- 模型数据 (.fmbv) — 数据从哪来、
load()接受哪些来源。 - 事件系统 — 全事件参考与模型加载时序。