Skip to content

加载时应用视图(view-on-load)

把模型装入场景时,常常希望一并恢复上次保存的「视图」——相机角度、隐藏了哪些零件、有没有爆炸/平移、染了什么色、什么显示模式。如果等模型几何全部加载完再去套用,用户会先看到一帧默认 fit 视图、随后「啪」地跳到目标视图。viewer.model.load(source, opts)view-on-load骨架就绪(model-skeleton-ready)阶段就把视图套好,消除这次闪烁。

可交互 demo · 底部控制面板演示 apply(运行时)与 state / document / fetch 三种 load 时形态在新窗口打开 ↗

API

ts
interface ModelLoadOptions {
  /** 加载后(骨架就绪时)应用一个视图。 */
  view?: ViewOnLoad;
  /** 应用时是否动画飞入(默认 false:首帧直接就位,不飞入)。 */
  animateView?: boolean;
}

type ViewOnLoad =
  | { state: ViewState } // ① 直接给视图态
  | { document: ViewDocument | string; select?: string | number } // ② 文档对象 / JSON 字符串
  | { fetch: true; url?: string; select?: string | number }; // ③ 自动 fetch sidecar

{ state } —— 直接给一个 ViewState

适合在内存里已经拿到一个 ViewState(例如先 viewer.views.capture() 存下、重载时还原):

ts
const snap = viewer.views.capture(); // { cam, projection, displayMode, hidden, transforms, colors, ... }
await viewer.model.load(bytes, { view: { state: snap } });

{ document } —— 一个 ViewDocument(或其 JSON 字符串)

.fmbview.json 的内容就是一个 ViewDocument。可以把解析好的对象、或原始 JSON 字符串直接交给 load,并用 select 指定应用其中哪一个视图:

ts
const jsonText = await readSidecarSomehow(); // 字符串
await viewer.model.load(bytes, { view: { document: jsonText, select: "Camera 1" } });

select 可为视图 idname 或整数索引;缺省时取文档的 activeId,再退化到第一个视图。

{ fetch: true } —— 自动拉取同源 sidecar

模型来自 URL 时,可让引擎自动推导并 fetch sidecar(默认 <modelUrl>.fmbview.json,去掉 query/hash 后追加后缀);也可用 url 显式指定:

ts
// 自动推导:models/part.fmbv → models/part.fmbv.fmbview.json
await viewer.model.load("https://cdn.example/models/part.fmbv", { view: { fetch: true, select: 0 } });
// 或显式指定 sidecar URL
await viewer.model.load(modelUrl, { view: { fetch: true, url: viewsUrl } });

.fmbview.json 文档格式(ViewDocument v2)

ts
interface ViewDocument {
  version: 2;
  signature: { nodeCount: number }; // 陈旧守卫:与当前模型节点数比对
  activeId: string; // 默认选中视图的 id
  views: { id: string; name: string; snap: ViewState }[];
}
  • signature.nodeCount 守卫:若文档记录的节点数与当前模型不一致(模型被改过),整份文档视为不兼容,跳过应用——不会把陈旧的隐藏/变换套到对不上的节点。
  • 版本:仅接受 version: 2;其它版本解析返回 null → 跳过。
  • 文档解析、兼容性判断、选视图都是纯函数,可单独使用:parseViewDocument(json)isViewDocumentCompatible(doc, nodeCount)selectView(doc, selector)deriveSidecarUrl(modelUrl)

view-applied 事件

每次 view-on-load 解析后,引擎发一个 view-applied 事件回报结果——无论成功还是被跳过:

ts
viewer.on("view-applied", (e) => {
  if (!e.applied) viewer.camera.fitView(); // 没套上(无 sidecar / 不兼容 / 坏 JSON)→ 退回默认 fit
});

e.reason 取值:

reason含义
ok已成功应用
not-foundsidecar 不存在 / 文档里没有匹配的视图
parse-errorJSON 损坏或非法结构
signature-mismatchnodeCount 不符,文档与当前模型对不上
empty文档存在但没有任何视图

view-on-load 失败只是 skip,绝不 reject load()——加载本身照常完成,你据 view-applied.applied 决定是否兜底 fitView()

运行时应用:viewer.views

view-on-load 是「加载时」入口;运行期随时可用 viewer.views(ViewsManager)捕获/应用视图:

ts
const snap = viewer.views.capture({ displayMode: "shaded" }); // 捕获当前场景为 ViewState
viewer.views.apply(snap, { animate: true }); // 平滑过渡应用(相机/隐藏/变换/外观同帧驱动)

上层应用接入现状

  • desktop 编辑器:直开 .fmbv 时,在源文件旁读 <源>.fmbview.json 并经 { document } 在骨架就绪套用;若套用成功则跳过 fitView,消除首帧闪烁。
  • web 编辑器:同源 ?model=<URL> 深链时,按 deriveSidecarUrl 自动 { fetch: true };本地拖入的文件(无法访问同目录 sidecar)走普通加载。
  • 独立阅读器 @fmb/viewer:支持 ?model= / ?view=<id|name|index> / ?views=<sidecarUrl> 深链。

相关